Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / game / Unity

Day 12: Creating AI Movements For Enemies In Unity

5.00/5 (3 votes)
10 Oct 2017CPOL9 min read 11.8K  
How to create AI movements for enemies in Unity

Here we are on Day 12 of the 100 days of VR. Yesterday, we looked at the power of rig models and Unity’s mecanim system (which I should have learned but ignored in the Survival Shooter tutorial…).

Today, we’re going to continue off after creating our animator controller.

We’re going to create the navigation component to our Knight Enemy to chase and attack the player. As you might recall, Unity provides us an AI pathfinder that allows our game objects to move towards a direction while avoiding obstacles.

Moving the Enemy toward the Player

Setting Up the Model

To be able to create an AI movement for our enemy, we need to add the Nav Mesh Agent component to our Knight game object. The only setting that I’m going to change is the Speed, which I set to 2.

At this point, we can delete our old enemy game object. We don’t need it anymore.

Next up, we need to create a NavMesh for our enemy to traverse.

Click on the Navigation panel next to the Inspector.

If it’s not there, then click on Window > Navigation to open up the pane.

Under the bake tab, just hit bake to create the NavMesh. I’m not looking to create anything special right now for our character.

Once we finish, we should have something like this if we show the nav that we created.

Make sure that the environment parent game object is set to static!

Image 1

Creating the Script

At this point, the next thing we need to do is create the script that allows the enemy to chase us.

To do that, I created the EnemyMovement script and attach it to our knight.

Here’s what it looks like right now:

C#
using UnityEngine;
using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
    private NavMeshAgent _nav;
    private Transform _player;

	void Start ()
	{
	    _nav = GetComponent<NavMeshAgent>();
	    _player = GameObject.FindGameObjectWithTag("Player").transform;
	}
	
	void Update ()
	{
	    _nav.SetDestination(_player.position);
	}
}

It’s pretty straightforward right now:

  • We get our player GameObject and the Nav Mesh Agent Component.
  • We set the Nav Agent to chase our player.

An important thing that we have to do to make sure that the code works is that we have to add the Player tag to our character to make sure that we grab the GameObject.

After that, we can play the game and we can see that our Knight enemy will chase us.

Image 2

Using the Attack Animation

Right now, the Knight would run in a circle around us. But how do we get it to do an attack animation?

The first thing we need to do is attach a capsule collider component onto our knight game object and make these settings:

  • Is Trigger is checked
  • Y Center is 1
  • Y Radius is 1.5
  • Y Height is 1

Similar to what we did in the Survival Shooter, when our Knight gets close to us, we’ll switch to an attack animation that will damage the player.

With our new Capsule Collider get into contact with the player, we’re going to add the logic to our animator to begin the attack animation.

First, we’re going to create a new script called EnemyAttack and attach it to our Knight.

Here’s what it looks like:

C#
using UnityEngine;
using System.Collections;


public class EnemyAttack : MonoBehaviour
{
    Animator _animator;
    GameObject _player;

    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
    }
}

The logic for this is similar to what we seen in the Survival Shooter. When our collider is triggered, we’ll set our “IsNearPlayer” to be true so that we’ll start the attacking animation and when our player leaves the trigger range, the Knight will stop attacking.

Note: If you’re having a problem where the Knight stops attacking the player after the first time, check the animation clip and make sure Loop Time is checked. I’m not sure how, but I disabled it.

Detecting Attack Animation

Adding a Mesh Collider

So now, the Knight will start the attack animation. You might notice that nothing happens to the player.

We’re not going to get to that today, but we’re going to write some of the starter code that will allow us to do damage later.

Currently, we have a Capsule Collider that will allow us to detect when the enemy is within striking range. The next thing we need to do is figure out if the enemy touches the player.

To do that, we’re going to attach a Mesh Collider on our enemy.

Unlike the previous collider which is a trigger, this one will actually be to detect when the enemy collides with the player.

Make sure that we attach the body mesh that our Knight uses to our Mesh Collider.

Image 3

I will take note that for some reason the Knight’s mesh is below the floor, however I’ve not encountered any specific problems with this so I decided to ignore this.

Adding an Event to our Attack Animation

Before we move on to writing the code for when the Knight attacks the player, we have to add an event in the player animation.

Specifically, I want to make it so that when the Knight attacks, if they collide with the player, we’ll take damage.

To do that, we’re going to do something similar to what the Survival Shooter tutorial did. We’re going to add an event inside our animation to call a function in our script.

We have 2 ways of doing this:

  1. We create an Animation event on imported clips from the model
  2. We add the Animation Event in the Animation tab from the animation clip

Since our knight model doesn’t have the animation we added in, we’re going to add our event the 2nd way.

We want to edit our Attack1 animation clip from the Brute Warrior Mecanim pack. inside the Animator tab.

While selecting our Knight Animator Controller, click on Attack1 in the Animator and then select the Animation tab to open it.

If either of these tabs aren’t already opened in your project, you can open them by going to Windows and select them to put them in your project.

Now at this point, we’ll encounter a problem. Our Attack1 animation is read only and we can’t edit it.

What do we do?

According to this helpful post, we should just duplicate the animation clip.

So that’s what we’re going to do. Find Attack1 and hit Ctrl + D to duplicate our clip. I’m going to rename this to Knight Attack and I’m going to move this into my animations folder that I created in the project root directory.

Back in our Animator tab for the Knight Animator Controller, I’m going to switch the Attack1 state to use the new Knight Attack animation clip instead of the previous one.

Image 4

Next, we’re going to have to figure out what’s a good point to set our trigger to call our code.

To do this, I dragged out the Animation tab and docked it pretty much anywhere else in the window, like so:

Image 5

Select our Knight object in the game hierarchy and then you can notice that back in the animation tab, the play button is clickable now.

If we click it, we’ll see that our knight will play the animation clip that we’re on.

Switch to Knight Attack and press play to see our attack animation.

From here, we need to figure out where would be a good point to run our script.

Playing the animation, I believe that triggering our event at frame 16 would be the best point to see if we should damage the player.

Image 6

Next we need to click the little + button right below 16 to create a new event. Drag that event to frame 16.

From under the Inspector, we can select a function from the scripts attached to play. Right now, we don’t have anything, except for OnTrigger().

For now, let’s create an empty function called Attack() in our EnemyAttack script so we can use:

C#
using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    Animator _animator;
    GameObject _player;

    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
    }

    void Attack()
    {
        
    }
}

All I did was that I added Attack() in.

Now that we have this code, we might have to re-select the animation for the new function to be shown, but when you’re done, you should be able to see Attack() and we should have something like this now:

Image 7

Updating our EnemyAttack Script

So now that we finally have everything in our character setup, it’s finally time to get started in writing code.

So back in our EnemyAttack script, here’s what we have:

C#
using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    private Animator _animator;
    private GameObject _player;
    private bool _collidedWithPlayer;

    void Awake()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", true);
        }
        print("enter trigger with _player");
    }

    void OnCollisionEnter(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = true;
        }
        print("enter collided with _player");
    }

    void OnCollisionExit(Collision other)
    {
        if (other.gameObject == _player)
        {
            _collidedWithPlayer = false;
        }
        print("exit collided with _player");
    }

    void OnTriggerExit(Collider other)
    {
        if (other.gameObject == _player)
        {
            _animator.SetBool("IsNearPlayer", false);
        }
        print("exit trigger with _player");
    }

    void Attack()
    {
        if (_collidedWithPlayer)
        {
            print("player has been hit");
        }
    }
}

Here’s what I did:

  1. Added OnCollisionExit() and OnCollisionEnter() to detect when our Mesh Collider comes into contact with our player.
  2. Once it does, we set a boolean to indicate that we’ve collided with the enemy.
  3. Then when the attack animation plays, at exactly frame 16, we’ll call Attack(). If we’re still in contact with the Mesh Collider, our player will be hit. Otherwise, we’ll successfully have dodged the enemy.

And that’s it!

Play the game and look at the console for the logs to see when the knight gets within attacking zone, when he bumps into the player, and when he successfully hits the player.

There’s actually quite a bit of ways we could have implemented this and I’m not sure which way is correct, but this is the thing I have come up with.

Other things that we could have done, but didn’t was:

  1. Made it so that if we ever come in contact with the enemy, whether attacking or not, we would take damage.
  2. Created an animation event at the beginning of Knight Attack and set some sort of _isAttacking boolean to be true and then in our Update(), if the enemy is attacking and we’re in contact with them, the player takes damage, then set _isAttacking to be false, so we don’t get hit again in the same animation loop.

Conclusion

And that’s that for day 11! That actually took a lot longer than I thought!

Initially, I thought it would be simply applying the Nav Mesh Agent like we did in the Survivor Shooter game, however, when I started thinking about attack animations, things became more complicated and I spent a lot of time trying to figure out how to damage the player ONLY during the attack animation.

Tomorrow, I’m going to update the PlayerShootingController to be able to shoot our Knight enemy.

There’s a problem in our script. Currently, whenever we run into an enemy, for some strange reason, we’ll start sliding in a direction forever. I don’t know what’s causing that, but we’ll fix that in another day!

Day 11 | 100 Days of VR | Day 13

The post Day 12: Creating AI Movements For Enemies In Unity appeared first on Coding Chronicles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)