Actually Decent Enemy AI
Somewhat comprehensible 450 lines of text Made at 1 am midnight because funny most importantly, it worksunknown
csharp
6 months ago
13 kB
9
Indexable
Never
using UnityEngine; using System.Collections; using UnityEngine.AI; public class OperatorAI : MonoBehaviour { public Transform player; [SerializeField] Animator animator; public LayerMask groundMask, playerMask; [SerializeField] public NavMeshAgent navMeshAgent; [SerializeField] private enum AIState { Idle, Chase, Attack, Disorient, Alert } [SerializeField] private AIState currentState; [SerializeField] float chaseRange; [SerializeField] float attackRange; [SerializeField] float walkPointRange = 20; private float chaseRangeSquared; private float attackRangeSquared; [SerializeField] private bool chasingPlayer = false; [Header("State Vars")] [SerializeField] bool pathAvailable; NavMeshPath navMeshPath; [Header("Idle State Variables")] public Vector3 walkPoint; public Vector2 idleDelay = new Vector2(3.15f, 5); [SerializeField] bool walkPointSet, hasStopped; public bool hasSetWalkPoints = false; [SerializeField] Transform[] fixedWalkPoints; [SerializeField] int idleAnims = 1; public delegate void PlayerDetected(); public event PlayerDetected OnPlayerDetected; private float disorientDuration; private float disorientTimer; private Vector3 lastKnownPlayerLocation; private State currentStateInstance; //------------------------------------------------------------------------------------- #region Main Stuff private void Awake() { navMeshPath = new NavMeshPath(); } private void Start() { currentState = AIState.Idle; chaseRangeSquared = Mathf.Pow(chaseRange, 2f); attackRangeSquared = Mathf.Pow(attackRange, 2f); // Create instances of the states State idleState = new IdleState(this); State chaseState = new ChaseState(this); State attackState = new AttackState(this); State disorientState = new DisorientState(this); State alertState = new AlertState(this); // Set the initial state currentStateInstance = idleState; SetStateIdle(); //maybe setting it twice would work } private void Update() { currentStateInstance.Update(); } private void LateUpdate() { animator.SetBool("isWalking", navMeshAgent.velocity.magnitude >= 0.75f); } private void ChangeState(State newState) { if (currentStateInstance != null) { currentStateInstance.Exit(); } currentStateInstance = newState; currentStateInstance.Enter(); } private bool IsPlayerInRange(float rangeSquared) { // Calculate the squared distance between the enemy and the player Vector3 playerPosition = player.position; Vector3 enemyPosition = transform.position; float distanceSquared = (playerPosition - enemyPosition).sqrMagnitude; // Check if the squared distance is within the desired range return distanceSquared <= rangeSquared; } private bool IsPlayerInAttackRange() { // Calculate the distance between the enemy and the player float distance = Vector3.Distance(transform.position, player.position); // Check if the player is within the attack range return distance <= attackRange; } bool CalculateNewPath() { navMeshAgent.CalculatePath(walkPoint, navMeshPath); if (navMeshPath.status != NavMeshPathStatus.PathComplete) return false; else return true; } public void StartDisorient(float duration) { if (currentState != AIState.Disorient) { currentState = AIState.Disorient; disorientDuration = duration; disorientTimer = Time.time + duration; StartCoroutine(DisorientCoroutine()); } } private IEnumerator DisorientCoroutine() { while (Time.time < disorientTimer) { yield return null; } SetStateAlert(); } void SearchWalkPoint() { if (hasSetWalkPoints) { int randomPoint = Random.Range(0, fixedWalkPoints.Length); walkPoint = fixedWalkPoints[randomPoint].position; } else { float randomZ = Random.Range(-walkPointRange, walkPointRange); float randomX = Random.Range(-walkPointRange, walkPointRange); walkPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ); } if (Physics.Raycast(walkPoint, -transform.up, 1.7f, groundMask)) { walkPointSet = true; } if (CalculateNewPath() == true) pathAvailable = true; else pathAvailable = false; } public void SetStateIdle() { ChangeState(new IdleState(this)); } public void SetStateChase() { ChangeState(new ChaseState(this)); } public void SetStateAttack() { ChangeState(new AttackState(this)); } public void SetStateDisorient() { ChangeState(new DisorientState(this)); } public void SetStateAlert() { ChangeState(new AlertState(this)); } public bool IsChasingPlayer() { return chasingPlayer; } public void StartChasingPlayer() { chasingPlayer = true; } public void StopChasingPlayer() { chasingPlayer = false; } #endregion //=--=-=-=--------------------------------------=========================================---------- #region State Classes private abstract class State { protected OperatorAI owner; public State(OperatorAI owner) { this.owner = owner; } public abstract void Enter(); // When state gets entered into public abstract void Update(); // While state runs public abstract void Exit(); // When state gets exited from } //----------------------------------------------------------------------------------------- // Idle private class IdleState : State { public IdleState(OperatorAI owner) : base(owner) { } public override void Enter() { Debug.Log("Idling"); } public override void Update() { if (!owner.walkPointSet || !owner.pathAvailable) owner.SearchWalkPoint(); Vector3 distToWalkPoint = owner.transform.position - owner.walkPoint; if (distToWalkPoint.magnitude <= 1f) { owner.walkPointSet = false; } // Transition to the chase state if the player is within a certain range if (owner.IsPlayerInRange(owner.chaseRangeSquared) && !owner.IsChasingPlayer()) { // Trigger the player detection event owner.OnPlayerDetected?.Invoke(); owner.SetStateChase(); } if (owner.walkPointSet && owner.pathAvailable && !owner.hasStopped) { owner.hasStopped = true; owner.StartCoroutine(IdleToDestination(owner.walkPoint, owner.idleDelay.x, owner.idleDelay.y)); return; } } IEnumerator IdleToDestination(Vector3 position, float delayTimeStart = 0f, float delayTimeEnd = 0f) { float _delay = Random.Range(delayTimeStart, delayTimeEnd); int _idle = Random.Range(0, owner.idleAnims); owner.animator.SetFloat("IdleAnim", _idle); yield return new WaitForSeconds(_delay); owner.navMeshAgent.SetDestination(position); owner.hasStopped = false; } public override void Exit() { // Perform exit behavior } } //----------------------------------------------------------------------------------------- // Chase private class ChaseState : State { public ChaseState(OperatorAI owner) : base(owner) { } public override void Enter() { // Perform enter behavior Debug.Log("Chasing"); owner.StartChasingPlayer(); // Set the chasing flag } public override void Update() { owner.navMeshAgent.SetDestination(owner.player.position); // Transition to the attack state if the player is within attack range if (owner.IsPlayerInAttackRange()) { owner.SetStateAttack(); } // Transition back to the idle state if the player moves out of range else if (!owner.IsPlayerInRange(owner.chaseRangeSquared)) { owner.StopChasingPlayer(); // Clear the chasing flag owner.SetStateIdle(); } } public override void Exit() { owner.StopChasingPlayer(); } } //----------------------------------------------------------------------------------------- // Attack private class AttackState : State { public AttackState(OperatorAI owner) : base(owner) { } public override void Enter() { Debug.Log("Attacking"); owner.animator.SetBool("isAttacking",true); owner.navMeshAgent.SetDestination(owner.transform.position); } public override void Update() { // Transition back to the chase state if the player moves out of attack range if (!owner.IsPlayerInAttackRange()) { owner.SetStateChase(); } } public override void Exit() { owner.animator.SetBool("isAttacking",false); } } //----------------------------------------------------------------------------------------- // Disorient private class DisorientState : State { private bool disorientFinished = false; public DisorientState(OperatorAI owner) : base(owner) { } public override void Enter() { // Perform enter behavior Debug.Log("Disoriented"); owner.animator.SetBool("hasBeenFlashed",true); owner.SetStateAlert(); owner.lastKnownPlayerLocation = owner.player.position; // Store the last known player location } public override void Update() { if (Time.time >= owner.disorientTimer) { disorientFinished = true; } } public override void Exit() { owner.animator.SetBool("hasBeenFlashed",false); } public bool IsDisorientFinished() { return disorientFinished; } } //----------------------------------------------------------------------------------------- // Alert private class AlertState : State { public AlertState(OperatorAI owner) : base(owner) { } private NavMeshAgent navMeshAgent; public override void Enter() { // Perform enter behavior Debug.Log("Alert"); owner.animator.SetBool("isAlerted",true); // Example: Use a NavMeshAgent to navigate towards the location navMeshAgent = owner.navMeshAgent; navMeshAgent.SetDestination(owner.lastKnownPlayerLocation); Debug.Log("Moving to last known player location: " + owner.lastKnownPlayerLocation); } public override void Update() { if (owner.currentState != AIState.Disorient) { owner.SetStateIdle(); return; } if (owner.currentState == AIState.Disorient) { DisorientState disorientState = owner.currentStateInstance as DisorientState; if (disorientState != null && disorientState.IsDisorientFinished()) { owner.SetStateAlert(); navMeshAgent.SetDestination(owner.lastKnownPlayerLocation); } } if (owner.IsPlayerInRange(owner.chaseRange)) { owner.SetStateChase(); } } public override void Exit() { Debug.Log("Never knew we uhh we uhh"); } } // For easy debugging ofc private void OnDrawGizmosSelected() { Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, attackRange); Gizmos.color = Color.green; Gizmos.DrawWireSphere(transform.position, chaseRange); Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, walkPointRange); try { Gizmos.DrawLine(gameObject.transform.position, player.position); } catch { }; } #endregion }