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
!
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:
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.
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:
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
.
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:
- We create an Animation event on imported clips from the model
- 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.
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:
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.
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:
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:
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:
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:
- Added
OnCollisionExit()
and OnCollisionEnter()
to detect when our Mesh Collider
comes into contact with our player. - Once it does, we set a boolean to indicate that we’ve collided with the enemy.
- 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:
- Made it so that if we ever come in contact with the enemy, whether attacking or not, we would take damage.
- 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.
CodeProject