woensdag 28 januari 2015

Simple Patrolling 2D AI in Unity3D with C#

Ever wanted to have a simple AI character in a 2D platforming game that you could easily assign patrol routes to? Well that's a pretty specific request, but have I got the thing for you!

What this script does, is move the AI game object from marker to marker, which can be any transform component. I prefer to use empty game objects with icons on them so I can see and select them in the editor, but you can't see them in-game. Of course you can use this marker system to your advantage. For example, if you want the player to be able to see the path the AI takes in a stealth game. Or you can make it so that the markers disappear when the AI reaches them and make the player able to create new markers, voila, strategy game!

You can also imagine using this script in combination with the microphone input in my previous post. A stealth game where you can shout into the microphone to distract the guards, or maybe whisper to prevent them from hearing you pass important information.


This code can be put on the desired object, then it will require a few things for you to do.
You'll need to put a 2D Collider on the object. (Preferrably a box or a circle)
If you want the object to have physics, just add a Rigidbody 2D.
Then just add any other components required by the extra options you've selected.

AIController.cs:
using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Timer))] //If you want to use the idle functionality, just add your timer here, or mine included at the bottom of this tutorial
public class AIController : MonoBehaviour 
{
 private Vector2 targetPos; //For ranged combat and the like
 [SerializeField] private Transform playerPos; //For anything related to the player's position
 [SerializeField] private Transform myEyes; //If you want AI that can spot the player
 [SerializeField] private float speed = 0, sightLength = 5; //The speed the AI moves at //The distance the AI can see
 [SerializeField] private int direction = -1; //The direction the AI is currently moving in (1 or -1)
 private float duration; //If you want to use the idle feature, this works in combination with my timer

 [SerializeField] private Transform[] markers; //The locations of the path-markers
 [SerializeField] private float[] idleTime; //If you want to use the idle feature, here you can set the duration of each idle
 private byte currentMarker; //Just the current target the AI is moving towards
 [SerializeField] private bool cycle = false; //If true, the AI will start following the markers back, else it'll move to the first one
 private bool invertedPath = false; //This just indicates if the AI is following the path forward or back
 private bool idle = false, idled = false, dead = false; //Stuff so the code works

 private Timer timer; //If you want to use the idle functionality, just add your timer here, or mine included at the bottom of this tutorial

 private bool playerInSight = false; //If you want the AI to be able to spot the player, this indicates if it has the player in its sights
 
 public int health; //If you want the AI to have health

 public Animator animator; //If you want the AI to animate

 // Use this for initialization
 void Start () 
 {
  if (myEyes == null)
  {
   myEyes = gameObject.GetComponentInChildren(); //If you wanted the AI to be able to see the player, but forgot to tell the game object
  }
  if (playerPos == null)
  {
   playerPos = GameObject.Find("player").transform; //Finds the player's position
  }
  if (markers.Length == 0)
  {
   idle = true; //This will make the AI idle automatically when no markers are placed
  }
  if (animator == null)
  {
   animator = gameObject.GetComponent(); //If no animator was added to the script, this will find it
  }
  timer = GetComponent(); //If you want to use the timer script added below, this finds it when placed on the game object
 }
 
 // Update is called once per frame
 void Update () 
 {
  if (!dead) //As long as the AI isn't dead
  {
   Move ();
   Watch ();

   if (health <= 0) //When the AI dies
   {
    animator.SetTrigger("dead");
    dead = true;
   }
  }
 }

 void IdleHandler () //When the AI is idling
 {
  duration -= Time.deltaTime;

  if (duration <= 0 && markers.Length > 0)
  {
   idle = false;
  }
 }

 void Watch ()
 {
  //I see you
  RaycastHit2D hit = Physics2D.Linecast(myEyes.position, playerPos.position,-1,0.1f,-0.1f);
  if (hit.collider != null)
  {
   if (hit.collider.tag == "Player")
   {
    if (Vector2.Distance(transform.position, playerPos.position) < sightLength)
    {
     playerInSight = true;
    }
   }
   else
   {
    playerInSight = false;
   }
  }

  //Where are you?
  if (playerInSight)
  {
   myGun.CursorAdjustment();
   myGun.Fire();
   animator.SetBool("firing",true);
   
   if (playerPos.position.x < transform.position.x)
   {
    direction = -1;
   }
   else
   {
    direction = 1;
   }
  }

  if (!playerInSight)
  {
   if (playerPos.position.x < transform.position.x)
   {
    direction = -1;
   }
   else
   {
    direction = 1;
   }
  }
 }

 void Move ()
 {
  //Move, my minion!
  if (!idle || playerInSight)
  {
   transform.position = new Vector3(transform.position.x + direction * speed, transform.position.y, transform.position.z);
   animator.SetBool("walking",true);
  }
  else
  {
   animator.SetBool("walking",false);
  }
  
  if (direction == 1)//Flips the 2D character around
  {
   if (transform.rotation.y != 180)
    transform.rotation = Quaternion.Euler(0,180,0);
  }
  else
  {
   if (transform.rotation.y != 0)
    transform.rotation = Quaternion.Euler(0,0,0);
  }

  if (idle)
  {
   IdleHandler();
  }
  
  if (!idle && !playerInSight) //When the AI isn't idle, this moves it
  {
   MarkerMovement();
   MarkerSwitch();
  }
 }

 void MarkerMovement ()
 {
  //Move which way?
  if (markers[currentMarker].position.x < transform.position.x)
  {
   direction = -1;
  }
  else
  {
   direction = 1;
  }
 }

 void MarkerSwitch ()
 {
  //I arrived at the current marker!
  if (markers[currentMarker].position.x - 1f <= transform.position.x && markers[currentMarker].position.x + 1f >= transform.position.x)
  {
   if (idleTime[currentMarker] > 0 && !idle && !idled)
   {
    idle = true;
    idled = true;
    duration = idleTime[currentMarker];
    return;
   }

   idled = false;

   //Ok, I'm at the last one... What now?
   if (currentMarker >= markers.Length-1)
   {
    if (cycle && !invertedPath)
    {
     //Turn around, follow them back!
     invertedPath = true;
     currentMarker -= 1;
     return;
    }
    else
    {
     //Just go straight back to the first one
     invertedPath = false;
     currentMarker = 0;
     return;
    }
   }
   if (currentMarker == 0 && invertedPath)
   {
    invertedPath = false;
    currentMarker += 1;
    return;
   }
   //Is the next marker this way?
   if (markers.Length > currentMarker && !invertedPath)
   {
    currentMarker += 1;
    return;
   }
   //What about this way?
   else if (0 < currentMarker && invertedPath)
   {
    currentMarker -= 1;
    return;
   }
  

  }
 }
}

Timer.cs:
using UnityEngine;
using System.Collections;

public class Timer : MonoBehaviour 
{
 [SerializeField] private bool destroyer = false;
 [SerializeField] private bool triggerer = false;
 [SerializeField] private bool surviveLoad = false;
 [SerializeField] private bool quitGame = false;

 public float duration;

 [System.NonSerialized] public bool triggered = false;

 void Awake ()
 {
  if (surviveLoad)
   DontDestroyOnLoad(gameObject);
 }

 void Update () 
 {
  duration -= Time.deltaTime;

  if (duration < 0)
  {
   Destroyer();
   Triggerer();
   QuitGame();
  }
 }

 void Destroyer ()
 {
  if (destroyer)
  {
   Destroy(gameObject);
  }
 }

 void Triggerer ()
 {
  if (triggerer)
  {
   triggered = true;
  }
 }

 void QuitGame ()
 {
  if (quitGame)
  {
   Application.LoadLevel("menu");
  }
 }
}

Pretty simple right? Now imagine instead of using 2D Unity stuff, you used 3D stuff like MoveTowards and used a collider to detect whether your AI has reached it's next marker. You just turned this code for 2D AI into code for 3D AI without much trouble!
Now go on and experiment with coding!

Geen opmerkingen:

Een reactie posten