Part 2 of 5 - Creating smart enemy characters in Unity 3D - Determine proximity to player
Enemy characters play a very important role in any game. As a developer it is your responsibility to make them as smart as you possibly can. There are numerous AI packages available at the Unity store that can make your characters super smart.
However, for the purpose of this article we will use simple code to make the enemy behave smartly.
There are five aspects of a smart enemy. They should:
1) Patrol their region and have some fun
2) Be self-aware and determine proximity to main player
3) Run to main player
4) Attack main player
5) Manage health & die beautifully
Let's start by exploring each of these topics and work towards an intelligent and smart enemy character. We will dedicate the rest of this blog post to determine proximity of the enemy to the main player. In future blog posts we will explore the remaining aspects of developing the smart enemy character.
Be self-aware and determine proximity to main player
In order for an enemy character to be self-aware it needs to simply keep tabs on the player's location with respect to it's own location.
Let's start by declaring the following variables in the enemy's script so that we can:
a) Track whether the player was viewed (using bool playerInSight),
b) Get the player's current position (using Vector3 personalLastSighting)
c) Also, the player itself would need to tracked (using GameObject player)
// Whether or not the player is currently sighted public var playerInSight : boolean = false; // Last place this enemy spotted the player public var personalLastSighting : Vector3; // Reference to the player private var player : GameObject; // You can then get access to the player // in the enemy's Awake( ) method like this: player = GameObject.FindGameObjectWithTag(Tags.player);
You might have played games in the past where an enemy somehow detects the player even through the wall and tries to walk into the wall. These types of bugs can be seen in many popular games as well.
Let's see if we can develop a clean logic to avoid all such issues.
The three conditions that have to be met before a player is truly visible are:
Condition 1) Player is within trigger zone of enemy (achieved by looking for collisions of a enemy's sphere collider)
Condition 2) Player is in enemy's field of view (achieved by calculating the angle view between the player and the enemy)
Condition 3) Enemy's view is not blocked (achieved by projecting a Raycast from the enemy and checking if it collides with the player)
Colliders and Raycasts are often used to determine proximity and visibility respectively.
> A collider will tell you with confidence whether a player is nearby.
> A Raycast will give you the confidence about whether the enemy's sight is not blocked.
We could write our code simply using Raycast but the enemy will be more engaging if it had the colliders as well so that it can react nicely.
Let's look at each of our three conditions:
Condition 1) Player is within trigger zone of enemy (achieved by looking for collisions of a enemy's sphere collider)
Go ahead and add a sphere collider component for your enemy. Make it large enough so that it collides with the player whenever the player is nearby. Consider a radius of about 10 times the enemy width.
The method OnColliderStay ( ) can help us detect if the enemy's spherical collider has in fact collided with the player.
Here's how we can use this method :
function OnTriggerStay (other : Collider) { // We check to see if it was in // fact the player object that // entered the sphere collider area if(other.gameObject == player) { ............... } }
This tells us that the sphere collider around the enemy has in fact collided with the player. Now we know that the player character is actually near the enemy.
You will need access the collider object later on. Here's how you can access it in your code:
// Reference to the sphere collider // trigger component on the enemy private var col : SphereCollider; // In Awake ( ) Awake ( ) { // Get collider around enemy col = GetComponent(SphereCollider); }
Condition 2) Player is within the enemy's field of view (achieved by calculating the angle view between the player and the enemy)
Next let's calculate the angle between the enemy and the player. This will tell us if the player is within the enemy's field of view.
Let's calculate two things:
1) A vector from enemy to player -- calculated as other.transform.position - transform.position
2) A forward vector projected from the enemy - calculated as transform.forward
Now if the difference between the vectors
// Create a vector from the enemy to the player // and store the angle between it and forward. var vectorFromEnemyToPlayer : Vector3 = other.transform.position - transform.position; var forwardVector = transform.forward; var angle : float = Vector3.Angle(vectorFromEnemyToPlayer, forwardVector);
// If the angle between forward and where // the player is less than half the angle of view… if(angle < fieldOfViewAngle * 0.5f) { /// NICE - NOW we know that player /// is in front of enemy and within it's view ....... }
The code above helps establish a view angle between the player and the enemy as the enemy is looking in the forward direction.
If the condition mentioned above is met, it means that the player is right in front of the enemy.
Condition 3) Enemy's view is not blocked (achieved by projecting the Raycast from the enemy and checking if it collides with the player)
At this time, our last and final test should be to establish a Raycast from the enemy in the forward direction, with the same radius as the collider.
If the Raycast collides with the player, then we know that the player is visible to the enemy, and all conditions have been met.
if(angle < fieldOfViewAngle * 0.5f) { var hit : RaycastHit; // … and if a raycast towards the player hits something… if(Physics.Raycast(transform.position + transform.up, direction.normalized, hit, col.radius)) { // … and if the raycast hits the player… if(hit.collider.gameObject == player) { //HOORAY !!!! The enemy has found the player // … the player is in sight. playerInSight = true; // save the player’s current position. personalLastSighting = player.transform.position; } } }
That covers all aspects of discovering a player.
Don't forget to write the following code, so that the enemy can stop tracking the player once the player is outside the spherical collider area. This can be achieved like this:
function OnTriggerExit (other : Collider) { // If the player leaves the trigger zone... if(other.gameObject == player) { // ... the player is not in sight. playerInSight = false; } }
Hope you enjoyed this tutorial.
We will pick up from this point in Part 3 of the series.









