using Unity.Mathematics; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; public enum UnitState { Idle, Move, MoveToResource, Gather, MoveToEnemy, Attack } public class Unit : MonoBehaviour { [Header("Components")] public GameObject selectionVisual; private NavMeshAgent navAgent; private SelectionMarker selectionMarker; [Header("Stats")] public UnitState state; public int gatherAmount; public float gatherRate; private float lastGatherTime; public int curHp; public int maxHp; public int minAttackDamage; public int maxAttackDamage; public float attackRate; private float lastAttackTime; public float pathUpdateRate = 1.0f; private float lastPathUpdateTime; private Unit curEnemyTarget; public float attackDistance; public UnitHealthBar healthBar; public ResourceSource curResourceSource; //events [System.Serializable] public class StateChangeEvent : UnityEvent { } public StateChangeEvent onStateChange; public Player player; [System.Obsolete] void Awake() { //get the components navAgent = GetComponent(); SetState(UnitState.Idle); } // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { } // Update is called once per frame void Update() { if (selectionMarker != null && !navAgent.pathPending && navAgent.remainingDistance <= navAgent.stoppingDistance) { selectionMarker.DestroySelectionMarker(); selectionMarker = null; } switch(state) { case UnitState.Move: { MoveUpdate(); break; } case UnitState.MoveToResource: { MoveToResourceUpdate(); break; } case UnitState.Gather: { GatherUpdate(); break; } case UnitState.MoveToEnemy: { MoveToEnemyUpdate(); break; } case UnitState.Attack: { AttackUpdate(); break; } } } public void ToggleSelectionVisual(bool selected) { if(selectionVisual != null) selectionVisual.SetActive(selected); } public void MoveToPosition(Vector3 pos, SelectionMarker marker = null) { if (selectionMarker != null) { selectionMarker.DestroySelectionMarker(); selectionMarker = null; } navAgent.isStopped = false; navAgent.SetDestination(pos); selectionMarker = marker; SetState(UnitState.Move); } //move to a resource and begin to gather it public void GatherResource(ResourceSource resource, Vector3 pos) { curResourceSource = resource; SetState(UnitState.MoveToResource); navAgent.isStopped = false; navAgent.SetDestination(pos); } void SetState(UnitState toState) { state = toState; //calling the event if(onStateChange != null) onStateChange.Invoke(state); if(toState == UnitState.Idle) { navAgent.isStopped = true; navAgent.ResetPath(); } } void MoveUpdate() { if(!navAgent.pathPending && navAgent.remainingDistance <= navAgent.stoppingDistance) SetState(UnitState.Idle); } void MoveToResourceUpdate() { if(curResourceSource == null) { SetState(UnitState.Idle); return; } if(!navAgent.pathPending && navAgent.remainingDistance <= navAgent.stoppingDistance) SetState(UnitState.Gather); } void GatherUpdate() { if(curResourceSource == null) { SetState(UnitState.Idle); return; } LookAt(curResourceSource.transform.position); if(Time.time - lastGatherTime > gatherRate) { lastGatherTime = Time.time; curResourceSource.GatherResource(gatherAmount, player); } } //called every frame the "MoveToEnemy" state is active void MoveToEnemyUpdate() { if(curEnemyTarget == null) { SetState(UnitState.Idle); return; } if(Time.time - lastPathUpdateTime > pathUpdateRate) { lastPathUpdateTime = Time.time; navAgent.isStopped = false; navAgent.SetDestination(curEnemyTarget.transform.position); } if(Vector3.Distance(transform.position, curEnemyTarget.transform.position) <= attackDistance) SetState(UnitState.Attack); } //called every frame the "Attack" state is active void AttackUpdate() { //if our target is dead or null, go idle if(curEnemyTarget == null) { SetState(UnitState.Idle); return; } //if we are still moving if(!navAgent.isStopped) navAgent.isStopped = true; //attack every "attackRate" seconds if(Time.time - lastAttackTime > attackRate) { lastAttackTime = Time.time; curEnemyTarget.TakeDamage(UnityEngine.Random.Range(minAttackDamage, maxAttackDamage + 1)); } //Look at the enemy LookAt(curEnemyTarget.transform.position); //if we are too far away, move towards the enemy if(Vector3.Distance(transform.position, curEnemyTarget.transform.position) > attackDistance) SetState(UnitState.MoveToEnemy); } //rotate to face the given position void LookAt(Vector3 pos) { Vector3 dir = (pos - transform.position).normalized; float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg; transform.rotation = Quaternion.Euler(0, angle, 0); } //move to an enemy unit and attack them public void AttackUnit(Unit target) { curEnemyTarget = target; SetState(UnitState.MoveToEnemy); } //called when we take damage from another unit public void TakeDamage(int damage) { curHp -= damage; if(curHp <= 0) Die(); //update the health bar healthBar.UpdateHealthBar(curHp, maxHp); } //called when our health reaches 0 void Die() { player.units.Remove(this); Destroy(gameObject); } }