231 lines
6.5 KiB
C#
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);
|
|
}
|
|
}
|