Go to Top

Make smarter enemies! Get started with Non Player Character Artificial Intelligence by reading this introduction to the techniques and processes involved.

Starting with your character AI

Motivation:

I just updated a little the article (07th April 2013 ), at the moment the projects to download are not totally matching the article snippets.

Differences:

  • There is a new PathScript Component to get the paths for our NPCs
  • The waypoint script is now using gizmos and is modified in adequation with the component above
  • NPC script has lost some lines due to the new components above, the dictionary is replaced by a list
  • delegates replaced booleans in if statement in delegate paragraph

Due to the modifications, I may have left some mistakes, if so, please be kind enough to let me know.

Enjoy :)

You want to develop your first game, probably a zombie game or a Slender-like game (See below about this) and you are doing so. But hell on earth when you see that zombie getting through the trees and passing walls, they are zombies not ghosts. You go through your codes and the components of your zombie and everything seems fine, you even check the walls. Well, let me tell you something, everything is not fine or it would work  (Thank you Captain Obvious!!).

In this tutorial we will go through the basics of moving an NPC (Non-Player-Character) around, how to give him a little bit of reaction depending on its environment and how to implement a second FSM a little more developed than the one you may have seen in the Space Shooter tutorial but also more accessible than the one you will see in the FSM tutorials. Also you will have a little first peek at pathfinding algorithm.

Starting with our NPC

When dealing with a NPC character the best first move is to use the character controller (CC).  This will simply solve all of your problems of  NPC passing through walls.

You can learn the basics of CC with this video:

The CC is simply a complex component that helps in many ways to get collision detection without using a rigidbody. It is mainly composed of a capsule that wraps the player and detects and reports collision.

Using CC on your NPCs  and they will bump onto any colliders the world has to offer.There will be situations where CC could not be the best of solutions, for instance if you are trying to recreate a Mario Galaxy environment where gravity is constantly switched, that won't do. If you played a game like Prey (2006), the player was able to walk on the wall, CC won't do either. One major issue with CC is the fact that it does not react to physics, so you would have to do that by script. But in many cases, you are good to go with it.

You attach it to your NPC and first thing you do in your script is fetch the CC so that you can manipulate it. For  a tutorial on how and why fetching component, see here. We also make sure our object contains a CC using an attribute placed before the class.

Do use the RequireComponent attribute when you have two components interacting. If for instance, script A relies on the functions or variables of  script B, do add the attribute on  script A

[RequireComponent (typeof (ScriptB))]

Same if for instance, you have a script for Physics, you are likely to need the Rigidbody component.

Here is what you do:

using UnityEngine;
using System.Collections;

// This below is an attribute that ensures a CC is attached to the object and cannot be removed
// Try and remove the CC component from the object to see the result.
[RequireComponent (typeof (CharacterController))] 
public class AIScript : MonoBehaviour {
   CharacterController _controller;
   Transform _transform;

   void Start(){
      _controller = GetComponent<CharacterController>();
      _transform = GetComponent<Transform>();
   }
}

Using our _controller variable, we are in control of the Character Controller (what an awkward sentence that is...). The second line is just a cache of the Transform component for optimization.We shall get our NPC to move.

float speed = 5f;
float gravity = 20f;
Vector3 moveDirection;

void Update(){
    moveDirection = _transform.forward;
    moveDirection *= speed;
    moveDirection.y -= gravity;
    _controller.Move(moveDirection * Time.deltaTime);
}

Hold on a second before rushing into scripting as this is not about to do much good yet. The script above is pretty much taken from the Unity CC docs, we have a moveDirection vector3 that gets the forward of the object, each member is multiplied by the speed and the y member receives the gravity so that our NPC is constantly affected by "real " gravity as it is in the update. Finally, we call the Move method from the CC class to which we pass our moveDirection vector. Time.delatTime is just there to make our game frame independent.

For the time being our NPC only go straight forward, if you intend to develop a not-so-fun game that will be just fine. For any other purpose,  we need to get our NPC turning around. Here is the tutorial that will get you through this all so I will simply apply what is there in here:

Vector3 target;
float maxRotSpeed = 200.0f;
float minTime = 0.1f;
float velocity;

void Update () {    
//Previous moving codes 
    var newRotation = Quaternion.LookRotation(target - _transform.position).eulerAngles;
    var angles = _transform.rotation.eulerAngles;
    _transform.rotation = Quaternion.Euler(angles.x, 
        Mathf.SmoothDampAngle(angles.y, newRotation.y, ref velocity, minTime, maxRotSpeed),
            angles.z);
}

The idea is that we create a quaternion vector from the NPC to the target that we convert to euler angles, so from quaternion vector to Vector3 vector. Then we convert our current rotation to eulerAngles.  Finally, we smoothly move from our current rotation to the target rotation with SmoothDampAngle. Our x and z rotations are not changing.

You will still have to be patient, we are missing some details  to get our guy going. But what we have here is the basics of moving our CC. We are only missing a target. Keep in mind our guy is stupid. He does what he is told to and we are about to tell him to go there (and he will, trust me). Next part we assign a value to target so that our NPC will use it as....target.

To make the reading easier, I will turn the two parts (moving and rotating) we have into one Move() function.

Let's get our NPC to move around with a simple script. :

bool change; 
float range;
void Start () {
   range = 2f;
   target = GetTarget();
   InvokeRepeating ("NewTarget",0.01f,2.0f);
}
void Update () {        
   if(change)
       target = GetTarget ();

   if(Vector3.Distance(_transform.position,target)>range){
      Move();
      animation.CrossFade("walk");
   }else animation.CrossFade ("idle");
}
Vector3 GetTarget(){
   return new Vector3(Random.Range (0,300),0,Random.Range (0,300));
}
void NewTarget(){
   int choice = Random.Range (0,3);
   switch(choice){
      case 0: 
         change = true;
         break;
      case 1: 
         change = false;
         break;
      case 2:
         target = _transform.position;
         break;
   }    
}

Our AI does not think, so we need to tell it where to move. For this purpose, the function GetTarget() is used. It simply returns a Vector3 that we assign to the target variable.

Notice the NewTarget function called with InvokeRepeating (for explanation on Invoke functions see here) that will simply decide if we assign a new target or not. It uses a random number between 0 and 2 and uses that number in a switch to decide the behavior.

  • 0, we get a new target
  • 1, we continue with the previous target
  • 2, we stop and wait

The movement of the NPC is based on the distance to the target. If we are not close enough (>range), the NPC moves towards the target, if we are close enough, it waits. That is why on case 2, the transform.position is provided to the target, so that the NPC is close enough and wait. In the case of movement, the walking animation is played, in case of resting, the idle animation is played.

For this first AI our FSM looks like the picture next:

First FSM

The picture tells us that the decision center (NewTarget function) defines the next action. Notice that the decision does not choose to walk, it only decides to provide a target. The Update will create the displacement using the target.

This kind of AI is not likely to be good enough for a “Human” NPC but is fairly good enough for any kind of animals. For instance, you could use this kind of script for a boar wandering in the forest. There is still one issue, on our terrain there is no particular environment threats. But what if there were a cliff or a lake, you do not want the NPC to get there, it would not look natural. To prevent this kind of happenings, we simply provide the NPC with a set of predefined targets.

Also, another issue that will come up pretty quick is the relief of your terrain. As you are assigning random values and in our case we kept y at 0, it means this algorithm is fine for flat terrain only. Simply think your relief goes at y = 10, which is just a tiny little hill, still you are too far from the point at 0, your character would never get below the range to switch target. Otherwise, you would have to increase the range but then again, that is not really good.

Get the project from here.

Adding waypoints

So the idea here is simple, we won't let our NPC go around freely. We provide a set of point that will be where the NPC goes and nowhere else. Consider a warden that walks around a building and nothing else. You place four point around the building and you let him know that his only duty is to walk from point A to B, then B to C, C to D and finally back to A and there you go again, A to B and so on. Not the best AI but we do not want smart AI yet.

On top of that since you are manually placing the waypoints if the relief would go up and down, you would have to place it at the level of the ground. For  the rest let the CC do its job.

Path

On the picture above the waypoints A and B are at different y positions. The green line represent the actual path your CC will take. It just makes sense that since we are applying constant gravity , the CC just goes towards the target but follows the relief curve.

Let's first talk a little about the design. We could easily create an array of Transform to store our waypoints and manually assign waypoint A to 0, waypoint B to 1 and so on. Well, that doe snot sound really efficient to me. Particularly if we have hundreds of waypoints. We need to figure out a way that allows us to make our script universal, that is, for any amount of waypoint and also any waypoint grid.

First thing we do is define our waypoint. We need them to get an index in order to know which is first and second and so on. Well, let's add:

using UnityEngine;
using System.Collections;

public class WaypointScript : MonoBehaviour {

   bool used;
   public bool GetUsed(){
      return used;
   }
   public void SetUsed(bool b){
      used = b;
   }
   public float radius = 0.5f;
   void OnDrawGizmosSelected() {
        Gizmos.color = Color.red;
        Gizmos.DrawSphere(transform.position, radius);
    }
}

Attach that to the waypoint prefab.When placing a waypoint on the map, if the waypoint is selected in the hierarchy that will show a little red ball. It is now easier to see where is our waypoint. I suggest you add an empty game object and add the waypoint as children. It will keep your hierarchy clean and selecting the parent object will show the whole path. Now give a tag to the waypoint and start duplicating our waypoint to create a path.The tag is the name of our path. So you can create a second different path simply giving a different tag.

Only little special point, make sure the next node is always the closest one, let make clarify this (if possible), the distance between two consecutive nodes should always be smaller than the distance between the node and any other. That is if you have nodes A, B, C, D and E (in order), make sure B is the closest to A, then C is the closest to B, D the closest to C and finally E does not really matter since it is the last one remaining but as it will close the path do not make it closer to A than B would be (I probably lost half of you already...). Also, make sure the waypoints are placed above ground (not too high though).

Now we need a component that will create the different paths and will share them with the NPCs that need them. Create a new script called "PathScript" and attach it to an empty game object that we call "PathObject".

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PathScript : MonoBehaviour {

   public GameObject[]start;
   Dictionary<string,List<Transform>>path = new Dictionary<string, List<Transform>>();

   void Awake () {
      string str = null;
      for(int i = 0;i<start.Length;i++){
         int index=0;
         str = start[i].gameObject.tag;
         GameObject [] gos = GameObject.FindGameObjectsWithTag(str);

         List<Transform> waypoints = new List<Transform>();
         waypoints.Add (start[i].transform);
         start[i].gameObject.GetComponent<WaypointScript>().SetUsed(true);
         while(true){
            Transform closest = FindClosest(waypoints[index],gos);
            if(closest != null)
               waypoints.Add (closest);
            if(++index >= gos.Length)break;
         }
         path.Add (str,waypoints);
     }  
   }

   Transform FindClosest(Transform start, GameObject[] obj){
      int layerMask = 1 << 2;
      layerMask = ~layerMask;
      Transform closest = null;float distance = Mathf.Infinity;
      foreach(GameObject go in obj){
         WaypointScript sc = go.GetComponent<WaypointScript>();
         if(start!=go.transform && !sc.GetUsed()){
            if(!Physics.Linecast (start.position,go.transform.position,layerMask)){
               float dist = (start.position - go.transform.position).magnitude;
               if(dist < distance){
                  distance = dist;
                  closest = go.transform;
               }
            }
         }
      }
      if(closest != null){
         closest.gameObject.GetComponent<WaypointScript>().SetUsed (true);
      }
      return closest;
   }

   public List<Transform> GetPath(string str){
      return path[str];
   }
}

This is a little bit of a first step into pathfinding. We have the Awake function that creates a public GameObject array. What you need to do is simply drag and drop the starting waypoint of your path into the slot and that is it for now actually, the rest is done for you.

The script gets the tag of the waypoint you gave, then find all waypoints with similar tag and place them in an array.  A list of Transform is created and the starting waypoint is added and remember the used boolean in our waypoint script, we set it to true so that we won't use that waypoint again. Then starts a loop that will take the latest node placed in the list and compare it with all other waypoints. The closest is return and placed in the list, then we start again until we have no more waypoint. The resulting list is placed in a Dictionary of string and List of Transform so that we have a way to retrieve our list with a name.

Pffff, next the FindClosest method: well no biggie here, simply we get our latest node from the list and we iterate through the array checking if the waypoint is not used already, if it is not hidden from the latest node (Linecast) and finally if it is the closest. We then have the GetPath method that uses the string parameter to retrieve the path from the dictionary. That simple!!!

Note that the FindClosest method is a simplified version of pathfinding method , greatly simplified I agree, but the idea is still there, comparing a node with other nodes and returning the closest one.

Now how do we provide our path to our NPCs?

In the code snippet below, I added a public string strTag;, this variable is showing in the inspector of teh NPC and allows you to provide the name of the path you want the NPC to follow. The name is actually the tag given. Remember that our path is made of waypoints and those waypoints have a common tag representing the path name. This is what you need to provide to the strTag of the NPC for it to find the path. Hopefully I made myself clear enough...


Waypoint code snippet

using UnityEngine;
using System.Collections;
//Added namespace
using System.Collections.Generic;

[RequireComponent(typeof(CharacterController))]
public class AIScript : MonoBehaviour
{
    //Cached variables
    CharacterController _controller;
    Transform _transform;

    //Movement variables
    float speed = 5f;
    float gravity = 100f;
    Vector3 moveDirection;
    float maxRotSpeed = 200.0f;
    float minTime = 0.1f;
    float velocity;
    //Added variables
    float range;
    List<Transform> waypoint;
    int index;
    public string strTag;

    void Start()
    {      
        _controller = GetComponent<CharacterController>();
        _transform = GetComponent<Transform>();

        index = 0;

        //Added codes
        if (string.IsNullOrEmpty(strTag))
            Debug.LogError("No waypoint tag given");
        range = 1f;
        waypoint = GameObject.Find("PathObject").GetComponent<PathScript>().GetPath(strTag);
    }

    void Update()
    {
        if (Vector3.Distance(_transform.position, waypoint[index].position) > range)
        {
            Move(waypoint[index]);
            animation.CrossFade("walk");
        }
        else NextIndex();
    }

    void Move(Transform target)
    {
        // Movement
        moveDirection = _transform.forward;
        moveDirection *= speed;
        moveDirection.y -= gravity;
        _controller.Move(moveDirection * Time.deltaTime);
        //Rotation
        var newRotation = Quaternion.LookRotation(target.position - _transform.position).eulerAngles;
        var angles = _transform.rotation.eulerAngles;
        _transform.rotation = Quaternion.Euler(angles.x,
         Mathf.SmoothDampAngle(angles.y, newRotation.y, ref velocity, minTime, maxRotSpeed), angles.z);
    }

    void NextIndex()
    {
        if (++index == waypoint.Count) index = 0;
    }
}

So what is new? Notice the required namespace at the top for using our List. Then we have a public string that needs to be given a value. Do that in the inspector.

if (string.IsNullOrEmpty(strTag))Debug.LogError("No waypoint tag given"); ensures a string is given. If none the game won't start.

We declare our  List<Transform> waypoint ;  note that it is only a reference but no memory is allocated. This way each NPC has a reference but only one path exists in memory instead of having duplicated paths for each NPC.

The Update has changed a little and we have a new function:

void Update()
{
    if (Vector3.Distance(_transform.position, waypoint[index])> range)
    {
        Move(waypoint[index]);
        animation.CrossFade("walk");
    }
    else 
        NextIndex();
}
void NextIndex()
{
     if (++index == waypoint.Count) index = 0;
}

 

NextIndex(); simply increase the index until it is equal to the waypoint.Count. It is then set to 0 to avoid out of bound and close our path.

Get the project here.

Adding patterns

In the previous part we told our NPC to walk around and he now does. We could also tell him to do other actions in between. Instead of blindly go from A to B, then B to C (damn there he goes again...) and so on, we now tell him go from A to B but when you are there wait a while and do something (in our case he will do a victory animation), then continue. And maybe just wait a while when you reach another waypoint. Well, feel free to make him do whatever you want.

Actually, what the NPC will do will mainly depend on the animations you have under your sleeves. If you had a bench on the way, you could develop an animation with the NPC sitting down for a sec and continuing. You can even get him moonwalking if you fancy, again it has no limit but your imagination (and what you want your game to look like). Avoid the moonwalk tip if you plan on doing a zombie game or the next Call of Duty...

What we need to do is prepare some function we want to call in the appropriate moment. Let's see first our FSM.

FSMWalk

The FSM above is based on a previous tutorial. The Rotate and Wait state is now a Victory state

As stated previously, nothing fancy, we are progressing slow. Our guy walks, reaches the waypoint and then depending on which waypoint he is on, we launch an action. Note that waypoints 2 and 3 will not do anything.

The action are a simple victory animation, you can use any animation you want instead, you can also add other states. The Wait state is just an IEnumerator function that waits 2 sec on an idle animation.

Now the code:


Pattern code snippet

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(CharacterController))]
public class AIScript : MonoBehaviour
{
    // Cached variables
    CharacterController _controller;
    Transform _transform;

    //Movement variables
    float speed = 5f;
    float gravity = 100f;
    Vector3 moveDirection;
    float maxRotSpeed = 200.0f;
    float minTime = 0.1f;
    float velocity;
    float range;

    //Waypoint variables
    List<Transform> waypoint;
    int index;
    public string strTag;

    //Added variables
    bool walking, waiting, victoring;
    bool isWaiting, isVictoring;

    void Start()
    {
        _controller = GetComponent<CharacterController>();
        _transform = GetComponent<Transform>();
        if (string.IsNullOrEmpty(strTag)) 
            Debug.LogError("No waypoint tag given");
        index = 0; 
        range = 4;

        waypoint = GameObject.Find("PathObject").GetComponent<PathScript>().GetPath(strTag);
        //Added codes
        walking = true; waiting = false; victoring = false;
        isWaiting = false; isVictoring = false;
        animation["victory"].wrapMode = WrapMode.Once;
    }

    void Update()
    {
        if (walking)
        {
            if (!isWaiting && !isVictoring)
                Walk();
        }
        else if (victoring)
        {
            StartCoroutine(Victory());
            victoring = false;
        }
        else if (waiting)
        {
            StartCoroutine(Wait());
            waiting = false;
        }
    }

    void Walk()
    {
        if (Vector3.Distance(_transform.position, waypoint[index]) > range)
        {
            Move(waypoint[index]);
            animation.CrossFade("walk");
        }
        else
        {
            switch (index)
            {
                case 0:
                    walking = false; victoring = true;
                    isVictoring = true;
                    break;
                case 1:
                    walking = false; waiting = true;
                    isWaiting = true;
                    break;
                default:
                    NextIndex(); break;
            }
        }
    }
    void Move(Transform target)
    {
        //Movement
        moveDirection = _transform.forward;
        moveDirection *= speed;
        moveDirection.y -= gravity;
        _controller.Move(moveDirection * Time.deltaTime);
        // Rotation
        var newRotation = Quaternion.LookRotation(target.position - _transform.position).eulerAngles;
        var angles = _transform.rotation.eulerAngles;
        _transform.rotation = Quaternion.Euler(angles.x,
         Mathf.SmoothDampAngle(angles.y, newRotation.y, ref velocity, minTime, maxRotSpeed), angles.z);
    }

    void NextIndex()
    {
        if (++index == waypoint.Count) index = 0;
    }

    //Added functions
    IEnumerator Victory()
    {
        if (!animation.IsPlaying("victory")) animation.CrossFade("victory");
        yield return new WaitForSeconds(animation["victory"].length);
        walking = true; isVictoring = false;
        NextIndex();
    }

    IEnumerator Wait()
    {
        animation.CrossFade("idle");
        yield return new WaitForSeconds(2.0f);
        walking = true; isWaiting = false;
        NextIndex();
    }
}

So, we have some boolean variables to check what the status of our guy is. He can be walking, waiting or victoring. The update checks the booleans and call the appropriate function. The Walk function checks which waypoint is being reached, and defines the status of each boolean according to the waypoint. The rest is simple coding you should understand.

You often see in games this kind of pattern, for instance the guard at the entrance will walk back and forth in front of the door but after a while will stop and look around or might do a special action which often is the trigger condition for the player to perform a special action, like shooting right in the face.

Get the project here.

Introduction to delegate

Before we dive into our FSM tutorials and get scared by all those delegates and closures and decide to retire in a remote mountain lodge with no computers, I will introduce a simple example about delegates.

If you have a C or C++ background, you could compare them to the function pointer or the functor from C++. Remember the:

void Afunction(){
    //stuff here
}

void (*fct)(int);
fct = &Afunction;

delegates are somehow similar but easier and typesafe.

We are now about to modify our code so that we can use delegate. First off we need to declare the delegate, then create an instance of the delegate and finally assign a function to it. Then at last we can use our delegate.

delegate void DelFunc();
DelFunc delegate;
delegate = Afunction;

static void Afunction(){

}
void Update(){
   delegate();
}

This is the basic declaration and that is probably all you are about to see from most internet tutorials, few or none takes the risk to show more about it, we will. You know that a static function cannot access non-static members. So how do we use that on our NPC in a way that if we have many on scene, it still works.

float variable;
delegate void DelFunc();
DelFunc delegate;
delegate = this.Afunction; //Did you see the this pointer?

void Afunction(){
    variable = variable +10;
}

Now the whole point of a delegate is to be used but also to be flexible. Our goal is to have one function to call another one from a bag of functions. See below the example:


Delegate code snippet

using UnityEngine;
using System.Collections;
using System;

public class Delegate :MonoBehaviour {
	[SerializeField]
	float pos;
	float val = 10;
	//Delegate declaration
        public delegate void DelegateFunction();
	public delegate IEnumerator DelegateEnum();
	// Delegate object
        DelegateFunction _delegate;
	DelegateEnum _delegateEnum;
	bool del;
	void Start(){
		_delegate = this.Increase;   
		_delegateEnum = null;    
		pos = 0;
		del = true;
	}
	void Update () {
        // We press Space and _delegate is called since del is true 
        // (that is why we assign a function in Start to avoid null reference)
		if(Input.GetKeyDown(KeyCode.Space)){
			if(del)_delegate(); 
			// _delegateEnum is called in a StartCoroutine
                        else StartCoroutine(_delegateEnum());  
		}
		if(Input.GetKeyDown(KeyCode.I)){
			del =true;                // we ensure del is true
			_delegate =this.Increase; // we assign a function to _delegate
		}
		if(Input.GetKeyDown(KeyCode.D)){
			del =true;
			_delegate = this.Decrease;
		}
		if(Input.GetKeyDown(KeyCode.O)){
			del =true;
			_delegate = this.Zero;
		}
		if(Input.GetKeyDown(KeyCode.W)){
			del =false;        // we ensure del is false
			_delegateEnum = this.Wait; // we assign a function to _delegateEnum
		}
		if(Input.GetKeyDown(KeyCode.A)){
			del =false;
			_delegateEnum = this.WaitAndAdd;
		}

	}
	public void Increase(){
		pos += val*Time.deltaTime; 
		print (pos);
	}
	public void Decrease(){
		 pos -= val*Time.deltaTime;
		print (pos);
	}	
	public void Zero(){
		pos = 0;
		print (pos);
	}
	public IEnumerator Wait(){
		yield return new WaitForSeconds(1.0f);
		print("Enum"+pos);
	}
	public IEnumerator WaitAndAdd(){
		print("Adding soon"+pos);
		yield return new WaitForSeconds(1.0f);
		pos+=2;
		print("Added "+pos);
	}
}

Attach that script to any object of a scene and look at the console. You can also look at the Editor since pos has the [SerializeField] attribute, it makes a private variable show in the Inspector.

Depending on the button pressed, a function is assign to a delegate. Since the signature of a function needs to match the signature of a delegate (vice-versa), we need two delegates. We just use a boolean to decide which one is called in the update.

It is possible to use parameters as well, you would create a delegate with the appropriate signature for return value and parameter and pass the functions with the same signature but  I will not go into Action and Func, as it is well done in the FSM tutorials.

Now let's modify our AIScript with delegates. Minor modifications are needed:


AIScript Delegate code snippet

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(CharacterController))]
public class AIScript : MonoBehaviour
{
    // Cached variables
    CharacterController _controller;
    Transform _transform;

    //Movement variables
    float speed = 5f;
    float gravity = 100f;
    Vector3 moveDirection;
    float maxRotSpeed = 200.0f;
    float minTime = 0.1f;
    float velocity;
    float range;

    //Waypoint variables
    public string strTag;
    List<Transform> waypoint;
    int index;

    //Added variables
    delegate void DelFunc();
    delegate IEnumerator DelEnum();
    DelFunc delFunc;
    DelEnum delEnum;

    void Start()
    {
		if(string.IsNullOrEmpty(strTag))Debug.LogError("No waypoint tag given");
        index = 0;
        _controller = GetComponent<CharacterController>();
        _transform = GetComponent<Transform>();
        range = 2;
        waypoint = GameObject.Find("PathObject").GetComponent<PathScript>().GetPath(strTag);
        animation["victory"].wrapMode = WrapMode.Once;

        //Added codes
        delFunc = this.Walk;
        delEnum = null;
    }

    //Modified update
    void Update()
    {
        if (delFunc != null){
            delFunc();
        }else if (delEnum != null){
            StartCoroutine(delEnum());
            delEnum = null;
        }
    }

    //Modified Walk
    void Walk()
    {
        if (Vector3.Distance(_transform.position, waypoint[index]) > range)
        {
            Move(waypoint[index]);
            animation.CrossFade("walk");
        }
        else
        {
            switch (index)
            {
                case 0:
                    delFunc = null;
                    delEnum = this.Victory;
                    break;
                case 1:
                    delFunc = null;
                    delEnum = this.Wait;
                    break;
                default:
                    NextIndex(); break;
            }
        }
    }

    void Move(Transform target)
    {
        // Movement
        moveDirection = _transform.forward;
        moveDirection *= speed;
        moveDirection.y -= gravity;
        _controller.Move(moveDirection * Time.deltaTime);
        // Rotation
        var newRotation = Quaternion.LookRotation(target.position - _transform.position).eulerAngles;
        var angles = _transform.rotation.eulerAngles;
        _transform.rotation = Quaternion.Euler(angles.x,
         Mathf.SmoothDampAngle(angles.y, newRotation.y, ref velocity, minTime, maxRotSpeed), angles.z);
    }

    void NextIndex()
    {
        if (++index == waypoint.Count) index = 0;
    }

    //Modified animation functions
    IEnumerator Victory()
    {
        if (!animation.IsPlaying("victory")) animation.CrossFade("victory");
        yield return new WaitForSeconds(animation["victory"].length);
        NextIndex();
        delFunc= this.Walk;
    }
    IEnumerator Wait()
    {
        animation.CrossFade("idle");
        yield return new WaitForSeconds(2.0f);
        NextIndex();
        delFunc = this.Walk; 
    }
}

Our Update is now tinier, and we lost most of our boolean checks.  One good point of a delegate is that you can set it to null, meaning it simply points nowhere. What is so good about it is that we can check for nullity and use our delegate as boolean.

    if (delFunc != null){
        delFunc();
    }else if (delEnum != null){
       StartCoroutine(delEnum());
       delEnum = null;
    }

The line delEnum = null;  is there to avoid multiple calls of the coroutine. What you need to understand is the flow of our state-machine.

switch (index){
   case 0:
      delFunc = null;
      delEnum = this.Victory;
      break;
   case 1:
      delFunc = null;
      delEnum = this.Wait;
      break;
   default:
      NextIndex(); 
      break;
}

The switch simply sets the delFunc to null and delEnum to the appropriate action. Now, our coroutines are running, nothing is actually happening in the Update anymore. At the end of the coroutine delFunc is set back to this.Walk.

Our guy is fine walking around the place but he still does not do anything with us. Next, we see how to make him interact with the player.

Get the project here.

Notice that the project includes two NPCs that do independent thing. With some more code, we could also define a different order of actions but I leave this one to you. As a hint, you could create an array of string to store animation names. Each NPC could then have a different order of actions. You would then pass the content of the array as parameter of the animation calls. [/alert]

Come closer (checking range)

Good, our NPC has a pattern of movement we stored and he looks more realistic than a simple movement from A to B, then B to C (now come on!!!). Still, if you add a First-Person-Controller and move around, you may have noticed that you can get close to him, even real close, he simply ignores you. It makes sense since we have not told him to do any particular action in case we would get close.

If you add a FPC, remove the camera already in use.

We need to check the range or the distance between our player and the NPC. There are two approaches to do so:

  1. Using a trigger collider
  2. Using Vector3.Distance or similar approach

Trigger Collider:

Pros:

  • Easy to use
  • All done by the engine
  • We get a reference to the colliding object

Cons:

  • That is more the engine has to do

Using a trigger collider can be simple, we attach a sphere collider, set it to IsTrigger and then we can check collision. When something enters, we verify who it is using the reference the function created and apply the action. Good point it is done by the engine, every frame it takes all colliders and checks the distance between each triangle of the collider and see if any other collider intersects or is inside.

Distance checking:

Pros:

  • Easy to use and implement

Cons:

  • Depending on the function choice it can be expensive
  • We need a reference to the object

Well, let's see two different cases in which one is better that the other. First, we have an enemy and we want to check collision with everything around (enemies, player and random NPCs). We cannot keep a reference of each and every one of them, on top of that, we would have to regularly call for any new object or any destroyed object. This is a case where the collider comes handy:

void OnTriggerEnter(Collider other){}

I get a reference automatically when anyone collides. This way, no need for arrays or lists of references. other is my reference and after the collision, the reference is destroyed. On the other hand, a whole Collider object  is created and destroyed calling the constructor and then the garbage collector and I need to check who collides with the NPC.

Now if I want my enemy to interact with a single object in particular, the player for instance and it might happen for various reasons and regularly, I might get a reference to the player at start and use it all along.

Transform _player;

void Start(){
   _player = GameObject.Find("Player").GetComponent<Transform>();
}

Now let's check if my player is in range.

We use the squared value (we already use it before on this tutorial) so that no square root is involved. Keep in mind that my squareRange is 100 so the actual distance is 10.

Transform _player;
Transform _transform;
float attackRange = 10f;
float closeRange = 4.0f;

void Start(){
   _transform = GetComponent<Transform>();
   _player = GameObject.Find("Player").GetComponent<Transform>();
   if(player == null)
      Debug.LogError("No player on scene");
}

function Update(){
    if(AIFunction())//Player in
}

bool AIFunction(){
    if(Vector3.Distance(_transform.position - _player) < attackRange)return true;
    else return false;
}

Good, now we can just modify our function so that when in, the NPC follows us and when out, he gets back to his duty.

    bool AIFunction(){
        if(Vector3.Distance(player.position, _transform) < attackRange){
           delFunc = this.Attack;
          return true;
       }else{
            delFunc = this.Walk;
            return false;
        }
    }

We  need modify our Update as such:

if(AIFunction()&& delEnum != null){
    StopAllCoroutines();
}

We call AIFunction that returns true , note that at this point, if we are in range, we are the target already. delEnum is checked so that if our NPC is doing his little animation, it will stop the animation and will follow us.

Now we have our guy minding his own business but he does not get too kindly on folks that come around so he attacks when you get too close but if you agree to leave then he gets back to his duty. By the way, he does not attack for now, so let's do it.

We need an Attack function:

void Attack(){
   if(Vector3.Distance(player.position, _transform)> closeRange){
      Move(player);
      animation.CrossFade("walk");
  }else{
      print ("Slap slap!!");
   }
}

And finally, we modify our AI function:

bool AIFunction(){
   if(Vector3.Distance(player.position, _transform.position) < attackRange){
      delFunc = this.Attack;
      return true;
   }else{
      delFunc = this.Walk;
      return false;
   }
}

Depending on the distance we assign a different function.

This will do the trick but you probably noticed that whether you come from the front or behind the result is the same. Also, what if you are behind a tree or a house, checking the range, the NPC would know you are behind even though he cannot see you. Let's fix that in the next part.

I can see you (Line of sight)

In order to give some vision to our NPC we use a little vector trick. We use dot product and unit circle from trigonometry.

A·B = |A|·|B|cosΘ

A·B / |A|·|B| = cosΘ

arcos(A·B / |A|·|B| ) = Θ

angle is in radians.

That tells us dot product of A and B is equal to product of the magnitude of A and B multiplied by cosin of the angle. Aie. Well actually it is way more simple than it looks. But it also shows that with some manipulation we can find the angle between two vectors.Look at the picture below.

Présentation1

What this tells us is that the NPC is at the center of the circle looking in the green vector direction. The player is the blue dot at the end of the black vector. We want to know the red angle and we can do that with the dot product.

Our dot product becomes green·black / |green|·|black| = cosΘ

It also tells us, that if the dot product returns a positive value, my player is in front of the NPC. We are getting closer.

Now, green is the transform.forward of our NPC and black is (player.position - transform.position). Mmm but hold on green is a unit vector so magnitude is 1 and I can make my black vector a unit vector as well without modify its direction property. Hooray!!!

Why hooray, because is I use the normalized version of my black vector the |green|·|black| part is equal 1 and dividing by 1 is like no division so I am left with transform.forward · (player.position - transform.position).normalized = cosΘ.

And that is it. I can use Vector3.Dot(transform.forward ,(player.position - transform.position).normalized)>0 and if this is true my guy is in front, the NPC sees him and attack.

if((player.position - _transform.position).sqrMagnitude < squareRange && 
      (Vector3.Dot((player.position - _transform.position).normalized, _transform.forward) >0)){}

We still have a problem, if we managed to get quickly behind the guy, he loses our sight.  So we need to modify our script so that when the NPC knows we are close, it will not lose sight until we get out of range. For this, I simply use a boolean.

bool chasing;
bool AIFunction()
{
     if (Vector3.Distance(player.position, _transform.position) < attackRange)
     {
         if (chasing)
         {
             delFunc = this.Attack;
             return true;
         }
         else
         {
             if (Vector3.Dot(direction.normalized, _transform.forward) > 0 )
             {
                 delFunc = this.Attack;
                 dhasing = true;
                 return true;
             }
             return false;
         }
     }
     else
     {
         if(chasing)delFunc = this.Walk;
         chasing = false;
         return false;
     }
 }

So we check if the player is in range AND if he is in sight. Then when he is seen, we set the boolean chasing to true. Next frame, it does not matter if we are behind him since he is aware of us. Only when we get far enough, then he gives up.

if(chasing){
   delFunc = this.Walk;
   chasing = false;
}
return false;

You might wonder about this bit above , well, we only want the NPC to get back to Walk if he has been chasing us and we got away from him. The rest of teh time we just return false.

Now how can we check if the NPC actually sees us. You do not want the NPC to see you if you are behind a wall. So we use Physics.Linecast. It will cast a line (as the name says) from one position to another and report if something is in between. But first, one side note about Linecast. If we use like this:

    if (!Physics.Linecast (_transform.position, player.position)) {
        //I see you
    }

You will notice that the NPC never sees you. The function returns true if something is in-between and skips our action if so. But the problem comes from our NPC itself. _transform.position is really likely to be positioned in the middle of the models, the Linecast will then automatically considered a collision with the collider of the NPC, two solutions to that. The simple one, add an empty game object right in front of the eye of the NPC with forward matching the view and casts the line from there. Good point about it, the NPC now sees from the eye level and I like this approach more actually as it matches realism.  Or we need to tell the compiler we want to ignore collision with the NPC using Layers.

Click Layers in the inspector, and select Add Layer. Select the User Layer 8 and add Vision, give that layer  to our player. We need to check if the line goes from the NPC eyes to our player position but our player also has a collider.

So we will modify a little our linecast function:

Transform _eyes;
int layerMask = 1 << 8;

void Start(){
    _eyes = transform.Find("Eyes");
    layerMask = ~layerMask;
}

void Update(){  
    Debug.DrawLine (_transform.position, _player.position, Color.yellow);
    if (!Physics.Linecast (_eyes.position, _player.position, layerMask)) {
           ("Did Hit "+hit.collider.gameObject.name);
    }
}

The layer variable uses some bitwise manipulation, instead of giving a particular value to the variable, we modify it by bit so we say to move to the 8th bit and turn it to 1-> 10000000. This kind of manipulation is widely used in communication and embedded-system, in game a little less...

The Start does another of those bitwise thingy, what we want is actually a bit like 01111111, so instead of 7 operations like the first one for each bit we can do it in two. ~ is a NOT operator just like ! but for bit, so it turns 0 into 1 and 1 into 0 and returns the invert byte (8bit). It is also called the complement. Let's not dig too much into bitwise manipulation shall we?

The Debug.DrawLine is just there for you to see the Linecast in the scene view. The linecast function checks every collision between our NPC and the player.If none then it means the NPC can see us.

So all we need here is this:

    bool AIFunction(){
        if (Vector3.Distance(player.position, _transform.position) < attackRange){
            if (chasing){
                delFunc = this.Attack;
                return true;
            } else{
                if (Vector3.Dot(direction.normalized, _transform.forward) > 0 &&
                    !Physics.Linecast(_eyes.position, player.position, layerMask)){
                        delFunc = this.Attack;
                        chasing = true;
                        return true;
                }
                return false;
            }
        }else{
            if(chasing)delFunc = this.Walk;
            chasing = false;
            return false;
        }
    }

Finally, our NPC starts to act reasonably. Last part we will look at is if our NPC is closer to a safe point, he will move there instead of attacking directly.

You may have notice I have added a script for the sword collision which is not a real collision. The issue being that we used an animation so collision won't do properly. A simple function will fix that problem.

using UnityEngine;
using System.Collections;

public class SwordScript : MonoBehaviour {

   Transform player;
   Transform _transform;
   float sqrRange = 3;

   void Start(){
      player = GameObject.Find ("Player").GetComponent<Transform>();
      if (player == null)
          Debug.LogError("No player on scene");
      _transform = GetComponent<Transform>();
   }

    void CollisionSword(){
    if (Vector3.Distance(player.position, _transform.position)< sqrRange){
        if(Vector3.Dot (direction.normalized,_transform.forward) > 0){
            //Add here your logic for decreasing player health
            print ("Hit");
        }
    }
    }
}

We are using a similar approach as for the line of sight, if we are close enough and facing the player at the moment of call then we get hit. The special thing here is that I used animation event. Select the NPC, then go to window in the menu bar and open up a animation window.
In the animation selection drop-down, select attackOwn (I had to create a new attack animation as imported animation are read-only). Around frame 13, you will notice a little white bar. This is an animation event. It just means at frame 13, I am calling the function that is given. By the way, you will probably see a (function not supported) message. Ignore it, it works. from what I have found, it seems only the functions attached to the first script on the object should be working. Nonetheless, it does work but give a warning.

Untitled1Untitled

When our guy gets close, he starts swinging and we get a message telling us we are getting hit.

We also add an attack function that is called when we are close enough to the player that simply launch the animation that will call the SwordCollision function.


Final code snippet

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(CharacterController))]
public class AIScript : MonoBehaviour{
    #region variables
    #region cached variables
    CharacterController _controller;
    Transform _transform;
    Transform player;
    Transform _eyes;
    #endregion
    #region movement variables
    float speed = 5;
    float gravity = 100;
    Vector3 moveDirection;
    float maxRotSpeed = 200.0f;
    float minTime = 0.1f;
    float velocity;
    float range;
    float attackRange;
    #endregion
    #region waypoint variables
    int index;
    public string strTag;
    List<Transform> waypoint;
    #endregion
    #region delegate variable
    delegate void DelFunc();
    delegate IEnumerator DelEnum();
    DelFunc delFunc;
    DelEnum delEnum;
    #endregion
    #endregion
    bool chasing;
    bool coroutineOn;
    int layerMask = 1 << 8;

    void Start(){
        _controller = GetComponent<CharacterController>();
        _transform = GetComponent<Transform>();
        _eyes = transform.Find ("Eyes");
        player = GameObject.Find("Player").GetComponent<Transform>();
        if (player == null)
            Debug.LogError("No player on scene");
        if (string.IsNullOrEmpty(strTag)) 
            Debug.LogError("No waypoint tag given");

        index = 0;
        range = 1f; attackRange = 15f;

        waypoint = GameObject.Find("PathObject").GetComponent<PathScript>().GetPath(strTag);
        animation["victory"].wrapMode = WrapMode.Once;

        delFunc = this.Walk;
        delEnum = null;

        chasing = false;
        layerMask = ~layerMask;
    }

    void Update()
    {
        if (AIFunction()&&coroutineOn) {
            StopAllCoroutines();
            coroutineOn = false;
        }

        if (delFunc != null){
            delFunc();
        }else if (delEnum != null){
               StartCoroutine(delEnum());
               delEnum = null;
        }
    }

    #region movement functions
    void Move(Transform target){
        //Movements
        moveDirection = _transform.forward;
        moveDirection *= speed;
        moveDirection.y -= gravity;
        _controller.Move(moveDirection * Time.deltaTime);
        //Rotation
        var newRotation = Quaternion.LookRotation(target.position - _transform.position).eulerAngles;
        var angles = _transform.rotation.eulerAngles;
        _transform.rotation = Quaternion.Euler(angles.x,
            Mathf.SmoothDampAngle(angles.y, newRotation.y, ref velocity, minTime, maxRotSpeed), angles.z);
    }

    void NextIndex(){
        if (++index == waypoint.Count) index = 0;
    }

    void Walk(){
        if (Vector3.Distance(_transform.position, waypoint[index])> range) {
            Move(waypoint[index]);
            animation.CrossFade("walk");
        }
        else{
            switch (index) {
                case 0:
                    delFunc = null;
                    delEnum = this.Victory;
                    break;
                case 1:
                    delFunc = null;
                    delEnum = this.Wait;
                    break;
                default:
                    NextIndex(); break;
            }
        }
    }

    void Attack(){
        if (Vector3.Distance(_transform.position , player.position) > range){
            Move(player);
            animation.CrossFade("charge");
        }else{
            animation.CrossFade("attackOwn");
        }
    }

    #endregion

    #region animation functions
    IEnumerator Victory() {
        couroutineOn = true;
        if (!animation.IsPlaying("victory")) animation.CrossFade("victory");
        yield return new WaitForSeconds(animation["victory"].length);
        NextIndex();
        delFunc = this.Walk;
        couroutineOn = false;
    }

    IEnumerator Wait(){
        couroutineOn = true;
        animation.CrossFade("idle");
        yield return new WaitForSeconds(2.0f);
        NextIndex();
        delFunc = this.Walk;
        couroutineOn = false;
    }
    #endregion

    #region AI function
    bool AIFunction(){
        if (Vector3.Distance(player.position, _transform.position) < attackRange){
            if (chasing){
                delFunc = this.Attack;
                return true;
            } else{
                if (Vector3.Dot(direction.normalized, _transform.forward) > 0 &&
                    !Physics.Linecast(_eyes.position, player.position, layerMask)){
                        delFunc = this.Attack;
                        chasing = true;
                        return true;
                }
                return false;
            }
        }else{
            if(chasing){
                delFunc = this.Walk;
                chasing = false;
            }
            return false;
        }
    }
    #endregion
}

Get the project here.

Conclusion

You now have the basic to develop your NPC further. Hopefully that was useful and I hope it will ease a little the approach to the more advanced delegate tutorial.

So you learn how to get our little guy around without passing through walls and trees. He also sees you and run at you. You learn about simple delegates. Now it is your turn to get this to a higher level.

Hope you liked it.

Also, it took a little while and a lot of coffee to review all that and make it better but still, it could be that you see something wrong. Please report it so that we can make it even better. Also, if you feel some techniques should be used over the one provided, well then again, drop a comment.

, , , , , , , , , ,

49 Responses to "Basics of AI-Character v2.0"

  • Joseph
    November 28, 2012 - 1:37 pm Reply

    Is there a project file for this tutorial?

    • fafase
      November 28, 2012 - 2:05 pm Reply

      Well, not really… I guess I will soon add the one I was using for developing the tutorial.
      Still everything in the project is also in the tutorial.

      But ok I will add it soon.

      Fafase

      • Bo
        January 7, 2013 - 7:38 pm Reply

        Can you please upload that project? Its not that I don’t want to type all that code, but im just allergic to errors (which, given my level of coding talent, are certain to occur…) :P

        • fafase
          January 7, 2013 - 8:52 pm Reply

          Hei Bo,

          Ok, I will upload it but I can’t right now as the file is about 4000km from me.

          You say you are allergic to errors, did you find any? Because if you are allergic, I am not immuned to do some either.

          The project I will upload will simply contain exactly the same as the last snippet. What is on the article is a copy/paste of the project as I always try to make sure I do not provide errors.

          Still, I promissed once and now comes a second request so I will definitely clean up the project and upload it.

          Fafase

  • Terry Morgan
    January 12, 2013 - 6:09 pm Reply

    Thanks, this site is one of the few I can understand, sort of.

    quote
    now comes a second request
    unquote

    Make that 3, you could just put cubes in for the enemies, as long
    as they behave right.

  • B;C Dev Note 3 « Nizlumina Anvils
    January 19, 2013 - 4:51 pm Reply

    [...] Here’s a great AI tutorial by UnityGems. Definitely going to implement it in someway. Current AIType in second draft: [...]

  • gamesonytablet
    January 28, 2013 - 3:18 pm Reply

    Hi, great tutorial!

    BTW, I seem to be the third request for the project file upload. Pls?

    No doubt, you’ll surely save the world!

  • fafase
    January 29, 2013 - 7:32 pm Reply

    Sorry for the delay on uploading the file, it happened that the original file went all pinky when I upgraded to Unity4. I had a nice medieval environment with Terrain and stuff but it is all transparent and pink now….

    So I will do a new project within this week hopefully.

    Sorry again about all this.

    Fafase

  • runawayIntelligenceExcursion
    March 4, 2013 - 2:57 pm Reply

    Excellent tutorial.
    Possible correction:
    In NewTarget() function, under case 2:
    ” target = transform.position; ”
    should be
    ” target = _transform.position; ”

    This is not really an error , since both will run, but in line with the caching/optimization thing (?)

    • fafase
      March 4, 2013 - 3:28 pm Reply

      Indeed, thanks for that.

  • intelligent idiot
    March 5, 2013 - 11:00 pm Reply

    I am making a zombie game. wherever I put my zombie prefab, they chase me, even they are located in the other side of the world. So I would like to change that, so the zombies chase you only when they ‘see’ me. until then, they roam around. what could be a possible script for that? It iz very hard to merge my enemy ai and your script. so… help?

  • fafase
    March 6, 2013 - 6:24 am Reply

    Your script probably looks something like:

    void Update(){
    Move(player);
    }

    So the zombie moves with the player as target. A quick way:

    void Update(){
    if(distanceWithPlayer< attackRange){
    Move(player);
    }else{
    Move(otherTarget);
    }
    }

    You can use the very part of the article to fill up the else statement.

    Fafase

  • runawayIntelligenceExcursion
    March 8, 2013 - 3:33 pm Reply

    I have been adapting the Quaternion related C# code on this site , for my game A.I.

    I have also started using such C# code for humanoid motions ( that are typically done with animation clips). i.e. for walking, head tracking-motion.
    I directly apply Quaternion.Slerp() , Quaternion.Euler() on the rigged bone transforms.
    The walking motion doesn’t look bad at all, and will look better after I tweak it.

    Is there any performance hit in using C# code for walking, etc. instead of animation clips?
    ( I prefer not to use the Unity animation tools , whether Legacy or Mecanim.
    I much prefer to use C# since it gives me more procedural flexibility;
    and I learn more C# that way).

  • runawayIntelligenceExcursion
    March 20, 2013 - 3:03 am Reply

    In the “Come closer (checking range)” section,
    the variable name “player” should be corrected to “_player” in the following lines:
    the updated AIFunction() method: Line 2
    the Attack() method : Lines 2 and 3

    Reason: “player” is not defined (typo).

    By the way, I am using a slightly simplified version of this code , and it works great !

    • fafase
      March 20, 2013 - 6:28 am Reply

      Indeed yes, thanks for that.

  • runawayIntelligenceExcursion
    March 20, 2013 - 3:11 am Reply

    In the “Waypoint code snippet” in the “adding Waypoints” section, is there any reason why variables “range” and “index” are initialized in the Start() method , instead of in the variable declaration section just above ?

    I moved it to the variable declaration section, and it works fine. The Start() code is less cluttered that way.

    • fafase
      March 20, 2013 - 6:30 am Reply

      They both do pretty much the same thing,I would have to check the IL to see if there are any differences at all. It is just about how you like to do it.

  • Lee
    April 19, 2013 - 2:17 am Reply

    Thank you! I’ve spent hours on Google looking at example waypoint snippets that don’t work on Terrain with hills, valleys, etc. . With your guidance, I at last have a (mostly) working AI and the bugs I’m seeing like my enemy falling through the ground @ WP1 for a second or two are probably of my own making.

    I was a little unsure of the “Eyes” thing – is that stuff with layers needed if a GameObject that sits outside the CC collider is the origin of the linecast?

    PS: In the last code snippet I think there are 2 typos on coroutineOn v couroutineOn

    • fafase
      April 19, 2013 - 5:34 am Reply

      Hey Lee,

      Good Point actually but I got to tell you not…

      See, Linecast goes from a point to another point and if something (a collider) prevents the line from reaching the target point then you get a collision. As opposed to Raycast that goes from one point and in a direction.

      In our case we go from eyes which indeed sits outside the FPS to the position of our NPC model which sits probably in the middle of our NPC (if the model was professionally made). That means we need to ignore the collider of our NPC or the line will never reach its position point.

      We could have gone without layerMask and use an extra check inside with a RaycastHit reference but I wanted to demonstrate the use of layer.

      Got it?

      By the way, you are probably right on the typos though.

      Fafase

  • MLynge
    April 20, 2013 - 2:09 pm Reply

    Hi, great tut. Just a minor error or correction of a coding convention thing.
    In section: Starting with our NPC, “Vector3 target” is later referred to as _target in line 12: if(Vector3.Distance(_transform.position,_target)>range)

  • MLynge
    May 3, 2013 - 9:40 pm Reply

    I section: I can see you (Line of sight), it is a bit unclear, which of the two sugested solutions is followed to coupe with the problem of the linecast colliding with the collider of the NPC, and that you have to (1) create an empty GameObject (2) rename it “Eyes” (3) add as a child to the NPC.

    • fafase
      May 4, 2013 - 9:41 am Reply

      Actually, you are right I should review that part a little. As for answering, happens to be that all of your three suggestions are used. You create an empty game object that you name Eyes and attach to the player.

      I will modify that to make it only one suggestion based on what is said there.

      I think the other suggestion was simply to use the position of the NPC but as it is said, position is in the middle of the model meaning he would see from his belly. As a result you could have the NPC and the player looking at each other but if there is a collider as the level of the belly, the NPC would not see the player.

      You raised a point, when I have time I will clarify this a little more with a drawing.

      Fafase

  • Somebody
    May 17, 2013 - 12:37 am Reply

    Does anyone have a C# version of this script? Leave a link?

    • fafase
      May 17, 2013 - 4:29 am Reply

      Euh… it is C#.

  • Sidus
    May 29, 2013 - 5:25 am Reply

    I keep getting this error when using the AIScript

    NullReferenceException
    UnityEngine.GameObject.GetComponent[Transform] () (at C:/BuildAgent/work/812c4f5049264fad/Runtime/ExportGenerated/Editor/UnityEngineGameObject.cs:18)
    AIScript.Start () (at Assets/Scripts/Ai/AIScript.cs:47)

    and

    NullReferenceException
    UnityEngine.Transform.get_position () (at C:/BuildAgent/work/812c4f5049264fad/Runtime/ExportGenerated/Editor/UnityEngineTransform.cs:19)
    AIScript.AIFunction () (at Assets/Scripts/Ai/AIScript.cs:171)
    AIScript.Update () (at Assets/Scripts/Ai/AIScript.cs:75)

  • fafase
    May 29, 2013 - 6:43 am Reply

    Like this it is difficult to say since your lining is different than the one in the tutorial.

    You may have misspelled one word, make sure you have GameObject and not gameObject when appropriate and vice-versa.

    You can also send me your code at [email protected] and I will have a look.

    Fafase

  • JKMurray
    June 23, 2013 - 9:26 pm Reply

    Thanks a lot for this excellent explanation! It’s one of the first times I actually see a wel-written and clear tutorial on this subject, which just isn’t easy.

  • Kite
    July 12, 2013 - 8:50 am Reply

    I followed the codes til the 1st portion where you add the rest then move script before the waypoints. but my AI staggers when walking… it stops in place while playing the walk animation then stops to idle then continues to walk for a bit then stop in place over and over again. is there anyway to fix this?

    I moved the CC lower for adjust for the animations but now the AI flies from the ground, but it indeed fixed the stop motions.

  • GO
    August 10, 2013 - 5:05 pm Reply

    Hi, thanks for this great tutorial, but there is just one thing, the gravity is not applied on the NPC, When he falls of the plateform, he is floting even if I increase the gravity.
    Can you tell from where does the problem comes. thanks.

    • fafase
      August 13, 2013 - 4:42 am Reply

      At first sight I would say that your fake gravity that you applied is within the
      if(controller.isGrounded){
      // do plenty of things
      // including pulling the guy down
      }

      • GO
        August 13, 2013 - 8:16 am Reply

        Thank you, I wasn’t using isGrounded. Thanks, it’s working now.

  • Qiri
    October 9, 2013 - 7:40 am Reply

    Hey, don’t know if you still reply to comments.. but i figured i’d give it a shot.
    I got stuck at the part “Adding patterns”. Everything worked without any animations.
    The NPC walks a path and stops to chant a victory shout and can go into idle state. But the moment i add animations it stops working.

    void Walk()
    {
    if ((trans.position – waypoint[index].position).sqrMagnitude > range)
    {
    Move(waypoint[index]);
    animation.CrossFade(“Walk”);
    }

    This is what i’m talking about ^^. He now just walks straight ahead.
    I think i know why this is. The rotation gets changed in the animation and it overrides the rotation towards the next waypoint?
    I just have no clue how to solve it. Do you have any idea?

    The code is literally what you’ve got. Just when i comment the animation part it works. But when i uncomment the animations, he just walks straight ahead.

    Help please? ^^

    • Qiri
      October 9, 2013 - 8:27 am Reply

      Ok,

      I fixed it… I put the animation literally on the controller itself. Instead of the graphics part that was a child of the controller.

      Had to change the !!!animation.CrossFade(“walk”);!!!
      to !!!Body.animation.CrossFade(“walk”);!!!

      Now it works fine :)

      Thanks for this tutorial by the way :)

  • fafase
    October 10, 2013 - 2:42 pm Reply

    @Qiri, the script considers you have the animation on the top object of the model, which is how it is done most of the time. Sorry I got a little late on the answer but you fixed on your own from which I guess you learnt more and is more rewarding :).

    Concerning if we are still around, well I wish I could be more around but my job is quite taking some time. Hopefully, the world economic crisis will do its job and I will lose mine so I can get back to making tutorials.

    Fafase

  • Kevin S.
    October 30, 2013 - 12:37 pm Reply

    Hey Fafase.

    Thank you for sharing this! Exactly what I was looking for! I’m new to Game Developing, but slowly getting there. This will really help me out!

    My main problem right now is with modelling and animations =/

  • Cynthia
    January 24, 2014 - 9:47 pm Reply

    Hi, Im trying to follow your tutorial and I am getting errors in the PathScript & AIScript. I am unsure if its the code your just me how I set the project up.

    The game will run the the NPC will not move:

    Error #1
    ArgumentException: An element with the same key already exists in the dictionary.
    System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.List`1[UnityEngine.Transform]].Add (System.String key, System.Collections.Generic.List`1 value)
    PathScript.Awake () (at Assets/myScripts/PathScript.cs:29)

    Its screaming at this line of code–> “path.Add (str,waypoints);”

    Error #2
    KeyNotFoundException: The given key was not present in the dictionary.
    System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.List`1[UnityEngine.Transform]].get_Item (System.String key)
    PathScript.GetPath (System.String str) (at Assets/myScripts/PathScript.cs:58)
    AIScript.Start () (at Assets/myScripts/AIScript.cs:42)

    Is screaming at this line of code –> “return path[str];”

    I have Unity 3.5 so I can’t view your project setups since its Unity 4. help is greatly appreciated

  • fafase
    January 28, 2014 - 7:45 pm Reply

    Hi Cynthia,

    Not so easy to debug without looking at your code. For the first error, my guess is that you have two paths but the starting waypoints have the same tag. The dictionary then complains that you have twice the same tag being used as key.

    As a result, your second error says that you are trying to fetch a value from a key that does not exist due to the fact that your second path starting nodes has the same tag as the first path starting node.

    Fafase

    • Cynthia
      January 28, 2014 - 7:52 pm Reply

      Hi fafase,
      I figured out what I did wrong. It was a basic mistake. When trying to create the tag “waypoint”. I made a layer called “waypoint” instead. I figured to create a correct tag in Unity. Thank you for responding!!!

  • Kayleigh Yu
    February 28, 2014 - 9:19 pm Reply

    There are inconsistent line endings in the ‘Assets/SwordScript.cs’ script. Some are Mac OS X (UNIX) and some are Windows.
    This might lead to incorrect line numbers in stacktraces and compiler errors. Many text editors can fix this using Convert Line Endings menu commands.

    I get that error in Unity 4.

  • PJ
    April 13, 2014 - 8:41 am Reply

    bool change;
    float range;
    void Start () {
    range = 2f;
    target = GetTarget();
    InvokeRepeating (“NewTarget”,0.01f,2.0f);
    }
    void Update () {
    if(change)
    target = GetTarget ();

    if(Vector3.Distance(_transform.position,target)>range){
    Move();
    animation.CrossFade(“walk”);
    }else animation.CrossFade (“idle”);
    }
    Vector3 GetTarget(){
    return new Vector3(Random.Range (0,300),0,Random.Range (0,300));
    }
    void NewTarget(){
    int choice = Random.Range (0,3);
    switch(choice){
    case 0:
    change = true;
    break;
    case 1:
    change = false;
    break;
    case 2:
    target = _transform.position;
    break;
    }
    }

    Where’s the “Move();” at?

    I’ve looked everywhere here, but do not see it? Is this an embedded function within Unity? If not, could you please update it so that I could actually see where this function is going to and what it’s doing?

    Thanks.

    • fafase
      April 13, 2014 - 1:04 pm Reply

      “To make the reading easier, I will turn the two parts (moving and rotating) we have into one Move() function.”

      This is in the first paragraph of the article.

      You should not only copy and paste but also read the article, you may find some useful tips.

  • ZakkiOrichalcum
    April 19, 2014 - 5:53 am Reply

    Thank you so much for making this tutorial! This was one of my biggest fears in my project that I was making for class was getting enemies to move in a somewhat reasonable fashion. And since the stealth tutorial from unity uses the NavMeshAgent which is only for Pro things, I thought I was kind of screwed. Its not as pretty sometimes as the NavMesh but I really like your system. It helped me a lot in how things can move and what to do for everything, so thank you thank you thank you very very much.

Leave a Reply

%d bloggers like this: