In my previous post I described my enemy combat system. Since then, I’ve made a few tweaks.
HitBoxes
The EnemyAttackData scriptable object now has a hitBoxId field. I decided to go simple on this vs creating a
few dozen micro assets. It’s just a simple string, but it’s used to correspond with hit boxes attached to various
bones on the enemy mesh.
Then within the MeleeEnemyCombatController I assign each of the HitBox components to a list before which gets converted
into a dictionary so that when we begin our next attack, we can map the hitBoxId to a specific hitbox. Using animation
events (via a custom AnimationEventRouter) - I can listen for events such as damage_start and damage_end to
enable / disable the collider. The hitbox components also have a configurable tags list to determine what objects they
should collide with as well as reports via event when they did hit something via the HitBoxCollisionResult.
Now within the combat controller, I can simply react to the event and pass along the damage to the target. This uses
the unified DamageProcessor component in order to scale damage, determine critical hits, apply damage reduction, etc.
BoneId
For the RangedEnemyCombatController the enemy attack data also exposes a BoneId property which works pretty much
exactly the same way the HitBoxId works, only instead of storing a dictionary of hitboxes, it stores the transform
associated with the bone. This allows enemies to have multiple projectile spawn points for different ranged attacks.
My next task is to replace the simple projectile spawn behaviour with the actual ProjectileSpawner which has features
such as linear burst shots, cone bursts, circlular bursts, and “random” bursts.
Attack Flow
For melee enemies, it’s fairly straight forward. When the enemy spawns, the next attack is selected so that we can determine
the attack range. This sets two properties in the EnemyContext:
- NextAttack
- NextAttackStatus
While the next attack is pending, the status is pending. Once we enter into the attack state and the attack begins, the status is set to active. If the state exits due to death, stun, etc. the status is set to canceled, otherwise once the attack finished, the attack is changed to finished. Meanwhile in the combat controller, once the attack status changes to finished, the next attack is queued based on whatever isn’t on cooldown.
Ranged attacks work similarly, only the projectile is spawned during the attack windup phase which allows us to parent it to the bone id / spawn point it’s assigned to before the attack phase enters the ranged_release phase, in which case, the projectile is fired off.
Conclusion
It was a pretty fun system to build. I think the whole system only took me about 12 hours so far, but I have a lot more planned for it - for example, integrating my VFX system. It’s not going to win any awards, but I have a system that’s flexible, data driven, and easy to work with. I call that a win.