Spinning 360 degrees and stopping (AI)

Alright so let me begin by explaining what my code is supposed to do…

There are around 10 random rooms, a random room is picked, the AI enemy walks to that room using a navagent with setdestination, during the walk to the room if the AIs raycasting happens to hit the player it enters chase mode where it grabs the players position and sets it as its destination, once it reaches the players last known destination it will scan for the player by spinning around basically a full circle as if it was looking around the room like “The hell? where did he go?” hell just a sweeping motion to the left and then to the right would be nice…

And then if the raycast hits anything during this spin, he then updates the players location and goes it it again and repeats the process until he either catches him (not coded yet because still figuring out scanning) or loses him then goes back into room search mode.

But I am having problems getting him to scan for the player, I can’t get him to properly spin around in a circle, I can get him to go in like a quarter of a circle or half a circle but never a full 360 degrees… I am also assuming once I do get 360 degree spinning code he can automatically break out of the spin once his raycast hits the player target…

I have tried Rotation.lookaround and slerp and lerp and various things and I can never seem to get just one full rotation like I want, or even just kind of an AI sweep as if you were twisting your head looking around a room.

I have looked everywhere and tried many methods but none seem to work… so do you have any idea of the best way to go about this?

(I have him in modes, so whenever he is walking in room searching he is in mode 0, when he has found a player and is in persuit its mode 1, and whenever he is in scanning mode its mode 2, just for reference, just a simple int for if checks and what not)

Edit: I can post code if necessary but I really just need to figure out how to spin an object 360 degrees once from where ever hes standing on I assume the Y rotation axis… I believe so.

Imagine if your enemy has a Search() method, which is called when enemy searches for player. You can InvokeRepeating it when enemy starts search and CancelInvoke when the player is found or if enemy gave up on searching. I recommend invoking because doing search every frame is not a best practice because of excessive load on processor and it will not give any better result, furthermore, it will affect probabilities as I explain later.

At this point of answer I’ve started making some code… and that’s what I eventually did:

First is a RaycastPoints class, it was made just to ease the working with visibility points of a player.

using UnityEngine;
using System.Collections;

public class RaycastPoints
{
	//Head
	private Transform head;
	private string headName = "pHead";
	public Transform Head
	{
		get { return head;}
	}

	//Torso
	private Transform torso;
	private string torsoName = "pTorso";
	public Transform Torso
	{
		get { return torso;}
	}

	//Left hand
	private Transform lHand;
	private string lhandName = "pLHand";
	public Transform LHand
	{
		get { return lHand;}
	}

	//Right hand
	private Transform rHand;
	private string rhandName = "pRHand";
	public Transform RHand
	{
		get { return rHand;}
	}

	//Left leg
	private Transform lLeg;
	private string llegName = "pLLeg";
	public Transform LLeg
	{
		get { return lLeg;}
	}

	//Right leg
	private Transform rLeg;
	private string rlegName = "pRLeg";
	public Transform RLeg
	{
		get { return rLeg;}
	}

	//Search in descendants (children, their children etc) for a object with given name and return it's transform
	private Transform FindInChildren(Transform sparent, string sname)
	{
		foreach(Transform schild in sparent.GetComponentsInChildren<Transform>())
		{
			if(schild.name == sname) return schild;
		}
		return null;
	}

	//Searching for body parts with names, defined above
	public void SetPoints(Transform parent)
	{
		head = FindInChildren(parent.transform, headName);
		if(head == null) Debug.Log("Head was not found!");
		torso = FindInChildren(parent.transform, torsoName);
		if(torso == null) Debug.Log("Torso was not found!");
		lHand = FindInChildren(parent.transform, lhandName);
		if(lHand == null) Debug.Log("Left hand was not found!");
		rHand = FindInChildren(parent.transform, rhandName);
		if(rHand == null) Debug.Log("Right hand was not found!");
		lLeg = FindInChildren(parent.transform, llegName);
		if(lLeg == null) Debug.Log("Left leg was not found!");
		rLeg = FindInChildren(parent.transform, rlegName);
		if(rLeg == null) Debug.Log("Right leg was not found!");
	}
}

Then the Player:

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {

	private static Player instance;
	public static Player Instance
	{
		get
		{	
			if(instance != null) return instance;
			else
			{
				Debug.Log ("There is no player in the scene!");
				return null;
			}
		}
	}

	private RaycastPoints rcPoints = new RaycastPoints();
	public static RaycastPoints RCPoints
	{
		get { return instance != null ? instance.rcPoints : null;}
	}

	public Vector3 Position
	{
		get { return transform.position;}
		//It's not a good idea just to return Vector3.zero if there is no Player. It's better to debug.log("there is no player")
	}

	public Transform RCPRoot;

	void Start()
	{
		instance = this;

		if(RCPRoot != null) rcPoints.SetPoints(RCPRoot);
	}
}

The player has some static properties so you can easily access them from enemy’s script.
So, the Enemy:

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour {

	//Point of view, view angle and maximal view distance in units
	public Transform pointOfView;
	private Vector3 POV;
	public float ViewAngle = 90f;
	public float ViewDistance = 20f;

	//Probabilities of noticing parts of player
	public float probTorso = 0.95f;
	public float probHead = 0.3f;
	public float probHand = 0.15f;
	public float probLeg = 0.2f;

	//DEBUG VARIABLE
	private bool isSeen = false;

	//Search for player
	private void Search()
	{
		bool see = false;
		if(Player.Instance != null)
		{
			Vector3 dirToPlayer = (Player.Instance.Position - transform.position).normalized;
			if(Vector3.Angle(transform.forward, dirToPlayer) < ViewAngle)
			{
				if(VisibilityCheck(Player.RCPoints.Torso, probTorso)) { see = true; SeePlayer(); }
				if(VisibilityCheck(Player.RCPoints.Head, probHead)) { see = true; SeePlayer(); }
				if(VisibilityCheck(Player.RCPoints.LHand, probHand))  { see = true; SeePlayer(); }
				if(VisibilityCheck(Player.RCPoints.RHand, probHand))  { see = true; SeePlayer(); }
				if(VisibilityCheck(Player.RCPoints.LLeg, probLeg))  { see = true; SeePlayer(); }
				if(VisibilityCheck(Player.RCPoints.RLeg, probLeg))  { see = true; SeePlayer(); }

			}
		}
		isSeen = see;
	}

	private bool VisibilityCheck(Transform playerPoint, float probability)
	{
		RaycastHit hit;

		if(Physics.Raycast(POV, playerPoint.position - POV, out hit, ViewDistance) && hit.collider.tag == "Player")
		{
			return Random.Range(0f ,1f) < probability;
		}
		else return false;
	}

	private void SeePlayer()
	{
		//CancelInvoke("Search");
		//What happens when enemy sees player
	}

	void Start()
	{
		POV = pointOfView.position;
		InvokeRepeating("Search", 0f, 0.25f);
	}

	// Update is called once per frame
	void Update ()
	{
		POV = pointOfView.position;
		if(isSeen) renderer.material.color = Color.red;
		else renderer.material.color = Color.white;
	}
}

When I worked on Enemy I already was pretty tired, so the code looks not how I imagine it to be, but it shows the idea. I left debugging parts so you can check by yourself how it works.

To try it you should:

  1. Create empty scene.
  2. Create player object. Attach Player script to it.
  3. Create points for raycasting as new objects, set them as children of that player object or it’s children’s children, or even deeper down the hierarchy, no matter how, but player object must be ancestor of those objects. Those points just always must be where you can hit a part of a player, so they need to be attached to particular body parts to move with them and be in a collider of an object that represents the player and has “Player” tag so when you raycast to that point from any direction you will hit the player. Or you can make this raycasting objects have their own colliders and tag them as “Player” (colliders must be set as triggers, so they won’t interfere with other objects). Also those points should be named this way: head: pHead; torso: pTorso; left hand: pLHand; right hand: pRHand; left leg: pLLeg; right leg: pRLeg. The names are defined in RaycastPoints class, you can redefine them if you want. This names are needed to provide automatic search for raycasting points.
  4. Set RCPRoot field of Player script. It is an object, where script will look for raycasting points, so if those points are somewhere in children of a player object, just put a player object in this field.
  5. Create enemy object, attach Enemy Script to it.
  6. Create object for enemy’s point of view, set it in the proper field of Enemy script on enemy object.

So to check how it works I’ve created an empty gameobject called ‘Player’ and attached 6 spheres with colliders (just primitives) to it, given the names of raycasting points. (you can see it in a screenshots) Those spheres must have ‘Player’ tag!
Then I’ve created a sphere primitive for an enemy, attached Enemy script to it, created an empty gameobject as a child of enemy object for enemy’s point of view, positioned it outside the enemy sphere. I had to make it because this system does not implements layer masks, so raycasting from inside the enemy sphere will hit the enemy itself, not the player. I’ll add layers later, i think.

If you do this way and if I did not forgotten something, the enemy sphere will turn red, when Enemy script detects player and turn white when the player is not in sight.

So, back to probabilities. You can define in Enemy script how hard is for enemy to notice a part of a player’s body. If you invoke Search() method every 0.25 seconds the probability to see the torso, exposed for 1 second to enemy’s sight, with probability for a single VisibilityCheck() set to 0.95 will be 0.99999375, so you can say it’s almost sure to notice the player. And if the enemy can see only one hand, the probability to notice the player will be only 0.47799375. So you are will be noticed with probability of 48% if you show your hand to an enemy for a second.

The maths works like this:

  1. Calculate the probability of a part not being seen subtracting the probability to notice it from one. For hand it will be: 1 - 0.15 = 0.85
  2. Calculate how many times enemy will try to notice player in a second. If you InvokeRepeating Search() method every 0.25 seconds it will occur 4 times in a second. For invoke with 0.25 s delay it will be: 1/0.25 = 4
  3. Raise the probability you got in step 1 in power equal to number of visibility checks in a second you got on step 2. That will be the probability of a body part not being seen. So it’s the probability of enemy failing every noticing check. 0.85^4 = 0.52200625
  4. Subtract the probability of nor being noticed you got on step 3 from one. You will get the probability the enemy will notice that part of body exposed for a second. 1 - 0.52200625 = 0.47799375

So as I said, you can define probabilities for single checks and numbers of checks per second and it will affect probabilities, giving very interesting and flexible way to adjust your enemy’s vision.

Again, I hope I did not forgotten something. I’ll answer later if you will have questions.

36683-hierarchy.jpg