08. AI Behavior Revamp and Procedural Animation

Β· 730 words Β· 4 minute read

AI Behavior Revamp πŸ”—

My old solution relies on Unity’s NavMesh and NavMeshAgent. This becomes a chore to me later on because:

  • As my levels becomes more dynamic, managing NavMesh becomes a tedious work NavMeshAgent can reliably move towards the destination, but also costs too much to request frequently
  • My gameplay is very fast-paced, so I’d like my enemies to react to player action as fast as possible

So I decided to move to a custom solution that is basically about picking a preferred direction and move forward.

Movement Determination πŸ”—

Generally a level may look something like this:

obstacles

For each calculation, the agent will check the following things:

  • Obstacle around the agent (e.g. other agents, traps, and decors)
  • Distance and direction towards a preferred location (e.g. movement destination and target agent)

So it’s all about avoid bumping into obstacles and wanting to have a certain distance between certain target. Which looked like this:

scoring

Then I added some physical constraints to make the agent move more naturally, such as:

  • Having acceleration/deceleration speed
  • Having max speed
  • Will decelerate if the preferred direction is too different from current direction
  • Have a max rotation speed

I could’ve gone further and have rotation acceleration/deceleration, but I find it somewhat hard to balance around the numbers, so I’ve decided to omit it for now.

So far so good:

movement

enemy roaming

This approach also helps me make some actions more dynamic, such as “Strategic Dash” and “Emergency Evade”, for these action can now be used with understandings of the environment instead of a fixed direction and stuffs.

I’ve then spend some time optimizing raycast with multithread but nothing fancy.

Attack Pattern πŸ”—

I decided to move towards the idea of an attack pattern. To ensure the combat won’t become raw skill-based chaos, but a more manageable situation to be learned and resolved by the players.

So I constructed a hefty list of actions available, just to name a few:

commands

enemy pattern

And now instead of making multitude of enemy prefabs, I now have one enemy prefab that will instantiate with a EnemySetting scriptable object, which makes modification a lot easier.

enemy setting

Now with enemy behavior revamped, time to add in procedural animation.

Procedural Animation πŸ”—

Procedural Animation in my game is a big topic, because basically every single changing element was procedural, i.e. code driven.

I know early on that I’m not going to be doing keyframes at all. Not only because I’m just but one guy, also that I’ve watched the famous talk from David Rosen about procedural animation in Overgrowth and was convinced that procedural animation can be used to achieve suitably high quality animation with way less effort.

UI Animation πŸ”—

Not much to talk about here due to my simple UI setup, but I do have several glitch effect setup for the windows, which is driven by something like this:

switch (CurrentTransition)
{
    case WindowTransition.Noise:
        maskIndex[i, j] = UnityEngine.Random.Range(0, TextUtility.FadeIn.Length - 1);
        break;
    case WindowTransition.Glitch:
        if (UnityEngine.Random.value > 0.5f)
            maskIndex[i, j] += Mathf.FloorToInt(Time.unscaledDeltaTime / maskAnimationStep);
        break;
    case WindowTransition.DamageGlitch:
        if (UnityEngine.Random.value > 0.5f)
            maskIndex[i, j] += Mathf.FloorToInt(Time.unscaledDeltaTime / maskAnimationStep);
        break;
    case WindowTransition.Random:
        if (UnityEngine.Random.value > 0.25f)
            maskIndex[i, j] += Mathf.FloorToInt(UnityEngine.Random.Range(1, TextUtility.FadeIn.Length - 1) * Time.unscaledDeltaTime / maskAnimationStep);
        break;
    default:
        maskIndex[i, j] += Mathf.FloorToInt(Time.unscaledDeltaTime / maskAnimationStep);
        break;
}

Some demonstration:

ui animation

Generic Procedural Animation πŸ”—

This part is a bit more fun. Mostly done with DOTween because DOTween is just great. Some examples:

Hit Reaction πŸ”—

icon.DOPunchRotation(damageForce / unitSize, 0.5f, 10, 0)

hit reaction

Weapon Recoil πŸ”—

recoilTarget.DOPunchPosition(recoilForce / unitSize, recoilDuration, 1)

rifle recoil

shotgun recoil

Simple, quick to make, and can include the consideration of physicality quite easily.

Procedural Movement Animation πŸ”—

Now comes the fun part, though maybe not much to be explained.

I find that making procedural movement animation is basically programming the process of how the real world counterpart would move:

  • If leg is too far away, move
  • If moving, lean
  • Move faster if displacement is large

Which is as rudimentary as it can be, but this also makes it hard to find resources to follow. I ended up learning more about procedural animation by looking at Jakob Wahlberg’s tweets.

Since I revamped enemy behavior, my AI now have some proper physicality in its movement. So as long as my procedural movement animation can automatically accommodate the displacement of the unit, the result will always be great:

All movements

And by extension, it also works on player controlled character:

procedural animation

So with procedural animation in place, my game finally looks a lot more complete.