Top Down AI Enemy Movement, Face Player, and Shoot Tut

Hey if anyone is interested in how to make enemy AI with random movement (And this code will allow an enemy to crawl a maze if that’s what you want). I also go over line casting, and briefly explain layer mask. The enemy will look at the player and fire in his direction upon detection.

Enemy Bullet Script

using UnityEngine;

public class EnemyBullet01 : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Wall")
        {
            Destroy(gameObject);
        }

        if (collision.tag == "Player")
        {
            Destroy(gameObject);
        }
    }
}

Player Detection Script
@vakabaka Thanks for your help on this! You are mentioned in the video, amigo!

using UnityEngine;

public class PlayerDetection : MonoBehaviour
{
    public Transform origin, end, player;
    public float radarSpd;
    public bool playerDetected;

    public static bool playerIsDetected;

    private int playerLayer = 1 << 8;  
    private Rigidbody2D enemyRb;
    private Vector3 facePlayer;

    private void Start()
    {
        enemyRb = GetComponentInParent<Rigidbody2D>();
        playerIsDetected = false;
    }

    private void Update()
    {
        PlayerDetector();
        if (playerDetected == false)
        {
            Radar();
        }
        else { PlayerIsDetected(); }
              
    }

    void PlayerDetector()
    {
        Debug.DrawLine(origin.position, end.position, Color.red);
        playerDetected = Physics2D.Linecast(origin.position, end.position, playerLayer);
    }

    void Radar()
    {
        end.RotateAround(origin.position, Vector3.forward, radarSpd * Time.deltaTime);
        playerIsDetected = false;
    }

    void PlayersPosition()
    {
        facePlayer = player.position - enemyRb.transform.GetChild(0).GetChild(0).position;
        enemyRb.transform.GetChild(0).GetChild(0).up = -facePlayer;
    }

    void PlayerIsDetected()
    {
        if(playerDetected == true)
        {
            playerIsDetected = true;
            end.position = player.position;
            PlayersPosition();
        }
    }
}

Wall Detection Script

using UnityEngine;

public class EnemyDetection : MonoBehaviour
{
    public Transform originPointDown, endPointDown, originPointLeft,
                     endPointLeft, originPointRight, endPointRight,
                     originPointUp, endPointUp;

    public bool wallDetectedDown, wallDetectedLeft, wallDetectedRight, wallDetectUp;

    public static bool pathOpenDown, pathOpenLeft, pathOpenRight, pathOpenUp;
    private int wallLayer = 1 << 10;

    private void Update()
    {
         WallDetector();
    }

    void WallDetector()
    {
        Debug.DrawLine(originPointDown.position, endPointDown.position, Color.green);
        Debug.DrawLine(originPointLeft.position, endPointLeft.position, Color.green);
        Debug.DrawLine(originPointRight.position, endPointRight.position, Color.green);
        Debug.DrawLine(originPointUp.position, endPointUp.position, Color.green);
        wallDetectedDown = Physics2D.Linecast(originPointDown.position, endPointDown.position, wallLayer);
        wallDetectedLeft = Physics2D.Linecast(originPointLeft.position, endPointLeft.position, wallLayer);
        wallDetectedRight = Physics2D.Linecast(originPointRight.position, endPointRight.position, wallLayer);
        wallDetectUp = Physics2D.Linecast(originPointUp.position, endPointUp.position, wallLayer);
        CheckResults();
    }

    void CheckResults()
    {
        if( wallDetectedDown == true)
        {
            pathOpenDown = false;
        }
        else { pathOpenDown = true; }

        if ( wallDetectedLeft == true)
        {
            pathOpenLeft = false;
        }
        else { pathOpenLeft = true; }

        if ( wallDetectedRight == true)
        {
            pathOpenRight = false;
        }
        else { pathOpenRight = true; }

        if(wallDetectUp == true)
        {
            pathOpenUp = false;
        }
        else { pathOpenUp = true; }
    }
}

Enemy Movement Script

using System.Collections;
using UnityEngine;

public class Enemy01Move : MonoBehaviour
{
    [Header("Enemy Weapon Field")]
    public GameObject eBul;
    public Transform eBulSpawn;

    private Rigidbody2D enemyRb;
    private float enemySpd = 5f;
    private float bulSpd = 20f;
    private float shootTimer = 1.0f;
    private int moveDir;
    private int curDir; // You can store your current direction here but I'm not using it in this code. This is how you would do it though
    private bool canChange;
    private bool canFire;
    private bool readyTofire;
    private Vector2 moveDirection;

    private void Start()
    {
        enemyRb = GetComponent<Rigidbody2D>();
        moveDir = Random.Range(1, 5); // 1-4  -- 1=down 2=left 3=right 4=up
        if(moveDir < 1 || moveDir > 4)
        {
            while(moveDir < 1 || moveDir > 4) { moveDir = Random.Range(1, 5); }
        }

        canChange = true; // Allows the enemy to pick a random direction when true
        canFire = false; // Enemy can only fire if this is true
        readyTofire = false; // When true, it will set canFire true. To properly set the fire rate
    }

    private void Update()
    {
        if (PlayerDetection.playerIsDetected == false)
        {
            MovementHandler();
            readyTofire = false;
            shootTimer = 1.0f;
        }
        else
        {
            StopMoving();                      
        }

        if(readyTofire == true)
        {
            shootTimer -= Time.deltaTime;
           // Debug.Log(shootTimer);
            if (shootTimer > 0f && canFire == true)
            {
                StartCoroutine(FireRate());
            }

            if(shootTimer < -1f)
            {
                shootTimer = 1.0f;              
            }
        }
    }

    private void FixedUpdate()
    {
        enemyRb.MovePosition(enemyRb.position + (moveDirection * enemySpd) * Time.fixedDeltaTime);
    }

    void ChangeDirection()
    {
        // Can move in any direction
        if (EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == true && EnemyDetection.pathOpenRight == true &&
                                                                                                 EnemyDetection.pathOpenUp == true)
        {
            moveDir = Random.Range(1, 5); // 1-4  -- 1=down 2=left 3=right 4=up
            if (moveDir < 1 || moveDir > 4)
            {
                while (moveDir < 1 || moveDir > 4)
                {
                    moveDir = Random.Range(1, 5);
                    if (moveDir == curDir)
                    {
                        moveDir = Random.Range(1, 5);
                    }
                }// End of While loop
            }// End of IF moveDir statement
        } // End of all paths open IF statement

        // Cannot move right
        else if(EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == true && EnemyDetection.pathOpenRight == false &&
                                                                                                     EnemyDetection.pathOpenUp == true)
        {
            moveDir = Random.Range(1, 5); // 1-4  -- 1=down 2=left 3=right 4=up
            while(moveDir < 1 || moveDir > 4 || moveDir == 3)
            {
                moveDir = Random.Range(1, 5);
            }// End of While loop
        }// End of Right path closed all other paths open Statement

        // Cannot move Left or Right
        else if(EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == false && EnemyDetection.pathOpenRight == false &&
                                                                                                      EnemyDetection.pathOpenUp == true)
        {
            moveDir = Random.Range(1, 5);
            while(moveDir < 1 || moveDir > 4 || moveDir == 2 || moveDir == 3)
            {
                moveDir = Random.Range(1, 5);
            }// End of While Loop
        }// End of Up or Down path open IF Statement

        // Can only move up
        else if(EnemyDetection.pathOpenDown == false && EnemyDetection.pathOpenLeft == false && EnemyDetection.pathOpenRight == false &&
                                                                                                       EnemyDetection.pathOpenUp == true)
        {
            moveDir = 4;
        } // End of Up movement only IF statement

        // Can only move Up or Right
        else if(EnemyDetection.pathOpenDown == false && EnemyDetection.pathOpenLeft == false && EnemyDetection.pathOpenRight == true &&
                                                                                                      EnemyDetection.pathOpenUp == true)
        {
            moveDir = Random.Range(1,5);// 1-4  -- 1=down 2=left 3=right 4=up
            while (moveDir < 3 || moveDir > 4) // can only be 3 or 4
            {
                moveDir = Random.Range(1, 5);
            }
        }// End of Up or Right Movement IF statement

        // Can move Left, Right or Up
        else if(EnemyDetection.pathOpenDown == false && EnemyDetection.pathOpenLeft == true && EnemyDetection.pathOpenRight == true &&
                                                                                                     EnemyDetection.pathOpenUp == true)
        {
            moveDir = Random.Range(1, 5);
            while (moveDir > 4 || moveDir < 2) // Cannot be 1
            {
                moveDir = Random.Range(1, 5);
            }
        }// End of Left,Right,Up IF statement

        // Can only move Left or Up
        else if(EnemyDetection.pathOpenDown == false && EnemyDetection.pathOpenLeft == true && EnemyDetection.pathOpenRight == false &&
                                                                                                      EnemyDetection.pathOpenUp == true)
        {
            moveDir = Random.Range(1, 5); // 1 = down 2 = left 3 = right 4 = up
            while (moveDir < 1 || moveDir > 4 || moveDir == 1 || moveDir == 3)
            {
                moveDir = Random.Range(1, 5);
            }
        }// End of Left/Up IF statement

        // Cannot move left
        else if(EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == false && EnemyDetection.pathOpenRight == true &&
                                                                                                     EnemyDetection.pathOpenUp == true)
        {
            moveDir = Random.Range(1, 5);
            while(moveDir < 1 || moveDir > 4 || moveDir == 2)
            {
                moveDir = Random.Range(1, 5);
            }
        }// End of Down, Right, Up IF statement

        // Can move any direction except Up
        else if(EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == true && EnemyDetection.pathOpenRight == true &&
                                                                                                   EnemyDetection.pathOpenUp == false)
        {
            moveDir = Random.Range(1, 5);
            while(moveDir < 1 || moveDir > 3)
            {
                moveDir = Random.Range(1, 5);
            }
        }// End of any direction BUT Up statement

        // Cannot move Right or Up
        else if(EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == true && EnemyDetection.pathOpenRight == false &&
                                                                                                   EnemyDetection.pathOpenUp == false)
        {
            moveDir = Random.Range(1, 5); // 1 = down 2 = left 3 = right 4 = up
            while(moveDir < 1 || moveDir > 2)
            {
                moveDir = Random.Range(1, 5);
            }
        }// End of can't move Right or Up statement

        // Cannot move Left or Up
        else if (EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == false && EnemyDetection.pathOpenRight == true &&
                                                                                                   EnemyDetection.pathOpenUp == false)
        {
            moveDir = Random.Range(1, 5);
            while(moveDir < 1 || moveDir > 3)
            {
                moveDir = Random.Range(1, 5);
            }
        }// End of Can't move Left or Up statement

        // Can only move Down
        else if (EnemyDetection.pathOpenDown == true && EnemyDetection.pathOpenLeft == false && EnemyDetection.pathOpenRight == false &&
                                                                                                   EnemyDetection.pathOpenUp == false)
        {
            moveDir = 1;
        } // End of can only move Down statement

        // Can only move Left
        else if (EnemyDetection.pathOpenDown == false && EnemyDetection.pathOpenLeft == true && EnemyDetection.pathOpenRight == false &&
                                                                                                   EnemyDetection.pathOpenUp == false)
        {
            moveDir = 2;
        } // End of can only move Left statement

        // Can only move Right
        else if (EnemyDetection.pathOpenDown == false && EnemyDetection.pathOpenLeft == false && EnemyDetection.pathOpenRight == true &&
                                                                                                   EnemyDetection.pathOpenUp == false)
        {
            moveDir = 3;
        } // End of can only move Right

        else { moveDir = 4; } // In case we forgot a scenario or since we didn't add move up only, we'll move Up by default
    }

    void MoveLeft()
    {
        curDir = 2;
        enemyRb.transform.GetChild(0).GetChild(0).eulerAngles = new Vector3(0, 0, -90);
        moveDirection = Vector2.left;
    }

    void MoveRight()
    {
        curDir = 3;
        enemyRb.transform.GetChild(0).GetChild(0).eulerAngles = new Vector3(0, 0, 90);
        moveDirection = Vector2.right;
    }

    void MoveUp()
    {
        curDir = 4;
        enemyRb.transform.GetChild(0).GetChild(0).eulerAngles = new Vector3(0, 0, 180);
        moveDirection = Vector2.up;
    }

    void MoveDown()
    {
        curDir = 1;
        enemyRb.transform.GetChild(0).GetChild(0).eulerAngles = new Vector3(0, 0, 0);
        moveDirection = Vector2.down;
    }

    void StopMoving()
    {
        moveDirection = Vector2.zero;
        if (shootTimer >= 1.0f)
        {
            readyTofire = true;
            canFire = true;
        }
    }

    void StartShooting()
    {
            GameObject eBull = Instantiate(eBul, eBulSpawn.position, eBulSpawn.rotation);
            Rigidbody2D eBulRb = eBull.GetComponent<Rigidbody2D>();
            eBulRb.AddForce(eBulSpawn.up * bulSpd, ForceMode2D.Impulse);
            Destroy(eBull, 3f);              
    }

    void MovementHandler()
    {
        if (moveDir == 1)
        {
            if (EnemyDetection.pathOpenDown == true)
            {
                MoveDown();
            }
            else { ChangeDirection(); }
        }
        else if (moveDir == 2)
        {
            if (EnemyDetection.pathOpenLeft == true)
            {
                MoveLeft();
            }
            else { ChangeDirection(); }
        }
        else if (moveDir == 3)
        {
            if (EnemyDetection.pathOpenRight == true)
            {
                MoveRight();
            }
            else { ChangeDirection(); }
        }
        else if (moveDir == 4)
        {
            if (EnemyDetection.pathOpenUp == true)
            {
                MoveUp();
            }
            else { ChangeDirection(); }
        }

        if (canChange == true)
        {
            StartCoroutine(RandomMovement());
        }
    }

    IEnumerator RandomMovement()
    {
        canChange = false;
        var timer = Random.Range(0.5f, 2.5f);
        yield return new WaitForSeconds(timer);
        ChangeDirection();
        yield return new WaitForSeconds(0.5f);
        canChange = true;
    }

    IEnumerator FireRate()
    {
        canFire = false;
        float fireRate = 0.25f;
        yield return new WaitForSeconds(fireRate);
        StartShooting();
        yield return new WaitForSeconds(fireRate);
        canFire = true;

    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.tag == "Wall")
        {
            ChangeDirection();
        }
    }
}
5 Likes

Hi All,

I will add that with GitHub - h8man/NavMeshPlus: Unity NavMesh 2D Pathfinding - NavMesh for 2d it is easier to make wandering between waypoints, as a guard. Even with restricted movement in 4 directions, you still can utilize various NavMesh tools.

1 Like

Good, what I am missing in your videos is the example how this will work in the Unity. I mean, is it not better to show your work in action at beginn and talk about it without to go immediately deep in the code ?

1 Like

My laptop isn’t doing very well performance wise so if I click Run the video will lag and even if I stop recording my screen, it will cut the video off from where the lag started. So unfortunately, for now, this is the best I can do. Sucks! I know!
But you are right and that’s exactly how I want to do my videos. Thanks, amigo!

1 Like

You are completely correct! I love the NavMesh!
However, my videos, believe it or not is mostly to offer code examples for beginning coders. I really just started doing these videos so haven’t got into packages and resources yet. I do try to encourage custom design and delving into code at entry level. Learn the hard stuff, then show alternate/easier resources. My first line of series, I don’t even plan on getting deep into animating but I do want to introduce it at some point. :slight_smile:

Thank you for the feedback!

1 Like

Would using a LayerMask In wall Layers achieve the same effect?