Last week I showed a brief preview of our new, work-in-progress soldier enemy. Today, let's take a closer look at the finished product.
We started with a simple mandate: create an enemy type that feels humanoid, but still synthetic. Here's Erik's first art concept for it, which is actually not far off from where we ended up:
Achieving a humanoid look is easy (just make it a human-like biped). Achieving a humanoid behavior is a little trickier. We used a combination of techniques:
- Naturalistic acceleration and deceleration when moving
- Legs sway dynamically based on the speed and direction of travel
- Real-time target tracking which interpolates through multiple gun poses to keep the gun pointed at the target without spinning the entire soldier in place
- Real-time head tracking and turnarounds so the soldier is actually looking at the target as well
- Killed soldiers fall out of the sky instead of just exploding on the spot
When it all comes together, it looks like this:
For the movement accel/decel, we simply use Vector3.SmoothDamp to approach the desired position, which in most cases (including in the video above) is the next waypoint on a path:
position = Vector3.SmoothDamp( position, m_desiredPosition, ref m_smoothingVelocity, m_smoothingTime );
This gives a nice, natural acceleration at the start of a movement and a smooth deceleration to a stop precisely on the target position, with no overshoot.
For the leg sway, we've set up a couple poses in Spine, one for "moving forward" and the other for "moving backward":
In code, we've set a max X velocity which maps to these poses. So when the soldier is moving forward at or above max, we position its legs at 100% of the "moving forward" pose, and when it's moving backward at or above max, we position its legs at 100% of the "moving backward" pose. Every speed in between gets interpolated between these poses. Combined with the smooth-damped movement, this creates a very organic inertia-type effect on the legs.
(If this sounds familiar, that's because it is: we used the same approach for the Cosmonaut's leg sway as well.)
Those leg poses only have keyframes for the leg bones, which means we can play them on top of other animations, like the soldier's idle loop, without anything breaking. We use the same animation layering technique to accomplish the gun aims. In this case, we've created poses for "aiming upward" and "aiming downward":
In code, we check the angle between the soldier and the player (soldiers only ever target the player), where an angle of zero is perfectly horizontal. We've set a max angle which maps to these poses: when the player is above the soldier by 45˚ or more, we position the arms and head at 100% of the "aiming upward" pose, and when the player is below the soldier by 45˚ or more, we position the arms and head at 100% of the "aiming downward" pose. Every angle in between gets interpolated between these poses.
It looks unnatural – you might even say robotic (har har) – for the soldier's aim to track the player perfectly, so we also smooth-damp the aim target position:
m_aimTarget = Vector3.SmoothDamp( m_aimTarget, playerPos, ref m_aimBlendVelocity, m_aimBlendTime );
This makes it look more like how a human would aim: accelerating toward the target, then decelerating as it arrives in order to fine-tune the final aim. We smooth-damp the target position rather than the aim angle to simulate a slightly laggy response to player movement, rather than simulating just "generic" inaccuracy (although in truth it probably doesn't matter much either way).
When the player moves from one side of a soldier to the other, the soldier executes a quick turnaround so it's still facing the player. This contributes even more to them feeling alive and aware. The turnarounds are very simple in theory, as they're basically just three frames of full-body animation:
In practice, these were actually pretty time-consuming to create and get right. With a typical spritesheet-style animation you'd just paint three frames, but with Spine animation you still have a skeletal structure to deal with. We actually ended up with multiple skeletons with multiple images painted for each bone in order to execute the turnarounds correctly. We also had to temporarily disable the leg sway and target tracking so they didn't interfere with the turnaround in nasty ways, but the turn is quick enough that the gameplay effect is negligible.
Finally, we upgraded the way soldiers die. Most of our regular drone-style enemies simply explode on the spot and are immediately removed from the scene (hidden by a shower of sparks and debris, of course). That's fine for non-human robot drones, but we're not really used to seeing humanoid characters (even if they are technically synthetic) straight-up explode. And in the cases where we do see this, it's in a shower of gore, which a) doesn't make sense for humanoid robots, and b) isn't in keeping with the colorful and adventurous tone of the game.
So instead of exploding them, we simply knock them out of the sky:
We are still playing a little explosive "pop" effect, which in the future will be refined to more clearly suggest the soldier's jetpack exploding, but getting an actual "corpse" falling out of the sky really makes these guys feel a lot more natural and helps to further differentiate them from their now-familiar drone buddies.
Soldiers have been our most complex enemy implementation to-date; their behavioral complexity (from a code perspective) is approaching that of a boss, and their Spine rig is easily twice as complicated as our most complex boss so far. But they're also a hugely-important addition to the game, and one we'll be able to get a lot of mileage out of.