Hello. I’m trying to introduce a line of sight into my enemy chasing state part of my state Machine, so that my enemies can chase me around Obstacles. However, for some reason, mine is a little unpredictable. In other words, sometimes it works, sometimes it doesn’t
Can someone please have a look at my script and see if it can be fixed?
using UnityEngine;
using RPG.Skills;
using UnityEngine.AI;
namespace RPG.States.Enemies
{
public class EnemyChasingState : EnemyBaseState
{
public EnemyChasingState(EnemyStateMachine stateMachine) : base(stateMachine) {}
private float attackingRange;
public readonly int TargetingForwardSpeedHash = Animator.StringToHash("TargetingForwardSpeed");
public override void Enter()
{
Debug.Log($"{stateMachine.gameObject.name} has entered chasing state");
stateMachine.Animator.CrossFadeInFixedTime(FreeLookBlendTreeHash, stateMachine.CrossFadeDuration);
attackingRange = stateMachine.Fighter.GetAttackingRange();
attackingRange *= attackingRange;
}
public override void Tick(float deltaTime)
{
if (!IsInChaseRange() && !IsAggrevated())
{
stateMachine.TriggerOnPlayerOutOfChaseRange();
stateMachine.SwitchState(new EnemyIdleState(stateMachine));
return;
}
Vector3 lastPosition = stateMachine.transform.position;
if (IsInAttackingRange())
{
// Solution to ensure the enemy can move around Obstacles, if his enemy (Player/another NPC) is behind a wall of some sort:
if (stateMachine.LastAttacker != null)
{
if (stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Ranged || stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Magic)
{
Debug.Log($"{stateMachine.gameObject.name} is wielding a ranged weapon, and LastAttacker is {stateMachine.LastAttacker.gameObject.name}");
// if (HasLineOfSight()) stateMachine.SwitchState(new EnemyAttackingState(stateMachine));
// else
if (!HasLineOfSight())
{
// Play the movement animation towards your goal:
stateMachine.Animator.SetFloat(FreeLookSpeedHash, 1f, stateMachine.AnimatorDampTime, deltaTime);
Debug.Log($"{stateMachine.gameObject.name} has no line of sight of {stateMachine.LastAttacker.gameObject.name}");
// the direction changing and movement itself should happen consistently as we are moving, so they're done inside the following while loop:
if (CanMoveTo(stateMachine.LastAttacker.transform.position))
{
Debug.Log($"{stateMachine.gameObject.name} is attempting to move to {stateMachine.LastAttacker.gameObject.name}");
// Get the movement direction through the Agent's desired velocity, including any contributions to obstacle avoidance:
Vector3 movementDirection = stateMachine.Agent.desiredVelocity.normalized;
// Face the direction of your movement:
FaceMovementDirection(movementDirection, deltaTime);
// Move to the target:
MoveToTarget(deltaTime);
return;
}
}
}
}
if (!stateMachine.CooldownTokenManager.HasCooldown("Attack"))
{
// you're not on Attack Cooldown, so you're quite aggressive, so switch to attacking state
Debug.Log($"{stateMachine.gameObject.name} is exiting chase state to attack state");
stateMachine.SwitchState(new EnemyAttackingState(stateMachine));
return;
}
else
{
Move(deltaTime);
stateMachine.Animator.SetFloat(FreeLookSpeedHash, 0);
// TEST (SUCCESS): get the enemies to point at each other, instead of the player, if they're fighting one another:
if (stateMachine.LastAttacker == null) stateMachine.SwitchState(new EnemyIdleState(stateMachine)); // ensures the enemy knows what to do if the enemy is dead (if this isn't here, NREs will be unleashed like bombs...!)
else FaceTarget(stateMachine.LastAttacker.transform.position, deltaTime);
// ORIGINAL:
// FaceTarget(stateMachine.Player.transform.position, deltaTime);
return;
}
}
else MoveToTarget(deltaTime);
// Updating the Movement Animation, so it all acts out smoothly (ALL STUFF BELOW):
Vector3 deltaMovement = lastPosition - stateMachine.transform.position;
float deltaMagnitude = deltaMovement.magnitude;
if (deltaMagnitude > 0)
{
// if the game is not paused:
FaceTarget(stateMachine.transform.position - deltaMovement, deltaTime);
float grossSpeed = deltaMagnitude / deltaTime;
stateMachine.Animator.SetFloat(FreeLookSpeedHash, grossSpeed / stateMachine.MovementSpeed, stateMachine.AnimatorDampTime, deltaTime);
}
else
{
// if the game is paused:
FaceTarget(stateMachine.Player.transform.position, deltaTime);
stateMachine.Animator.SetFloat(FreeLookSpeedHash, 0f);
}
}
public override void Exit()
{
if (!stateMachine.Agent.enabled) stateMachine.Agent.enabled = true;
stateMachine.Agent.ResetPath();
stateMachine.Agent.velocity = Vector3.zero;
}
private bool IsInAttackingRange()
{
if (stateMachine.LastAttacker == null)
{
return Vector3.SqrMagnitude(stateMachine.Player.transform.position - stateMachine.transform.position) <= attackingRange;
}
else return Vector3.SqrMagnitude(stateMachine.LastAttacker.transform.position - stateMachine.transform.position) <= attackingRange;
}
public void MoveToTarget(float deltaTime)
{
if (!stateMachine.Agent.enabled) stateMachine.Agent.enabled = true; // turn on the NavMeshAgent, otherwise the enemy won't be able to chase you down...
/* // stateMachine.Agent.destination = stateMachine.Player.transform.position;
// TEST: Instead of just hunting the player down everytime this enemy is attacked, hunt down the LastAttacker (it can be the player or another NPC...).
// If you found none, and this function is called, hunt the player down:
stateMachine.Agent.destination = stateMachine.LastAttacker != null ? stateMachine.LastAttacker.transform.position : stateMachine.Player.transform.position;
// The following 4 lines of code are replaced by a Test below them:
Vector3 desiredVelocity = stateMachine.Agent.desiredVelocity.normalized; // Normalized Desired Speed of the NPC
Move(desiredVelocity * stateMachine.MovementSpeed, deltaTime); // Go to the player, at that desired speed
stateMachine.Agent.velocity = stateMachine.CharacterController.velocity; // The velocity of the player
stateMachine.Agent.nextPosition = stateMachine.transform.position; // The next position to aim for */
// Entire rest of this function is a TEST:
Vector3 destination = stateMachine.LastAttacker != null ? stateMachine.LastAttacker.transform.position : stateMachine.Player.transform.position;
if (CanMoveTo(destination))
{
stateMachine.Agent.destination = destination;
Vector3 desiredVelocity = stateMachine.Agent.desiredVelocity.normalized;
Move(desiredVelocity * stateMachine.MovementSpeed, deltaTime);
stateMachine.Agent.velocity = stateMachine.CharacterController.velocity;
stateMachine.Agent.nextPosition = stateMachine.transform.position;
}
else
{
Debug.Log($"{stateMachine.gameObject.name} cannot move to the target. Stopping");
stateMachine.Agent.ResetPath();
stateMachine.Agent.velocity = Vector3.zero;
}
}
private bool HasLineOfSight()
{
Debug.Log($"HasLineOfSight has been called");
Ray ray = new Ray(stateMachine.transform.position + Vector3.up, stateMachine.LastAttacker.transform.position - stateMachine.transform.position);
if (Physics.Raycast(ray, out RaycastHit hit, Vector3.Distance(stateMachine.transform.position, stateMachine.LastAttacker.transform.position - stateMachine.transform.position), stateMachine.LayerMask))
{
return false;
}
return true;
}
private bool CanMoveTo(Vector3 destination)
{
var path = new NavMeshPath();
var hasPath = NavMesh.CalculatePath(stateMachine.transform.position, destination, NavMesh.AllAreas, path);
if (!hasPath) {Debug.Log($"no path found"); return false;}
if (path.status != NavMeshPathStatus.PathComplete) {Debug.Log($"Incomplete Path found, Can't take this path"); return false;}
if (GetPathLength(path) > stateMachine.maxNavPathLength) {Debug.Log($"Path is too long"); return false;}
return true;
}
private float GetPathLength(NavMeshPath path)
{
float total = 0;
if (path.corners.Length < 2) return total;
for (int i = 0; i < path.corners.Length - 1; i++)
{
total += Vector3.Distance(path.corners[i], path.corners[i + 1]);
}
return total;
}
private void FaceMovementDirection(Vector3 forward, float deltaTime)
{
if (forward == Vector3.zero) return;
Quaternion desiredRotation = Quaternion.LookRotation(forward, Vector3.up);
stateMachine.transform.rotation = Quaternion.Slerp(stateMachine.transform.rotation, desiredRotation, stateMachine.FreeLookRotationSpeed * deltaTime);
}
}
}
For the most part, I realized that if the enemy gets hit, it works. If he just had an arrow get near him in range, it won’t work
which… leads me to believe that I also need to set the last attacker properly when the arrow approaches the enemy, so he knows who to chase