5 min read
Spellcasting

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:

fireball-targeting

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.

fireball projectile

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)!

Fireball Explode

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.

z-sort

naive renderlayers