Creating A Simpel AI(Enemys)

Hi there.

As the above title said, This is my first time trying to create a simple AI(Enemys) for my game. I would like to create two type of AI(Enemys). Right now I’m trying to do it one by one first the Swarm type.

  • Swarm type (where the AI(enemys) will attack you without you being detected or approach the enemy attack distance. Mostly all hack and slash game have this type of AI(enemys))
  • Guard type (where the AI(enemys) will attack you when you’ve been detected or near the attack distance of the AI(enemys) and when you run far away from the attack distance the AI(enemys) will not chase and attack you but will return to idle state.

as for this I have tried to create the Swarm type, But having trouble with what I want to achive. This is my script so far.

var player : Transform;
var walkSpeed : int; //value for walking
var walkSmooth : int; //value for smooth transition from walking to idle
var turnSmooth : int; //value for turning 
var attackDis : int; //stop before attack

private var myTransform : Transform;

function Awake()
{
	myTransform = transform;
}

function Start()
{
	var go : GameObject = GameObject.FindGameObjectWithTag("Player");

	player = go.transform;
}

function FixedUpdate()
{
	Debug.DrawLine(player.position, myTransform.position, Color.yellow);

	myTransform.rotation = Quaternion.Slerp(myTransform.rotation, Quaternion.LookRotation(player.position - myTransform.position), turnSmooth * Time.deltaTime);

	if (Vector3.Distance(player.position, myTransform.position) > attackDis) 
	{ 
		//Move towards target
		myTransform.position += myTransform.forward * walkSpeed * Time.deltaTime;
	}
}

@script RequireComponent (Rigidbody)

As you can see the in the video below my AI(enemys) will bump with each others and will be on top of each other.

Question Time:

  • How do I make them not bump with each other and stay on the ground and not jump on top of each other?
  • and how do I make my AI(enemys) avoid obstacle (e.g: the AI(enemys) can go around the obstacle to reach me)

Thanks in advance, help a nood make his dream come true.

Check out UnitySteer - it will certainly help with 2, as for 1 you’re likely going to need some AI abstraction here, something to “manage” groups (perhaps use formations?).

Okay, where do I start, sorry? and I see some clip from “MiniGore” game the swam they dont touch each other but they clip each other.

To avoid obstacles, you could just use a ray cast evey few moments from the monster, and if something is in front of its path , change its direction. This is just a simple method , by no means the best, but at your stage , probably well suited for your needs, both in practice and learning.

This would also take care of running into obstacles, and each other. As for them standing on top of each other etc, well , it seems to me , that you are instructing them to move with the actual transform, and that method of movement can (not always, pending on your setup) cause un reliable movement.

For eg: i have noticed , that a transform , and rigidbody on the same object (with a collider of course) can be moved and will stop accordingly when colliding with another collider bound object. But take for instance, an object that has no collider, or even the object with the rigidbody if it has no collider, it can just be told go anywhere. And finally , im not sure if your using any form of gravity on them , that may help.

I have some zombie AI i just started on yesterday here …

At the moment they are not using any form if preventive collision detection. I might add that to Day 3 Change log, who knows, haha.

http://willcotter.elementfx.com/UnityPlayers/Brandy.html

In my setup, i actually use characterControllers on the zombies (i find it easier to work with controllers over rigidbodys for what i need). Anyways , i will give ye a hand if i can, lemme know what ya digg up in your searches.

Looking good! :slight_smile:

@willc, From what I read you suggested is using "Physics.Raycast " am I right. Can "Physics.Raycast " raycast detect obstacle from their side? (e.g: like the picture below)

And what do you mean by:

not sure I get it, sorry.

726853--26422--$Raycast.jpg

UnitySteer is a collection of behavior modifiers that can be attached and weighted to control the actions of an enemy. It does not do pathing though. And as much as I adore the demo of swarming, pathing may become an issue when the dealing with things behind walls and such.

A combination of Steering and A* pathing may produce better results for you.

OK, so taking a basic AI controller that I built for someone on the forum. I modified it to do some raycasts and do a basic avoidance thing. it requires that all the units have colliders. So they don’t stack. It still lacks A* pathfinding, but it does a decent job of avoidance. Though it seems to like to turn left more than right. It basically does hip checks to see if there is enough clearance for the object to pass. If not, it does 45 degree ray casts to see which side is less cluttered.

It also handles minimum and maximum ranges and such. Pretty neat stuff. It is not based on blocks, but something similar to CharacterControllers. They are affected by gravity and do not jump if you rise up into the air.

Hopefully you can use at least some of it.

var target : Transform;
var moveSpeed : float = 5.0;
var rotationSpeed : float = 2.0;
var minDistance : float = 3.0;
var maxDistance : float = 60.0;
var eyeLevel = 1.0;
private var eyeOffset : Vector3;
private var castDistance : float;

function Start () {
	var go : GameObject = GameObject.FindGameObjectWithTag("Player");
	if(!go) return;
	target = go.transform;
	eyeOffset = Vector3.up * eyeLevel;
	castDistance = minDistance * 2;
}

function Update () {
	CheckGround();
	if(!target) return;
	var d1 : float;
	var d2 : float;
	
	//if((target.position - transform.position).magnitude < minDistance) return;
	if((target.position - transform.position).magnitude > maxDistance) return;
	
	var rotation = transform.rotation;
	transform.LookAt(target);
	transform.localEulerAngles.x = 0;
	transform.rotation = Quaternion.Slerp(rotation, transform.rotation, rotationSpeed * Time.deltaTime);
	
	d2 = GetHitOnTarget(Vector3.zero);
	if(d2 < minDistance || (target.position - transform.position).magnitude < minDistance) return;
	
	
	//var direction = target.position - transform.position;
	//var checkForward = Vector3.Dot(transform.forward, direction.normalized);
	//if(checkForward > 0.4){
	//	transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
	//}
	
	// avoidance
	// hip tests
	d1 = GetHitDistance(Vector3( 0.5, 0, 0), Vector3.forward);
	d2 = GetHitDistance(Vector3( -0.5, 0, 0), Vector3.forward);
	
	if(d1 < castDistance || d2 < castDistance){
		d1 = GetHitDistance(Vector3.zero, Vector3(-1, 0, 1));
		d2 = GetHitDistance(Vector3.zero, Vector3( 1, 0, 1));
		var vec = Vector3(-1, 0, 1);
		if(d1 < d2)
			vec = Vector3( 1, 0, 1);
		transform.LookAt(transform.TransformPoint(vec));
		transform.localEulerAngles.x = 0;
		transform.rotation = Quaternion.Slerp(rotation, transform.rotation, rotationSpeed * Time.deltaTime);
	}
	
	// movement tests
	d1 = GetHitDistance(Vector3.zero, Vector3.forward);;
	if(d1 > minDistance){
		transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
	}
}

function GetHitDistance(position : Vector3, vector : Vector3){
	var r = castDistance;
	var pos = transform.TransformPoint(position);
	var vec = transform.TransformDirection(vector);
	
	var ray = new Ray(pos + eyeOffset, vec);
	
	var hits : RaycastHit[];
	hits = Physics.RaycastAll(ray, castDistance);
	for(var hit : RaycastHit in hits){
		if(hit.transform.root != transform.root  hit.transform.root != target  !hit.collider.isTrigger){
			r = hit.distance;
			break;
		}
	}
	return r;
}

function GetHitOnTarget(position : Vector3){
	var r = maxDistance;
	
	var pos = transform.TransformPoint(position);
	var vec = target.position - transform.position;
	
	var ray = new Ray(pos + eyeOffset, vec);
	
	var hits : RaycastHit[];
	hits = Physics.RaycastAll(ray, castDistance);
	for(var hit : RaycastHit in hits){
		if(hit.transform.root == target){
			r = hit.distance;;
			break;
		}
	}
	return r;
}

function CheckGround(){
	//check our grounding
	var ray = new Ray(transform.position + Vector3.up * 1.0, -Vector3.up);
	var hits : RaycastHit[];
	hits = Physics.RaycastAll(ray, 1.1);
	var didHit = false;
	for(var hit : RaycastHit in hits){
		if(hit.transform.root != transform.root){
			didHit = true;
			transform.position = hit.point;
		}
	}
	
	// if we did not hit, we need to fall
	if(! didHit){
		transform.position += Physics.gravity * Time.deltaTime;
	}
}

@BigMisterB, wow thank you very much. My eye roll off when I read your post and trying to digest the information that been given.

Yes, this is the best way to do it. But it is advance for me. So I try started from something easy for me to do but still theres more to it then I expected. How far I am from my goal.

@BigMisterB, I would like to apologize to you because I tried and tried to learn from your script but it seems to much for me :(. So I try something else. Closer to what I imagine.

@willc, I took your advice about the raycast. I try read it and understand it. Still need more reading and pointer.

This is what I did so far, my script:

var player : Transform;

var walkSpeed : float = 5; //value for walking
var walkSmooth : float = 10; //value for smooth transition from walking to idle
var turnSmooth : float = 5; //value for turning 
var attackDis : float = 2; //stop before attack
var rotateSpeed : float = 20;

private var direction : int = 1;
private var myTransform : Transform;

function Awake()
{
	myTransform = transform;
}

function Start()
{
	var go : GameObject = GameObject.FindGameObjectWithTag("Player");

	player = go.transform;
}

function FixedUpdate()
{
	direction = Random.Range(-1, 2);
	
	Debug.DrawLine(player.position, myTransform.position, Color.yellow);
	
	myTransform.rotation = Quaternion.Slerp(myTransform.rotation, Quaternion.LookRotation(player.position - myTransform.position), turnSmooth * Time.deltaTime);

	if (Vector3.Distance(player.position, myTransform.position) > attackDis) 
	{ 
		if(!Physics.Raycast(myTransform.position, myTransform.forward, 2))
		{
			//Move towards target
			myTransform.position += myTransform.forward * walkSpeed * Time.deltaTime;
		}
		else
		{
			myTransform.Rotate(Vector3.up, 45 * rotateSpeed * Time.smoothDeltaTime * direction);
		}
	}
}

@script RequireComponent (Rigidbody)

My green cube manage to avoide the obsticale that I put, but they seems to jittering alot before changing direction to avoid the obstical. what is that happening? When it turn it doesnt turn smoothly.

OK, the way that you are doing it picks a random direction, then asks every frame if it can move towards the unit, if it cannot it rotates a random amount. This is where your jitteryness is coming from.

Lets rewrite what I did to conform to what you have.

Lets start off easy. using the Min and Max distances from my script we rewrite the update to read like this:

function Update () {
	var distance = Vector3.Distance(transform.position, target.position);
	if(distance > maxDistance) return;
	var rotation = transform.rotation;
	transform.LookAt(target);
	transform.rotation = Quaternion.Slerp(
		rotation,
		transform.rotation,
		rotationSpeed * Time.deltaTime);
	if(distance < minDistance) return;

	
	Debug.DrawLine(transform.position, target.position, Color.yellow);
}

What this means is that if we are too far away, don’t do anything. If we are too close, only make sure that we are rotating towards the target, but don’t do anything else.

Now, the whole store a rotation, set a look and whatever else, does pretty much the same thing as what you had before, it is just written simpler.

Now, lets use the function I wrote before (with some rewrites to make it a bit more readable:

function GetHitDistance(position : Vector3, vector : Vector3, distance : float){
	var r = distance;
	var pos = transform.TransformPoint(position);
	//Debug.DrawLine(pos, pos + transform.forward * 5, Color.red);
	var vec = transform.TransformDirection(vector);
	
	var ray = new Ray(pos, vec);
	var c = Color.green;
	
	var hits : RaycastHit[];
	hits = Physics.RaycastAll(ray, distance);
	for(var hit : RaycastHit in hits){
		if(
				hit.transform.root != transform.root 
				hit.transform.root != target 
				!hit.collider.isTrigger){
			r = hit.distance;
			c = Color.yellow;
			break;
		}
	}
	Debug.DrawLine(ray.origin, ray.GetPoint(r), c);
	return r;
}

Basically this function checks a physics hit from a transformed point and direction over a distance. First, we are going to “shoot from the hip” so to speak. We will create two rays from the hip of the box so that we are actually checking to see if the entire box can move through the area, and not just the center of the box.

So lets change this up a bit and use that function to make it move:

function Update () {
	// get and check distances
	var distance = Vector3.Distance(transform.position, target.position);
	if(distance > maxDistance) return;
	if(distance < minDistance) return;
	
	// store rotation
	var rotation = transform.rotation;
	
	// get ray tests
	var r1 = GetHitDistance(Vector3( 0.5,0,0), Vector3.forward, 2);
	var r2 = GetHitDistance(Vector3(-0.5,0,0), Vector3.forward, 2);
	
	// if we are good for both rays move it.
	if(r1 == 2  r2 == 2){
		transform.position += transform.forward * moveSpeed * Time.deltaTime;
		
		// do the rotation
		transform.LookAt(target);
		transform.rotation = Quaternion.Slerp(
			rotation,
			transform.rotation,
			2.0 * Time.deltaTime);
		
		// check the rotation and see if we should have rotated
		r1 = GetHitDistance(new Vector3( 0.5,0,0), Vector3.forward, 2);
		r2 = GetHitDistance(new Vector3(-0.5,0,0), Vector3.forward, 2);
		if(r1 < 2 || r2 < 2) transform.rotation = rotation;
	}

This now says, hey, if nothing is in front of you, move towards the target.

Now, lets complicate things a bit. If there is something in the way, lets do two raycasts at the hips pointing outward and find the one that is longer. This means that there is more space on that side. So we add this code to the end of it:

	// if we are not good then lets stop and rotate
	else {
		// check the rays from the hips
		r1 = GetHitDistance(new Vector3(-0.5,0,0), new Vector3(-0.5,0,2), 2);
		r2 = GetHitDistance(new Vector3( 0.5,0,0), new Vector3( 0.5,0,2), 2);
		
		// find the longest ray
		var direction = 1;
		if(r1 > r2) direction = -1;
		
		// apply rotation
		transform.Rotate(0, direction * rotationSpeed * Time.deltaTime, 0);
	}

This one extends the if(r1 == 2 r2 ==2) statement and will only come into play if either hit is less than 2.

It checks two new rays from the hips and outward slightly pointing forward This checks to see if anything is to the left or right of the object. So whichever distance is higher, evidently has more space. Choose that direction and start rotating in it.

The last thing, if you are using Capsules instead of boxes is to stabilize the model in space. You can get jitteryness from the physics engine if you are constantly trying to balance between a mathematical movement and physics one. This code prevents that.

function LateUpdate(){
	// stabilizer code
	var euler = transform.localEulerAngles;
	euler.x = 0;
	euler.z = 0;
	transform.localEulerAngles = euler;
	rigidbody.angularVelocity = Vector3.zero;
}

That is as simple as I can make it… lol