Running into an issue where the enemy cancels its bow attack if I move during it, basically if I stand still its fine but if I move it starts the animation but cancels before the animation event hits. I’m assuming it’s an issue with the action scheduler but can’t find where exactly the issue is. I’ll share my actionscheduler, fighter and aicontroller code.
using RPG.Core;
using RPG.Movement;
using UnityEditor;
using UnityEngine;
namespace RPG.Combat
{
public class Fighter : MonoBehaviour, IAction
{
// Weapon Properties
[SerializeField] private Weapon defaultWeapon = null;
private Weapon currentWeapon = null;
[SerializeField]
private Transform leftHandTransform = null;
[SerializeField]
private Transform rightHandTransform = null;
// Components
private Mover mover;
private ActionScheduler scheduler;
private Animator anim;
// Enemy
private Health target;
// IAction Name
public string Name => "Fighter";
// Variables
private float timeSinceLastAttack = Mathf.Infinity;
private void Awake()
{
mover = GetComponent<Mover>();
scheduler = GetComponent<ActionScheduler>();
anim = GetComponent<Animator>();
}
private void Start()
{
EquipWeapon(defaultWeapon);
}
private void Update()
{
timeSinceLastAttack += Time.deltaTime;
// Return if no target
if (!target || target.IsDead()) return;
// Check if we are in range
if (target && !GetIsInRange())
{
mover.MoveTo(target.transform.position, 1f);
}
else
{
mover.Cancel();
AttackBehaviour();
}
}
public void EquipWeapon(Weapon weapon)
{
currentWeapon = weapon;
weapon.Spawn(rightHandTransform, leftHandTransform, anim);
}
private void AttackBehaviour()
{
// Look at target
transform.LookAt(target.transform);
if (timeSinceLastAttack >= currentWeapon.GetCooldown())
{
// Set animation trigger and cooldown
anim.ResetTrigger("stopAttack");
anim.SetTrigger("attack");
timeSinceLastAttack = 0f;
}
}
private bool GetIsInRange()
{
if (target)
return Vector3.Distance(target.transform.position, transform.position) <= currentWeapon.GetRange();
else return false;
}
public bool CanAttack(GameObject combatTarget)
{
return combatTarget && combatTarget.TryGetComponent(out Health test) ? !test.IsDead() : false;
}
// Start Attack through scheduler
public void Attack(GameObject combatTarget)
{
scheduler.StartAction(this);
target = combatTarget.GetComponent<Health>();
}
public void Cancel()
{
anim.ResetTrigger("attack");
anim.SetTrigger("stopAttack");
target = null;
GetComponent<Mover>().Cancel();
}
#if UNITY_EDITOR
private void OnDrawGizmos()
{
float weaponAngle = 30f;
Vector3 left = Quaternion.AngleAxis(-weaponAngle / 2f, Vector3.up) * transform.forward;
Handles.color = new Color(1f, 0f, 0f, 0.1f);
Handles.DrawSolidArc(transform.position + Vector3.up, Vector3.up, left, weaponAngle, currentWeapon.GetRange());
Handles.color = Color.red;
Handles.DrawWireArc(transform.position + Vector3.up, Vector3.up, left, weaponAngle, currentWeapon.GetRange());
}
#endif
// Animation Event
private void Hit()
{
if (target)
{
if (currentWeapon.HasProjectile())
{
currentWeapon.Fire(rightHandTransform, leftHandTransform, target);
}
else
{
target.TakeDamage(currentWeapon.GetDamage());
}
}
}
private void Shoot()
{
Hit();
}
}
}
using RPG.Combat;
using RPG.Core;
using RPG.Movement;
using System;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[Range(0f, 1f)]
[SerializeField] private float patrolSpeedFraction = 0.2f;
[SerializeField] private float chaseDistance = 5f;
[SerializeField] private float suspicionTime = 3f;
private GameObject player;
private Fighter fighter;
private Health health;
private Mover mover;
// Guard
[SerializeField] private PatrolPath patrolPath;
[SerializeField] private float waypointTolerance = 1f;
private int currentWaypointIndex = 0;
private Vector3 guardPosition;
private float timeSinceLastSawPlayer = Mathf.Infinity;
private float timeSinceArrivedAtWaypoint = Mathf.Infinity;
private bool lookedAround = false;
private void Awake()
{
fighter = GetComponent<Fighter>();
health = GetComponent<Health>();
mover = GetComponent<Mover>();
}
private void Start()
{
player = GameObject.FindWithTag("Player");
guardPosition = transform.position;
}
private void Update()
{
if (health.IsDead()) return;
if (InAttackRange() && fighter.CanAttack(player))
{
lookedAround = false;
AttackBehaviour();
}
else if (timeSinceLastSawPlayer < suspicionTime)
{
SuspicionBehaviour();
}
else
{
lookedAround = false;
PatrolBehaviour();
}
UpdateTimers();
}
private void UpdateTimers()
{
timeSinceLastSawPlayer += Time.deltaTime;
timeSinceArrivedAtWaypoint += Time.deltaTime;
}
private void AttackBehaviour()
{
timeSinceLastSawPlayer = 0;
fighter.Attack(player);
}
private void SuspicionBehaviour()
{
if (lookedAround == false && GetComponent<NavMeshAgent>().velocity.magnitude <= 0.1f)
{
GetComponent<Animator>().SetTrigger("lookAround");
lookedAround = true;
}
GetComponent<ActionScheduler>().CancelCurrentAction();
}
private void PatrolBehaviour()
{
Vector3 nextPosition = guardPosition;
if (patrolPath)
{
if (AtWaypoint())
{
timeSinceArrivedAtWaypoint = 0;
CycleWaypoint();
}
nextPosition = GetCurrentWaypoint();
}
if (!patrolPath || timeSinceArrivedAtWaypoint > patrolPath.GetWaypointDwellTime(currentWaypointIndex))
{
mover.StartMoveAction(nextPosition, patrolSpeedFraction);
}
}
private bool AtWaypoint()
{
return Vector3.Distance(transform.position, GetCurrentWaypoint()) < waypointTolerance;
}
private Vector3 GetCurrentWaypoint()
{
return patrolPath.GetWaypoint(currentWaypointIndex);
}
private void CycleWaypoint()
{
currentWaypointIndex = patrolPath.GetNextIndex(currentWaypointIndex);
}
private void GuardBehaviour()
{
mover.StartMoveAction(guardPosition, patrolSpeedFraction);
}
private bool InAttackRange()
{
return Vector3.Distance(player.transform.position, transform.position) < chaseDistance;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.white;
Gizmos.DrawWireSphere(transform.position, chaseDistance);
}
}
}
using UnityEngine;
namespace RPG.Core
{
public class ActionScheduler : MonoBehaviour
{
IAction currentAction;
public void StartAction (IAction action)
{
// Check if action is already running
if (currentAction == action) return;
// Cancel current action if possible
if (currentAction != null)
{
currentAction.Cancel();
}
// Set new action
currentAction = action;
}
public void CancelCurrentAction()
{
StartAction(null);
}
}
}