|
using System; |
|
using System.Collections.Generic; |
|
using UnityEngine; |
|
using UnityEngine.AI; |
|
using UnityHFSM; |
|
|
|
[RequireComponent(typeof(GroundCheckComponent))] |
|
[RequireComponent(typeof(MoveComponent))] |
|
[RequireComponent(typeof(NavigateComponent))] |
|
|
|
public class EnemyMeleeTurtle : Enemy |
|
{ |
|
/// <summary> |
|
/// Authors list: |
|
/// Joshua Walcott |
|
/// |
|
/// Summary: |
|
/// The logic for the melee enemy. It starts out idle, then approaches if the player gets close enough, |
|
/// then hovers around the player before eventually attacking. |
|
/// |
|
/// </summary> |
|
|
|
private StateMachine finiteStateMachine; |
|
|
|
private GroundCheckComponent groundChecker; |
|
private HealthComponent health; |
|
private ModelEffectComponent modelEffects; |
|
private MoveComponent mover; |
|
private NavigateComponent navigator; |
|
|
|
private NavMeshAgent navMeshAgent; |
|
|
|
private GameObject dummyGameObject; |
|
//private Transform[] targets; |
|
private List<Transform> targets = new(); |
|
//public Transform currentTarget; |
|
|
|
[SerializeField] private float approachDistance = 15f; |
|
[SerializeField] private float attackDistance = 0.75f; |
|
[SerializeField] private float hoverDistance = 6f; |
|
[SerializeField] private float playerLossDistance = 18f; |
|
|
|
[SerializeField] private Vector2 idealHoverDistancesSet = new(4f, 5f); |
|
|
|
private float stateChangeTimer; |
|
|
|
[SerializeField] private float flickerTime = 0.55f; |
|
|
|
enum Age |
|
{ |
|
Adult, |
|
Baby |
|
}; |
|
|
|
protected override void Awake() |
|
{ |
|
base.Awake(); |
|
|
|
groundChecker = GetComponent<GroundCheckComponent>(); |
|
health = GetComponent<HealthComponent>(); |
|
modelEffects = GetComponent<ModelEffectComponent>(); |
|
mover = GetComponent<MoveComponent>(); |
|
navigator = GetComponent<NavigateComponent>(); |
|
|
|
navMeshAgent = GetComponent<NavMeshAgent>(); |
|
|
|
dummyGameObject = new GameObject(); |
|
|
|
GameObject[] targetGameObjects = GameObject.FindGameObjectsWithTag("Player"); |
|
foreach (GameObject gameObject in targetGameObjects) |
|
{ |
|
targets.Add(gameObject.transform); |
|
} |
|
currentTarget = targets[0]; |
|
|
|
finiteStateMachine = new StateMachine(); |
|
} |
|
|
|
protected override void Start() |
|
{ |
|
base.Start(); |
|
|
|
Action<float> Flicker = flicker => modelEffects.FlickerForSecondsEvent(flickerTime); |
|
health.OnDamaged += Flicker; |
|
|
|
idealHoverDistances = idealHoverDistancesSet; |
|
|
|
// Add states. |
|
finiteStateMachine.AddState |
|
( |
|
"Idle", new State |
|
( |
|
onEnter: state => EnterIdle(), |
|
onLogic: state => Idle(), |
|
onExit: state => ExitIdle() |
|
) |
|
); |
|
finiteStateMachine.AddState |
|
( |
|
"Approach", new State |
|
( |
|
onEnter: state => EnterApproach(), |
|
onLogic: state => Approach(), |
|
onExit: state => ExitApproach() |
|
) |
|
); |
|
finiteStateMachine.AddState |
|
( |
|
"Hover", new State |
|
( |
|
onEnter: state => EnterHover(), |
|
onLogic: state => Hover(), |
|
onExit: state => ExitHover() |
|
) |
|
); |
|
finiteStateMachine.AddState |
|
( |
|
"Wait", new State |
|
( |
|
onEnter: state => EnterWait(), |
|
onLogic: state => Wait(), |
|
onExit: state => ExitWait() |
|
) |
|
); |
|
finiteStateMachine.AddState |
|
( |
|
"Attack", new State |
|
( |
|
onEnter: state => EnterAttack(), |
|
onLogic: state => Attack(), |
|
onExit: state => ExitAttack() |
|
) |
|
); |
|
finiteStateMachine.AddState("Approach", new State(onLogic: state => Approach())); |
|
finiteStateMachine.AddState("EnterHover", new State(onLogic: state => EnterHover())); |
|
finiteStateMachine.AddState("Hover", new State(onLogic: state => Hover())); |
|
finiteStateMachine.AddState("EnterWait", new State(onLogic: state => EnterWait())); |
|
finiteStateMachine.AddState("Wait", new State(onLogic: state => Wait())); |
|
finiteStateMachine.AddState("PreAttack", new State(onLogic: state => PreAttack())); |
|
finiteStateMachine.AddState("EnterAttack", new State(onLogic: state => EnterAttack())); |
|
finiteStateMachine.AddState("Attack", new State(onLogic: state => Attack())); |
|
|
|
// Base state is Idle. |
|
finiteStateMachine.SetStartState("Idle"); |
|
|
|
// Logic for transitioning between states |
|
// First state is the from state, second is the to state, third is the condition |
|
/*fsm.AddTwoWayTransition( |
|
"Patrol", |
|
"Approach", |
|
transition => InApproachingRange() |
|
);*/ |
|
|
|
finiteStateMachine.AddTransition(new Transition( |
|
"Idle", "Approach", transition => (InApproachingRange() && EnemyHiveMindIsPresent()))); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"Approach", "Idle", transition => HasLostThePlayer())); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"Approach", "EnterHover", transition => InHoveringRange())); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"EnterHover", "Hover", transition => true)); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"Hover", "EnterWait", transition => (stateChangeTimer <= 0f && !GetEnemyHiveMindValue().CanAttack() && CloseToHoverTarget()))); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"Hover", "PreAttack", transition => (stateChangeTimer <= 0f && GetEnemyHiveMindValue().CanAttack()))); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"EnterWait", "Wait", transition => true)); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"Wait", "EnterHover", transition => (stateChangeTimer <= 0f && !GetEnemyHiveMindValue().CanAttack()))); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"Wait", "PreAttack", transition => (stateChangeTimer <= 0f && GetEnemyHiveMindValue().CanAttack()))); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"PreAttack", "Approach", transition => OutsideOfHoverDistance())); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"PreAttack", "EnterAttack", transition => InAttackingRange())); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"EnterAttack", "Attack", transition => true)); |
|
finiteStateMachine.AddTransition(new Transition( |
|
"Attack", "EnterHover", transition => stateChangeTimer <= 0f)); |
|
|
|
finiteStateMachine.Init(); |
|
} |
|
|
|
void FixedUpdate() |
|
{ |
|
finiteStateMachine.OnLogic(); |
|
} |
|
|
|
/// <summary> |
|
/// The approach state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void Approach() |
|
{ |
|
SetIsAttacking(false); |
|
|
|
mover.moveDirection = navigator.DecideDirection(dummyGameObject.transform, currentTarget.position, navMeshAgent); |
|
mover.Move(groundChecker.IsGrounded()); |
|
} |
|
|
|
/// <summary> |
|
/// The attack state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void Attack() |
|
{ |
|
mover.moveDirection = Vector3.zero; |
|
mover.Move(groundChecker.IsGrounded()); |
|
|
|
stateChangeTimer -= Time.deltaTime; |
|
} |
|
|
|
/// <summary> |
|
/// Returns true if the enemy & player are close enough together, while the enemy is hovering. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns> True: the enemy is close enough to the player. False: the enemy is not close enough to the player. </returns> |
|
bool CloseToHoverTarget() |
|
{ |
|
return navigator.DisplacementFromTarget(transform.position, hoverTarget) <= 1.0f; |
|
} |
|
|
|
/// <summary> |
|
/// Determines the closest target out of a list of targets. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void DetermineCurrentTarget() |
|
{ |
|
if (targets.Count <= 0) |
|
{ |
|
return; |
|
} |
|
|
|
float lowestDistance = float.MaxValue; |
|
|
|
foreach (Transform target in targets) |
|
{ |
|
float targetDistance = navigator.DisplacementFromTarget(transform.position, target.position); |
|
if (targetDistance < lowestDistance) |
|
{ |
|
lowestDistance = targetDistance; |
|
currentTarget = target; |
|
} |
|
} |
|
} |
|
/* |
|
/// <summary> |
|
/// Given a position, decides the direction to go. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void DecideDirection(Vector3 targetToFaceTowards) |
|
{ |
|
dummyGameObject.transform.position = new Vector3(transform.position.x, targetToFaceTowards.y, transform.position.z); |
|
|
|
bool willNavigate = willPathfind && navMeshAgent != null; |
|
if (willNavigate) |
|
{ |
|
navigator.SetDestination(targetToFaceTowards); |
|
NavMeshHit navMeshHit; |
|
navigator.SamplePathPosition(NavMesh.AllAreas, 1f, out navMeshHit); |
|
|
|
dummyGameObject.transform.LookAt(navMeshHit.position); |
|
direction = dummyGameObject.transform.forward; |
|
direction = new Vector3(direction.x, 0f, direction.z); |
|
} |
|
else |
|
{ |
|
//dummyGameObject.transform.position = new Vector3(transform.position.x, targetToFaceTowards.y, transform.position.z); |
|
|
|
dummyGameObject.transform.LookAt(targetToFaceTowards); |
|
//model.transform.rotation = dummyGameObject.transform.rotation; |
|
//model.transform.localPosition = Vector3.zero; |
|
//Debug.Log(dummyGameObject.transform.forward); |
|
//Debug.Log(dummyGameObject.transform.TransformDirection(dummyGameObject.transform.forward)); |
|
|
|
direction = dummyGameObject.transform.forward; |
|
direction = new Vector3(direction.x, 0f, direction.z); |
|
//direction.Normalize(); |
|
} |
|
} |
|
*//* |
|
/// <summary> |
|
/// Given a position, determines the horizontal (Y-axis omitted) displacement from self. |
|
/// </summary> |
|
/// <param name="targetToCheck"> The target to check. </param> |
|
/// <returns> Distance from target. </returns> |
|
float DisplacementFromTarget(Vector3 targetToCheck) |
|
{ |
|
return Vector2.Distance(new Vector2(transform.position.x, transform.position.z), new Vector2(targetToCheck.x, targetToCheck.z)); |
|
} |
|
*/ |
|
/*LazyDependency<EnemyHiveMind> EnemyHiveMind() |
|
{ |
|
LazyDependency<EnemyHiveMind> ehm; |
|
if |
|
}*/ |
|
|
|
/// <summary> |
|
/// The enter attack state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void EnterAttack() |
|
{ |
|
//SetIsAttacking(true); |
|
SetIsHovering(false); |
|
|
|
stateChangeTimer = 0.6f; |
|
|
|
//animator.SetTrigger("Attack"); |
|
} |
|
|
|
/// <summary> |
|
/// The enter hover state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void EnterHover() |
|
{ |
|
//SetIsAttacking(false); |
|
SetIsHovering(true); |
|
stateChangeTimer = 0.7f;//1.2f; |
|
DetermineCurrentTarget(); |
|
} |
|
|
|
/// <summary> |
|
/// The enter wait state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void EnterWait() |
|
{ |
|
//SetIsHovering(false); |
|
SetIsAttacking(false); |
|
|
|
stateChangeTimer = 2.0f;//0.3f; |
|
DetermineCurrentTarget(); |
|
} |
|
|
|
/// <summary> |
|
/// Returns true if the enemy & player are far enough apart. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns> True: the enemy has lost the player by being too far away. False: the enemy has not lost the player by being too far away. </returns> |
|
bool HasLostThePlayer() |
|
{ |
|
return navigator.DisplacementFromTarget(transform.position, currentTarget.position) > playerLossDistance; |
|
} |
|
|
|
/// <summary> |
|
/// The hover state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void Hover() |
|
{ |
|
if (navigator.DisplacementFromTarget(transform.position, hoverTarget) > 0.2f) |
|
{ |
|
//navigator.DecideDirection(hoverTarget); |
|
mover.moveDirection = navigator.DecideDirection(dummyGameObject.transform, hoverTarget, navMeshAgent); |
|
//mover.moveDirection = direction; |
|
} |
|
else |
|
{ |
|
mover.moveDirection = Vector3.zero; |
|
} |
|
mover.Move(groundChecker.IsGrounded()); |
|
|
|
if (navigator.DisplacementFromTarget(transform.position, currentTarget.position) < hoverDistance) |
|
{ |
|
stateChangeTimer -= Time.deltaTime; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// The idle state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void Idle() |
|
{ |
|
mover.moveDirection = Vector3.zero; |
|
mover.Move(groundChecker.IsGrounded()); |
|
|
|
DetermineCurrentTarget(); |
|
} |
|
|
|
/// <summary> |
|
/// Returns true if the enemy is in approaching range of the player. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns> True: the enemy is in approaching range of the player. False: the enemy is not in approaching range of the player. </returns> |
|
bool InApproachingRange() |
|
{ |
|
return navigator.DisplacementFromTarget(transform.position, currentTarget.position) < approachDistance; |
|
} |
|
|
|
// <summary> |
|
/// Returns true if the enemy is in attacking range of the player. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns> True: the enemy is in attacking range of the player. False: the enemy is not in attacking range of the player. </returns> |
|
bool InAttackingRange() |
|
{ |
|
return navigator.DisplacementFromTarget(transform.position, currentTarget.position) < attackDistance; |
|
} |
|
|
|
// <summary> |
|
/// Returns true if the enemy is in hovering range of the player. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns> True: the enemy is in hovering range of the player. False: the enemy is not in hovering range of the player. </returns> |
|
bool InHoveringRange() |
|
{ |
|
return navigator.DisplacementFromTarget(transform.position, currentTarget.position) < hoverDistance; |
|
} |
|
|
|
// <summary> |
|
/// Returns true if the enemy is not in hovering range of the player. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns> True: the enemy is not in hovering range of the player. False: the enemy is in hovering range of the player. </returns> |
|
bool OutsideOfHoverDistance() |
|
{ |
|
return navigator.DisplacementFromTarget(transform.position, currentTarget.position) > hoverDistance; |
|
} |
|
|
|
/// <summary> |
|
/// The pre-attack state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void PreAttack() |
|
{ |
|
SetIsHovering(false); |
|
SetIsAttacking(true); |
|
//navigator.DecideDirection(target.position); |
|
mover.moveDirection = navigator.DecideDirection(dummyGameObject.transform, currentTarget.position, navMeshAgent); |
|
//mover.moveDirection = direction; |
|
mover.Move(groundChecker.IsGrounded()); |
|
} |
|
|
|
/// <summary> |
|
/// The wait state. |
|
/// </summary> |
|
/// <param></param> |
|
/// <returns></returns> |
|
void Wait() |
|
{ |
|
mover.moveDirection = Vector3.zero; |
|
mover.Move(groundChecker.IsGrounded()); |
|
|
|
stateChangeTimer -= Time.deltaTime; |
|
} |
|
} |