Files

231 lines
6.5 KiB
C#

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<UnitState> { }
public StateChangeEvent onStateChange;
public Player player;
[System.Obsolete]
void Awake()
{
//get the components
navAgent = GetComponent<NavMeshAgent>();
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);
}
}