Enemy AI using raycasting.. **Solved**

HI, I have been looking into all the ways of doing Ai for an enemy character… What I have decided upon is to use raycasting to position the enemy and let him just randomly wonder about the room, and when he sees you to go into attack mode and chase you.
I have a basic attack script working(sort of) but what I want it to do is, if the enemy looses sight of you to go to the last known position he saw you at and go back into Random Wonder mode.

What I have accomplished right now is to have an invisible GO above the enemy which has a smoothLookAt script attached to look directly at the player and cast a ray of 100 length towards the character. If the ray hits the player, he simply follows the player(geting stuck on walls, falling behind, etc.)

for Random Wonder mode, I need the enemy to happily explore the room without getting stuck on any walls… sort of like those robotic hoovers make their way around your house, but, you get the idea? Anyway, my code I have right now is this:

//EnemylockOn.js 
//attatched to the invisible GO above the enemy

var player : Transform;
var LKplayerPos : Transform;

function Update () 
{
	var hit : RaycastHit;
	
  	if(Physics.Raycast(transform.position,transform.TransformDirection(Vector3.forward) , hit, 50)) 
	{
    	if(hit.collider.gameObject.tag == "Player")
		{
			LKplayerPos = player;
			print("Player found");
		}
		else if(hit.collider.tag == "Wall")
		{
			LKplayerPos = null;
			print("Player lost");
		}
	}
	Enemy_ai_v2.player = LKplayerPos;
}

//EnemyAIv2.js
//attatched to the enemy its self.

var speed : float = 6.0;
var rotation : float = 8.0;

private var target : Transform;
private var myTransform : Transform;

static var player : Transform;

function Start()
{
	myTransform = transform;
}

function Update()
{
	if(player)
	{
		target = player;
		myTransform.rotation = Quaternion.Slerp(myTransform.rotation, 
				Quaternion.LookRotation(target.position - myTransform.position),
				rotation * Time.deltaTime);
		
		myTransform.position += myTransform.forward * speed * Time.deltaTime * 2;
	}
}

I know what I am asking is very tricky, and that’s fine, I have been using Unity for over two years now and I do know my way around the program.

Many, many thanks
–TG106

UPDATE

this is my code right now, the enemy has basic RW working and a more precise status switching, also a respawn. Any suggestions are very welcome.

var hits : int;
var rayRange : float;
var speed : float = 6.0;
var rotation : float = 8.0; 
var fieldOfViewRange : float = 68.0; 
var minPlayerDetectDistance : float = 4.0;

var status : int = 0;
private var csp = false;
private var repos = false;

/////////////////////
// STATUS KEY:
// 0 = sleep         
// 1 = chase  		 
// 2 = randomWonder. 
/////////////////////

var player : Transform;
var target : Transform;
private var myTransform : Transform;
private var spawnPos : Vector3;

private var rayDirection = Vector3.zero;

static var reset = false;

function Start()
{
	myTransform = transform;
	spawnPos = transform.position;
    	
	status = 0;
	
	// Error checks
	
	if(player == null)
		Debug.LogError("Player is not set on " + this.name);
		
	if(target == null)
		Debug.LogError("target is not set on " + this.name);
}

function Update()
{
	// Setting local variables 
	
	var hit : RaycastHit;
	var dtt = Vector3.Distance(transform.position, target.position);
    var distanceToPlayer = Vector3.Distance(transform.position, player.position);
    
    rayDirection = player.position - transform.position;
	
	// Failsafe 
	
    if((status != 2) && (Physics.Raycast(transform.position, transform.forward, hit, 2)))
    {
        target.position = transform.position;
    }
    if(transform.position.y <= -20)
    {
    	transform.position = spawnPos;
    }
    
	// Catch the player if he is within view 
	
    if((Vector3.Angle(rayDirection, transform.forward)) < fieldOfViewRange)
    { 
        if (Physics.Raycast (transform.position, rayDirection, hit, rayRange)) 
        {
            if ((hit.transform.tag == "Player") && (hit.transform.tag != "Wall"))
            {
                target.position = player.position;	
                csp = true;		
            }
            else
            {
            	csp = false;
            }
        }
    }
    
	// Catch the player if he sneaks up behind 
	
    if(Physics.Raycast (transform.position, rayDirection, hit))
    { 
        Debug.DrawRay(transform.position, rayDirection, Color.green);
        
        if((hit.transform.tag != "Player") && (distanceToPlayer <= minPlayerDetectDistance))
        {
            target.position = player.position;
        }
    }
    
    // Status setting 
    
	if(dtt <= 3 && csp)
	{
		status = 0;
	}
    if(dtt > 3 && csp)
    {
		status = 1;
	}
	if(dtt <= 3 && !csp)
    {
    	status = 2;
    }
	
	// Status Control
	
	if(csp)
		hits = 0;
	
	switch(status)
	{
		case 0:
			break;
		
		case 1: 
			myTransform.rotation = Quaternion.Slerp(myTransform.rotation, 
							       Quaternion.LookRotation(target.position - myTransform.position),
							       rotation * Time.deltaTime);
			
			myTransform.position += myTransform.forward * speed * Time.deltaTime * 2;
			
			break;
			
		case 2:
			RandomWonderMode();
			break;
			
		default:
			Debug.LogError("Unrecognised enemy status " + status);
			break;
	}
	
	if(reset)
	{
		Reset();
	}
}

function RandomWonderMode()
{
	var pp = player.positon;
	var mp = transform.position;
	var dtt = Vector3.Distance(transform.position, target.position);
	var hit : RaycastHit;
	
	if(!csp)
	{
		if(dtt > 3)
		{
			myTransform.rotation = Quaternion.Slerp(myTransform.rotation, 
							       Quaternion.LookRotation(target.position - myTransform.position),
							       rotation * Time.deltaTime);
							       		
			myTransform.position += myTransform.forward * speed * Time.deltaTime;
		}
		else
		{
			repos = true;
		}
		
		if(Physics.Raycast (transform.position, transform.forward, hit, 2) || repos)
		{
			var pos = Vector3(Random.Range(-50,50), 1, Random.Range(-50,50));
			target.position = pos;
			repos = false;
			hits ++;
		}
		
		if(hits > 100 && !csp)
		{
			hits = 0;
			transform.position = spawnPos;
		}
	}
}

function Reset()
{
	transform.position = spawnPos;
	hits = 0;
	status = 2;
	reset = false;
	
	// Error checks
	
	if(player == null)
	{
		Debug.LogError("Player is not set on " + this.name);
	}
	if(target == null)
	{
		Debug.LogError("target is not set on " + this.name);
	}
}

Some comments:

o Don’t null out lastKnownPlayerPos when you see a wall. The entire point of saving LKPPos is for when you finally see a wall. A common trick is to have a “wanderTarget.” When you first lose the player, set wanderTarget to LKplayerPos (and when you reach wanderTarget, set the next one randomly.) You’d need a “player seen last frame” var to check for “I just lost him.”

o Probably don’t look straight ahead to check if you see the player (unless the player is huge.) A lot of people check the angle (between my forward, and world angle from me to player) and then raycast directly to the player, for visibility.

o You don’t need an extra gameObject doing the raycast, if you don’t want. Your NPC script can just raycast from EmptyChildWhereMyEyesAre in whatever direction you like.

o transform.TransformDirection(Vector3.forward) has a shortcut: transform.forward that can make programs a little easier to read.

You probably want to look into some kind of better path finding. The common algorithm used to path between two points on a flat surface is A*. Unity 3.5 comes with a nice easy to use navigation mesh system, check it out here: http://unity3d.com/support/documentation/Manual/Navmesh%20and%20Pathfinding.html

It’s for Pro only unfortunately. Otherwise, implementing A* and its subsystems is excellent practise and great experience to have.