#nullable enable using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; public class AIEntity : Entity { [FormerlySerializedAs("stats")] [SerializeField] public AIStats AIStats = null!; BaseState currentState = null!; public string[] enemies {get; protected set; } public bool facingRight { get; private set; } = true; override protected void Start(){ base.Start(); currentState = CreateInitialState(); currentState.EnterState(); } override protected void Update() { base.Update(); if (currentState.UpdateState() is {} newState) SwitchState(newState); } override protected void FixedUpdate() { base.FixedUpdate(); if (currentState.FixedUpdateState() is {} newState) SwitchState(newState); FlipAccordingToInput(); } void OnDrawGizmos() => currentState?.OnDrawGizmos(); void SwitchState(BaseState newState) { currentState.LeaveState(); currentState = newState; newState.EnterState(); } protected virtual BaseState CreateInitialState() => new FindTargetState(this); //Looks into enemy name list to see if the other is targetable virtual protected bool IsTargetable(Entity other){ foreach (string name in enemies){ if(other.entityName == name && other.IsAlive()){ return true; } } return false; } override public bool TakeDamage(float amount, Entity other){ Entity currTargetEntity = GetTarget().GetComponent(); if(!(currTargetEntity is null)){ if(currTargetEntity.entityName == "Vampire") SetTarget(other.transform); } return base.TakeDamage(amount, other); } #region Flip public void Flip() { facingRight = !facingRight; Vector3 scaler = transform.localScale; scaler.x *= -1; transform.localScale = scaler; } public void FlipAccordingToInput() { Vector3 direction = rb.velocity; if(target != null) { direction = target.position - transform.position; } if ((!facingRight && direction.x > 0) || (facingRight && direction.x < 0)) { Flip(); } } #endregion protected abstract class BaseStateAI : BaseState{ protected AIEntity entity; public BaseStateAI(AIEntity entity){ this.entity = entity; } } protected class SeekState : BaseStateAI{ public SeekState(AIEntity entity) : base(entity){ } public override void EnterState() { if(! entity.animator.GetCurrentAnimatorStateInfo(0).IsName("Attack")) { entity.animator.Play("Running"); } } public override BaseState? UpdateState(){ if(!entity.IsAlive()){ return new DeadState(entity); } Entity targetEntity = entity.GetTarget().GetComponent(); if(targetEntity != null){ if(targetEntity.IsAlive()){//target is alive, keep chasing it return null; }else{//target is dead, go to findTargetState return new FindTargetState(entity);; } } return null; } public override BaseState? FixedUpdateState(){ Transform target = entity.GetTarget(); entity.direction = Vector3.RotateTowards(entity.direction, (target.position - entity.transform.position), entity.rotSpeed*Time.fixedDeltaTime, 0.0f); if(entity.IsTargetable(entity.GetTarget().GetComponent())){ if(!entity.IsInAttackRange()){ entity.rb.MovePosition(entity.transform.position + entity.direction * entity.movementSpeed * Time.fixedDeltaTime); // entity.animator.Play("Running"); }else{ return new AttackState(entity); } }else{ return new FindTargetState(entity); } // entity.animator.Play("Idle"); return null; } } protected class FindTargetState : BaseStateAI{ float closeEnough; Vector3 roamPosition; public FindTargetState(AIEntity entity) : base(entity){ } public override void EnterState() { if (!entity.animator.GetCurrentAnimatorStateInfo(0).IsName("Attack")) { entity.animator.Play("Running"); } } public override BaseState? UpdateState(){ if(!entity.IsAlive()){ return new DeadState(entity); } Entity[] entities = FindObjectsOfType(); float lastDist = float.MaxValue; Entity chosenEntity = null!; foreach (Entity other in entities){// Find the closest entity if(entity.IsTargetable(other)){ float distance = Vector2.Distance(other.transform.position, entity.transform.position); if(distance < lastDist){ lastDist = distance; chosenEntity = other; if(lastDist <= entity.AIStats.closeEnough)break; } } } if(chosenEntity != null){ entity.SetTarget(chosenEntity.transform); return new SeekState(entity); }else{ if(roamPosition == new Vector3()) roamPosition = entity.AIStats.getRandomRoamPositon(); return null; } } public override BaseState? FixedUpdateState(){ if(roamPosition == new Vector3()){ // entity.animator.Play("Idle"); return null; } entity.direction = Vector3.RotateTowards(entity.direction, (roamPosition - entity.transform.position), entity.rotSpeed*Time.fixedDeltaTime, 0.0f); if(Vector2.Distance(entity.transform.position, roamPosition) >= entity.attackRange){ entity.rb.MovePosition(entity.transform.position + entity.direction * entity.movementSpeed * Time.fixedDeltaTime); }else{ roamPosition = entity.AIStats.getRandomRoamPositon(); } // entity.animator.Play("Running"); return null; } } protected class AttackState : BaseStateAI{ public AttackState(AIEntity entity) : base(entity){ } public override void EnterState() { entity.animator.Play("Attack"); } public override BaseState? UpdateState(){ if(!entity.IsAlive()){ return new DeadState(entity); } if(entity.IsInAttackRange()){ if(entity.attackTimer >= entity.attackCooldown){ entity.attackTimer = 0; return Attack(); }else{ entity.attackTimer += Time.deltaTime; } }else{ return new SeekState(entity); } return null; } private BaseState? Attack(){ Entity targetEntity = entity.GetTarget().GetComponent(); if(targetEntity != null){ targetEntity.TakeDamage(entity.attackDmg, entity); bool isTargetAlive = targetEntity.IsAlive(); if(!isTargetAlive){ return new FindTargetState(entity); } } return null; } } protected class DeadState : BaseStateAI{ public DeadState(AIEntity entity) : base(entity){ } public override void EnterState() { entity.animator.Play("Death"); } public override BaseState? UpdateState(){ return null; } } }