The most important part of any RPG is its combat. For Skymagi, it is about spellcasting, but mages can also fight without their sigils.
While most spells will require mana, autoattacking should not. I decided mages should have cantrips, simple spells that do minor effects without costing resources. Each mage’s cantrip should be somewhat unique, but not necessarily a defining feature.
All mages should be able to perform some actions:
Action | Effect |
---|---|
Autoattack | Do some damage |
Repair | Fix a broken sigil, drain room, or fix floor |
Extinguish | Put out a fire! |
So I gave each an autoattack and occasionally designed a cantrip that could be helpful utility. Not all autoattacks are equal.
Mage | Autoattack | Effect | Other Cantrip |
---|---|---|---|
Apprentice | Arcane Missile | Damage | |
Pyromancer | Firebolt | Damage | Gust: disperse gases |
Hydrosophist | Icebolt | Damage, minor slow effect | Splash: Faster fire extinguish |
Druid | Boulder | Slow, higher damage | |
Chronomancer | Thunder | Adds a static charge that can create a bounce effect, but minor damage | |
Necromancer | Life Drain | Heals the mage for some of the damage done | |
Priest | Astral Strike | Low damage, but creates a minor self-shield | Holy Shield: Provides a minor shield to another mage |
Battlemage | I hit people | Melee, but much higher damage |
So, first step, we actually need health!
Red/Green is the worst combination for colorblind folks, and we need the idea of a faction. I swapped the colors to be blue/red and used gray to indicate missing health.
Autoattacking
Autoattacking seems easy, but there is a lot of nuance in most modern RTS/MOBAs.
Autoattacks have a cast time to release that is incredibly short, usually matching pace of an animation, and they have an internal cooldown that ticks.
If done properly, this allows the user to stutter step, where they use the internal cooldown time to move, but ensure they get in each attack, and don’t move within the small cast time. It’s a skill and can be a critical part of a strategy game.
So here’s all of that together with no visuals to indicate any of it yet!
It’s only melee, doesn’t create a projectile, and there are no animations. And units don’t die when they run out of health. Let’s start with that.
Death
So I had to add in death. Death is tricky in an ECS system, because it requires removing an entity and all of its components. The general advice is flag entities as dead and then actually do removal at the end of an update cycle. That way you don’t break a system that is actively updating, and it allows you to parallalize systems in the future.
So I added a new component to flag an entity for removal, DEATH!
public class DeathComponent implements Component, Pool.Poolable {
@Override
public void reset() {}
}
Another tricky part of death. An entity doesn’t just disappear when it dies, it typically has an animation. One idea is to actually remove the entity, then create a new entity just to play the death animation.
I chose to just modify the entity, remove all components except its animation, and let it play out. Once the animation ends,
the DeathSystem
removes it entirely.
Projectiles
Projectiles add an additional level of complexity. Rather than just dealing damage directly, a new entity must be spawned that tracks another entity and on collision applies an effect.
It’s a bit silly with placeholders.
Sometimes really silly.
I wanted to at least have projectiles rotate towards their target. Calculating that angle is a simple atan2()
operation,
but unfortunately, I chose to write this game without an engine. Which means I didn’t yet have the ability to render sprites rotated,
scale them, or any of that. LibGDX makes the sprite rendering piece easier, but my RenderSystem
didn’t account for it.
Well, that only took like… 2 hours.
Now the projectiles must be deleted on collision and apply damage to the enemy.
Stutter Stepping & Animations
Typically an animation for autoattacks shouldn’t wait for an entire animation to play to release the projectile. Usually there is a wind-up and a follow-through.
So the autoattack cast time must be synced to the animation. Unfortunately, my animation
was a simple state machine. After the attack released, it would swap from state Attacking
back to Idle
and the follow-through animation wouldn’t play.
I decided to just add a special exception for follow-through, but I don’t love it as a solution.
Now stutter stepping is obvious and feels pretty good. I look for the light on the staff, and learn the timing:
Auto-attacking is done as a system, but there’s still quite a few specific mechanics and other cantrips that need to be implemented. I’ll save those for later.