Enemy Combat System Update

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.

The ProjectileBehavior allows for any subclass of ProjectileTrajectory to be assigned, such as linear, arc, homing, etc. Projectile pooling is done via a dictionary + queue which allows us to assign a prefab to the spawner as a ProjectileBehavior field, using the instance id to grab projectiles from the pool.

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:

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.