- Home /
"Look at" logic interfering with simple raycast obstacle avoidance
I'm adding simple obstacle avoidance to a test AI script.
I have the default Unity cube with an added rigidbody which has my FPS controller's transform targeted. At a certain distance the cube first looks at (rotates towards) and then starts moving towards the player (FPS) via vector 3. Here is the (C#) script, anyone feel free to use this script for your own purposes:
using UnityEngine;
using System.Collections;
public class EnemyAI3 : MonoBehaviour {
public Transform target;
public Transform idleStandPosition;
public Transform idleLookDirection;
public int idleStandDistance = 1;
public int moveSpeed;
public int rotationSpeed;
public int maxDistance;
public float attackTimer;
public float coolDown;
public float rayAttackTimer;
public float rayCoolDown;
public float raycastLength = 1f;
public float seeDistance = 10f;
public float startPursuitDistance = 5f;
public bool raycastHitting = false;
public bool idle = false;
public bool canSeeTarget = false;
public bool pursuing = false;
public bool attacking = false;
public bool retreating = false;
public float turnSpeed = 5;
private Transform myTransform;
void Awake() {
myTransform = transform;
}
// Use this for initialization
void Start () {
GameObject go = GameObject.FindGameObjectWithTag("Player");
target = go.transform;
maxDistance = 2;
attackTimer = 0;
coolDown = 2.0f;
rayAttackTimer = 0;
rayCoolDown = 2.0f;
}
// Update is called once per frame
void FixedUpdate () {
if(attackTimer > 0)
attackTimer -= Time.deltaTime;
if(attackTimer < 0)
attackTimer = 0;
if(rayAttackTimer > 0)
rayAttackTimer -= Time.deltaTime;
if(rayAttackTimer < 0)
rayAttackTimer = 0;
Debug.DrawLine(target.position, myTransform.position, Color.yellow);
//If the player is close enough for us to see it & our raycast is not hitting
if(Vector3.Distance(target.position, myTransform.position) < seeDistance && !raycastHitting) {
//Look at our target
myTransform.rotation = Quaternion.Slerp(myTransform.rotation, Quaternion.LookRotation(target.position - myTransform.position), rotationSpeed * Time.deltaTime);
canSeeTarget = true;
}
//If the player is too far for us to see it
if(Vector3.Distance(target.position, myTransform.position) < seeDistance && canSeeTarget) {
//Wait until our timer counts down
attackTimer = coolDown;
}
//If the player is too far for us to see it & our timer has count down & our raycast is not hitting
if(Vector3.Distance(target.position, myTransform.position) > seeDistance && attackTimer == 0 && !raycastHitting) {
//Look at our Idle Stand Position
myTransform.rotation = Quaternion.Slerp(myTransform.rotation, Quaternion.LookRotation(idleStandPosition.position - myTransform.position), rotationSpeed * Time.deltaTime);
canSeeTarget = false;
//If we haven't reached our Idle Stand Position
if(Vector3.Distance(idleStandPosition.position, myTransform.position) > idleStandDistance) {
//Move towards our Idle Stand Position
myTransform.position += myTransform.forward * moveSpeed * Time.deltaTime;
}
//If we have reached close to our Idle Stand Position & our raycast is not hitting
if(Vector3.Distance(idleStandPosition.position, myTransform.position) < idleStandDistance && !raycastHitting) {
//Look at our Idle Look Direction
myTransform.rotation = Quaternion.Slerp(myTransform.rotation, Quaternion.LookRotation(idleLookDirection.position - myTransform.position), rotationSpeed * Time.deltaTime);
}
}
//If we haven't reached our Maximum Distance
if(Vector3.Distance(target.position, myTransform.position) > maxDistance) {
//If our target is close enough for us to start pursuit
if(Vector3.Distance(target.position, myTransform.position) < startPursuitDistance) {
//Move towards player
myTransform.position += myTransform.forward * moveSpeed * Time.deltaTime;
//pursuing = true;
}
}
//Start Raycast
if(Physics.Raycast(myTransform.position, myTransform.forward, raycastLength)){
Debug.DrawLine(myTransform.position, myTransform.forward, Color.blue);
myTransform.Rotate(Vector3.up, 90 * turnSpeed * Time.smoothDeltaTime);
raycastHitting = true;
rayAttackTimer = rayCoolDown;
}
if(!Physics.Raycast(myTransform.position, myTransform.forward, raycastLength) && rayAttackTimer == 0){
raycastHitting = false;
}
//End Raycast
}
}
I've added a raycast which when hit I would like to stop the "look at" and rotate the cube 90 degrees on the y axis. I'm not getting any errors, but at the moment it looks like the "look at" logic is stopping the raycast rotation function from happening since it is always trying to rotate towards the player. I've gone through several versions of this but can't seem to isolate the look at and raycast rotate to work separately. I'm sure it's something simple.
I've uploaded a package of the simple scene here. 3.6MB
Any ideas?
Answer by Owen-Reynolds · Mar 05, 2012 at 06:42 PM
Might be helpful to chart out just what you want to happen. Often the program was perfect, but your idea was wrong, but:
An if/else decides what to do every frame, with no "memory" of last frame. So, your guy could not be blocked, rotate on Y for two frames, then the raycast hits a tree and it starts to spin back towards the player again, but then next frame is unblocked so rotates on Y again. Sometimes you want that, so nothing wrong with just ifs.
If you want it to "remember" something, such as "now I should spin for 1/2 second," you a var to remember it with. A common trick for something timed looks like:
float doThingAUntilNow = 0.0f; // seconds
// not an offset -- real time:
// Ex: time is 6, doThingA is 8 -> do A for 2 seconds (until time 8)
// NOTE: if dTAUN is less than current time, we aren't doing A
if( something happens) doThingAUntilNow = Time.time + howLongToDoIt;
if(Time.time < doThingAUntilNow) doThingA();
// optional:
else {
do whatever we'd do if not thing A
}
Thanks for the really helpful advice Owen. Basically, I want for the AI to only "look at" its target when the raycast isn't hitting anything, as otherwise the raycast logic is telling the gameobject to turn 90 degrees and at the same time, the look at logic is trying to rotate it back to face the target. Would it be easiest to just set a boolean (eg. raycastIsHitting) to true whenever a raycast is hitting and then for the "look at" only activate it when the boolean is false? Something like this:
if(!raycastIsHitting) {
//Do the "look at" logic here
}
Suppose you have a thin tree between you and the target. Your idea will have the object look at the target, but then, since it's blocked by that tree, rotate away, then be unblocked and rotate back...
It will twitch just on the edge of the tree. Is that what you want?
Aah, I see what you're saying. Yes obviously twitching isn't very desirable haha, thanks for that. What if I put a timer on the cube so that after each raycast hit it would delay the rotation for looking back at the target. Since the cube is constantly moving forward (whether "looking at" or hitting via raycast) the brief rotation away and back at the target should be enough to let it navigate around, shouldn't it? I will be testing this later this afternoon, so atm it is just a theory :) What do you think?
doThingAUntilNow
is a timer / stateVariable.
In practice, hard to say what will look right. Sometimes a "wider" or longer raycast; or less frequent; or a "turn for 1/4th second rule; or set a waypoint around the obstacle and navigate towards that.
Alright, I added the timer to the raycast. I also added the boolean "raycastHitting" which is set to true when the raycast hits something only. All the "look at" logic has a !raycastHitting condition now, please have a look at the updated script in the question above (it may take a few $$anonymous$$utes for the updates to go through though). Everything is working as it did before but it looks like the ray is being cast along the local x axis ins$$anonymous$$d of local z which is strange cos I'm using transform.forward. The boolean and timer are both public so I can watch them in the inspector as the game runs, everything looks like it is working as it should. Ins$$anonymous$$d of rotating 90 degrees, it is turning just a little bit though. Can you see what I am doing wrong? The raycast part is the very last section in the script. I've also uploaded a package of the scene I'm working with, if you have a spare moment is there any chance you could have a look at it? It is a very simple setup. Thanks so much for your help so far Owen, really appreciate your time.