Spellcasting is the bread-and-butter of Skymagi gameplay. All mages have a spellbook where they can learn spells, learned through gameplay and leveling up.
I’ve brainstormed a fairly substantial spell list for each mage specialization, and certain spells that will be common to all of them. Spells belong to a school of magic (abjuration, evocation, conjuration) and can be empowered by being cast by a sigil. Some spells can only be cast at a sigil.
Let’s start with an example, Fireball.
Fireball
Fireball is an AoE spell. The mage chooses a point and a projectile is spawned. When that projectile reaches its location (or is intercepted), it explodes within a radius dealing damage to anything around it. Fireball can also spawn fires that continue to deal damage.
If I want to create a generic system for spellcasting, I need to understand what all spells will have in common. I decided to breakdown Fireball like…
- Target: Area (AoE)
- Behavior: Projectile
- Effects: Damage, Spawn Entity
- Empowered: Fireball doesn’t deal siege damage to castles unless cast through a sigil
And added additional spell metadata:
- Spell: Cast time, range, mana cost, etc.
- School: Evocation
A mage will need to target a point, which will showcase an area. After selecting a target, the mage will determine if they are within range, and if not, move to cast the spell.
During casting, the mage will show a casting animation, and if not interrupted, will release the spell.
Step one: Targeting.
Targeting
What kind of targets could spells have?
public enum TargetType {
/** targets a single entity */
Entity,
/** targets a specific area / AoE */
Area,
/** targets a direction vector */
Direction,
/** targets self */
Self,
/** no target type */
None
}
MOBAs and RTSes want users to intuitively understand spell ranges and targets. However, that means I need a system that can show a target and command before the entity should actually exist.
I chose to create a CastCommandSystem
and CastTargetingSystem
to handle these issues.
The CastCommandSystem
determines when a player attempts to cast a spell via a Mage. I chose
to temporarily use QWER
with a set of slots where mages can place spells. When a user holds
Q
to cast, they are shown a targeting system, and on release, they cast the spell.
The CastTargetingSystem
sees that a mage has a CastTargeting
component for targeting a
particular spell. The system renders all pertinent information, including their current target.
It looks like this for Fireball:
The outer radius is the range, the second circle is the AoE. I’d like to add nice effects like adding a shader to units that are currently going to be hit, but that’s future polish.
Now, behavior…
Behavior
I refactored this like sixty times. I no longer have behavior, but instead have just ‘Triggers.’
-Kittems from the Future
I don’t love the term Behavior, but it’ll do for now. It is how the spell gets applied. A Projectile spell gets applied after it reaches a destination.
Some spells will occur instantly, others on projectiles, some on timing. Each of these behaviors is specific enough that I’ll likely want a system to handle it. So for now, I just wrote a Projectile behavior (reused it actually from autoattacking).
A projectile spawns after Cast, it reaches the target, and then dies.
It needs effects.
Effects
I cannot tell you how many times I read this fantastic article from Herbert Wolverson.
It is such a simple and yet elegant solution to creating any effect possible. I recommend anyone interested in gamedev to read the entire series, which I now treat as gospel.
However, my implementation obviously deviates quite a bit.
I chose to add a set of components that store data for various effects, such as HealthModEffect
or SpawnEntityEffect
.
My EffectsSystem
checks for when a spell cast occurs and applies effects to any targets on the spell entity.
In the case of Fireball, I can summarize the spell in a set of components:
fireball.spellCast = (engine, spellDef, caster, target) -> {
Entity spell = engine.createEntity();
// spawn at caster
spell.add(new Position(caster.x, caster.y))
// add our AoE target radius
.add(new Target(Targets.Area, 2))
// look like a fireball and go to the target
.add(new Speed(10))
.add(new Animation("spells/fireball/fireball-projectile"))
.add(new ProjectileBehavior(target, Behavior.HOMING))
// deal damage
.add(new HealthModEffect(-45, Element.Fire))
.add(new SpawnEntityEffect("core.effects.fire"))
// explosion effect 2x2 with 1.4 second time
.add(new DeathAnimation("spells/fireball/fireball-explosion", 2, 2, 1.4f));
// don't forget sounds!
AudioUtils.PlaySound("audio/combat/fireball-1.ogg", 1.0f);
}
I haven’t yet implemented SpawnEntityEffect
, but it uses the same prototype system to create a clone.
However, everything else is working (enjoy the placeholder explosion)!
Bonus Z-Sort
A small addition, but layers are now z-sorted properly, like this mage hiding behind a tree. Before, the mage would be on top of a tree.