Problem with Enemy reversing direction when colliding with walls.

I’ve written an Enemy State Based AI script which seems to work just fine. My only issue is with my collision with a wall. I’m using a Character Controller on my enemy and moving it using Move from Character Controller. This allows for the OnControllerColliderHit to work.

This seems to be functioning just fine and when the enemy strikes a wall on the x axis it rotates 180 degrees and moves in the opposite direction. However, if it hits a wall on the y or z axis it seems the bounce back and forth.

I have included the code. Please I have been racking my brain for some time now.

/*************************************************************************
*                                                                        *
* File: Enemy.cs                                                         *
*                                                                        *
* Author: Adam Davis                                                     *
*                                                                        *
* Desc: Enemy script AI for Goobs Cube Adventures                        *
*************************************************************************/
//
using System;
using UnityEngine;
using System.Collections;

public class EnemyAI : MonoBehaviour {

	// variable declaration
	public float speed = 1.0F;								// the speed at which the player moves.
	public float gravity = 20.0F;							// how much gravitiational pull is on the player.
    private Vector3 moveDirection = Vector3.zero;			// this is the vector that stores the players movement information.
	CharacterController controller ;
	GameObject player;
	public GameObject Gun;					// used to generate gun object
	public Rigidbody projectile;			// used to allow various types of projectiles
	public float bulletSpeed = 1000f;		// speed bullet travels	
	float myTimer = 0.5f;					// used to slow down the rate of fire
	public int health;
	public int minHealth = 1;
	public int maxHealth = 3;
	public Rigidbody goldCoins;
	
	public enum ENEMY_STATE{
		STATE_WANDER,
		STATE_CHASE,
		STATE_RETURN
	};
	
	public enum STATE_TRIGGERS{
		SIGHT_PLAYER,
		LOSE_PLAYER,
		KILLEDBY_PLAYER
	};
	
	public ENEMY_STATE currentState;
	public STATE_TRIGGERS currentTrigger = STATE_TRIGGERS.LOSE_PLAYER;	
	
	
	/********* TRIGGER_BEHAVIOR(int currentGenericEnemyAIState, int currentGenericEnemyAITrigger) *******/
	/*                                                                                                  */
	/*  DESCRIPTION:    Generic Enemy in-game behavior                                                  */
	/*                                                                                                  */
	/*  PASSED PARAMETERS:                                                                              */
	/*      currentGenericEnemyAIState - State the Generic Enemy AI is in                               */
	/*      currentGenericEnemyAITrigger - The trigger modifying the Generic Enemy State                */
	/*                                                                                                  */
	/*  NOTES:                                                                                          */
	/*                                                                                                  */
	/*  HISTORY:                                                                                        */
	/*      05-22-13 - first cut, atd                                                                   */
	/*                                                                                                  */
	/****************************************************************************************************/		
	void TRIGGER_BEHAVIOR(STATE_TRIGGERS currentTrigger)
	{
		switch(currentTrigger)											// Trigger causes state
		{			
		case STATE_TRIGGERS.SIGHT_PLAYER:				// Generic Enemy Enters Chase Mode
			Debug.Log(currentTrigger.ToString());		// Display it in UI
			STATE_BEHAVIOR(ENEMY_STATE.STATE_CHASE);	// Call Routine to handle States										
			break;
			
		case STATE_TRIGGERS.LOSE_PLAYER:				// Generic Enemy Loses sight of PLAYER
			Debug.Log(currentTrigger.ToString());		// Display it in UI
			STATE_BEHAVIOR(ENEMY_STATE.STATE_WANDER);	// Call Routine to handle States
			break;				
		
		case STATE_TRIGGERS.KILLEDBY_PLAYER:			// Generic Enemy is killed by PLAYER
			Debug.Log(currentTrigger.ToString());		// Display it in UI
			STATE_BEHAVIOR(ENEMY_STATE.STATE_RETURN);	// Call Routine to handle States					
			break;
		}			
	}
	
	/********* STATE_BEHAVIOR(int currentGenericEnemyAIState, int currentGenericEnemyAITrigger) *********/
	/*                                                                                                  */
	/*  DESCRIPTION:    Generic Enemy in-game behavior                                                  */
	/*                                                                                                  */
	/*  PASSED PARAMETERS:                                                                              */
	/*      currentGenericEnemyAIState - State the Generic Enemy AI is in                               */
	/*      currentGenericEnemyAITrigger - The trigger modifying the Generic Enemy State                */
	/*                                                                                                  */
	/*  NOTES:                                                                                          */
	/*         Handles Actions of Generic Enemy when in individual modes                                */
	/*         such as tracking PLAYER, etc.                                                            */
	/*                                                                                                  */
	/*  HISTORY:                                                                                        */
	/*      05-22-13 - first cut, atd                                                                   */
	/*                                                                                                  */
	/****************************************************************************************************/		
	void STATE_BEHAVIOR(ENEMY_STATE currentState)
	{
		switch(currentState)
		{
		case ENEMY_STATE.STATE_WANDER:
			/*********************************************************************************/
			/*                                                                               */
			/*          Code for Generic Enemy pathing behavior is called from here          */
			/*                                                                               */
			/*********************************************************************************/
			animation.Play("GenericEnemyWalking");
			moveDirection = Vector3.forward;			
			moveDirection = transform.TransformDirection(moveDirection);
			moveDirection *= speed;
			
			//Apply gavity and make the player move
			moveDirection.y -= gravity * Time.deltaTime;
			controller.Move(moveDirection * Time.deltaTime);
			break;
			
		case ENEMY_STATE.STATE_CHASE:
			/*********************************************************************************/
			/*                                                                               */
			/*           Code for Generic Enemy pathing behavior to chase PLAYER             */
			/*           goes in here.                                                       */
			/*                                                                               */
			/*********************************************************************************/
			Debug.Log(currentState.ToString());		// Display it in UI			
			
			
			// Look at including x and z leaning
			transform.LookAt(player.transform);			
			
			// Euler angles are easier to deal with. You could use Quaternions here also
			// C# requires you to set the entire rotation variable. You can't set the individual x and z (UnityScript can), so you make a temp Vec3 and set it back
			Vector3 eulerAngles = transform.rotation.eulerAngles;
			eulerAngles.x = 0;
			eulerAngles.z = 0;
 
			// Set the altered rotation back
			transform.rotation = Quaternion.Euler(eulerAngles);
			
			// slowing down the fire rate
			if(myTimer > 0){
				myTimer -=Time.deltaTime;
			}else if(myTimer<=0){				
				animation.Play("GenericEnemyWalkingThrow");
				fireGun();
				myTimer = 0.5f;
			}
			break;
			
		case ENEMY_STATE.STATE_RETURN:
			/*********************************************************************************/
			/*                                                                               */
			/*             Code to return Generic Enemy to base goes in here.                */
			/*                                                                               */
			/*********************************************************************************/
			
			break;
		}
	}
	
	// function to fire the gun
	void fireGun(){
		
		Rigidbody clone;					// used to clone bullets
		clone = Instantiate(projectile, Gun.transform.position,
			transform.localRotation) as Rigidbody;		// cloning the bullet object
		
		// direction to propel the bullet;
		if((transform.position.x - player.transform.position.x) > 0){
			clone.rigidbody.AddForce(Vector3.left * bulletSpeed);			
		}
		else if((transform.position.x - player.transform.position.x) < 0){
			clone.rigidbody.AddForce(Vector3.right * bulletSpeed);
		}
	}	
	
	void OnControllerColliderHit ( ControllerColliderHit hit )
	{
		if (hit.collider.gameObject.tag == "Wall"){
			
			//preventing the player from moving on the x and z axis
			Vector3 eulerAngles = transform.rotation.eulerAngles;
			eulerAngles.x = 0;
				
			// Set the altered rotation back
			transform.rotation = Quaternion.Euler(eulerAngles * -1);		
		
		Debug.Log ("HIT WALL - ROTATING!");	// Display it in UI
		}
	}
	

	
	void Start(){
		
		// assigning enemy health
		health = UnityEngine.Random.Range(minHealth, maxHealth);
		controller = GetComponent<CharacterController>();		
	}
	
	// Update is called once per frame
	void Update () {	

		player = GameObject.Find("Player");
		
		Vector3 sight = player.transform.position - transform.position;			// declaring the enemies forward line of sight
		sight.y = 0;
		
		float dot = Vector3.Dot(sight, transform.right);
			float forwardLineOfSight = 2f;
			
			if (dot < forwardLineOfSight  dot > -forwardLineOfSight)				// setting conditions for enemy to see player
			{
				RaycastHit  hit;													// preparing Raycast
				
				// checking to see if Raycast hit the Player
				if (Physics.Raycast(transform.position, (sight).normalized, out hit, forwardLineOfSight))
				{
					Debug.DrawRay(transform.position, (sight).normalized, Color.green);
					
					if(hit.collider.name == "Player"){
						Debug.DrawRay(transform.position, (sight).normalized, Color.red);
						
						currentTrigger = STATE_TRIGGERS.SIGHT_PLAYER;						
						//seePlayer = true;
					}
					else{
						//transform.renderer.material.color = Color.green;
						currentTrigger = STATE_TRIGGERS.LOSE_PLAYER;
					}
					
				}
				else{
					//transform.renderer.material.color = Color.green;
					currentTrigger = STATE_TRIGGERS.LOSE_PLAYER;
				}				
			}
		
		TRIGGER_BEHAVIOR(currentTrigger);	// run game simulation
	}
}

I just thought I’d be a bit more specific and show my collision routine.

    void OnControllerColliderHit ( ControllerColliderHit hit ) {
 
        if (hit.collider.gameObject.tag == "Wall"){
          
            Vector3 eulerAngles = transform.rotation.eulerAngles;          

            // Set the altered rotation back
            transform.rotation = Quaternion.Euler(eulerAngles * -1);        

        Debug.Log ("HIT WALL - ROTATING!"); // Display it in UI
        }
    }

Can anyone help me figure out why this is only working on x axis and not the z axis? It rotates and moves off the walls on the x-axis but basically looks like it’s stuck spinning when it collides with a wall on the other axis.

If you collide with an object head on, doing a 180 turn will point you away from the object and the next move will move you away.

If you clip an object on the side doing a 180 will just make you clip the object on the “other” side of the unit in the next move.

You probably want to do some calculation to work out an “escape vector” based on the position of the unit and the position of the ControllerColliderHit. You can then turn to face that and move directly away from whatever you’ve collided with.

As you can see here I have 2 walls, My enemy is the green cube with legs. He can strike the left wall and turn around just fine, even if he hits it at an angle, he turns around and moves in the opposite direction just fine. but if he strike the upper wall he turns around but then seems to be stuck turning around indefinitely. It’s my understanding that

transform.rotation = Quaternion.Euler(eulerAngles * -1);

rotates the x,y, and z rotation values by -1 of what they were. So if he was heading Vector2(0,90,0) and hits a wall it should change that to Vector3(0,-90,0)

am I wrong in my thinking?

Please if there is anyone that has created an Enemy AI that uses the Character Control and can hit walls and turn around 180 degrees could you please help me figure this out?

The problem that you’re having is because inverting the euler angles doesn’t necessarily result in a 180 degree rotation. Your example of (0, 90, 0) is a special case where it works, but in almost every other case it fails. The easiest way to invert your forward direction is probably to do:

transform.rotation = Quaternion.AngleAxis(180, transform.up) * transform.rotation;

Thank you so much this worked and solved my issue. you are a miracle worker!