Moved from swarm to NavMesh - need to fix the enemy navigation! Review the animation controller to see if anything is being triggered.

This commit is contained in:
2026-06-19 16:20:36 +01:00
parent 14371de4e6
commit e058a7984f
705 changed files with 2046326 additions and 219 deletions

View File

@@ -1,10 +1,17 @@
using Synty.AnimationBaseLocomotion.Samples;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(EnemyRagdoll))]
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyAI : MonoBehaviour
{
public enum EnemyType { Horde, Siege }
[Header("Targeting Priorities")]
public EnemyType type = EnemyType.Horde;
[Range(0f, 1f)]
public float hordeBarrierFocusChance = 0.2f;
[Header("Stats")]
public int health = 30;
private bool isStunned = false;
@@ -19,47 +26,166 @@ public class EnemyAI : MonoBehaviour
[Header("Blocking")]
[Range(0f, 1f)]
public float blockChance = 0.15f; // 15% chance to block by default
[HideInInspector] public Transform currentTarget;
private Animator anim;
private Rigidbody rb;
private EnemyRagdoll ragdoll;
private NavMeshAgent agent;
private Fortification targetFortification;
private bool isDead = false;
public GameObject stunVFX;
private GameObject activeStunVFX;
void Start()
{
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody>();
ragdoll = GetComponent<EnemyRagdoll>();
agent = GetComponent<NavMeshAgent>();
rb.isKinematic = true;
rb.useGravity = false;
EnemySwarmManager.Instance.RegisterEnemy(transform);
anim.SetFloat("MoveSpeed", 3.5f);
EnemySwarmManager.Instance.RegisterEnemy(this);
//anim.SetFloat("MoveSpeed", agent.speed);
// Add a random offset to the initial attack timer so the mob doesn't attack in perfectly unified sync
lastAttackTime = Time.time + Random.Range(0f, attackCooldown);
//AssignTarget();
}
void Update()
{
if (isDead || isStunned) return;
// PlayerController.Instance comes from the core script setup
if (SamplePlayerAnimationController.Instance != null)
// Retargeting check if a barrier is broken
if (targetFortification != null && targetFortification.IsDestroyed)
{
Vector3 offset = SamplePlayerAnimationController.Instance.transform.position - transform.position;
// sqrMagnitude is significantly faster for the CPU than Vector3.Distance
if (offset.sqrMagnitude <= attackRange * attackRange)
targetFortification = null;
AssignTarget();
}
if (currentTarget != null)
{
Vector3 offset = currentTarget.position - transform.position;
float effectiveRange = (targetFortification != null) ? attackRange * 1.5f : attackRange;
// --- THE FIX: Pure mathematical distance check ---
if (offset.sqrMagnitude <= effectiveRange * effectiveRange)
{
agent.isStopped = true;
anim.SetFloat("MoveSpeed", 0f);
// Face the target manually
Vector3 direction = offset.normalized;
direction.y = 0;
// Prevent Unity error if direction is perfectly zero
if (direction.sqrMagnitude > 0.001f)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 5f);
}
if (Time.time - lastAttackTime > attackCooldown)
{
PerformAttack();
}
}
else
{
agent.isStopped = false;
agent.SetDestination(currentTarget.position);
// Fallback: If agent gets stuck briefly, keep the run animation playing smoothly
float currentSpeed = agent.velocity.magnitude;
if (currentSpeed < 0.1f && !agent.pathPending) currentSpeed = agent.speed;
anim.SetFloat("MoveSpeed", currentSpeed);
}
}
}
// --- TRIGGERED VIA ANIMATION EVENT ON THE ENEMY ATTACK CLIP ---
public void ExecuteEnemyHit()
{
if (isDead || currentTarget == null) return;
Vector3 offset = currentTarget.position - transform.position;
float effectiveRange = (targetFortification != null) ? attackRange * 1.5f : attackRange;
if (offset.sqrMagnitude <= effectiveRange * effectiveRange)
{
if (targetFortification != null && !targetFortification.IsDestroyed)
{
targetFortification.TakeDamage(attackDamage);
}
else
{
// Safely grab the player health without relying on the old PlayerController script
PlayerHealth pHealth = currentTarget.GetComponent<PlayerHealth>();
if (pHealth != null)
{
pHealth.TakeDamage(attackDamage);
Debug.Log("<color=red>Enemy landed a hit on the Player!</color>");
}
}
}
}
private void AssignTarget()
{
bool prioritizeBarrier = (type == EnemyType.Siege) || (Random.value < hordeBarrierFocusChance);
if (prioritizeBarrier)
{
targetFortification = FindNearestFortification();
if (targetFortification != null)
{
currentTarget = targetFortification.transform;
}
else
{
FindPlayerTarget();
}
}
else
{
FindPlayerTarget();
}
}
private void FindPlayerTarget()
{
GameObject player = GameObject.FindGameObjectWithTag("Player");
if (player != null)
{
currentTarget = player.transform;
}
else
{
Debug.LogWarning("<color=red>EnemyAI could not find the Player! Ensure your Player Prefab has the Tag set to 'Player'.</color>");
}
}
private Fortification FindNearestFortification()
{
Fortification nearest = null;
float minDist = float.MaxValue;
foreach (Fortification fort in Fortification.AllFortifications)
{
if (fort.IsDestroyed) continue; // Ignore rubble
float dist = (transform.position - fort.transform.position).sqrMagnitude;
if (dist < minDist)
{
minDist = dist;
nearest = fort;
}
}
return nearest;
}
private void PerformAttack()
{
lastAttackTime = Time.time;
@@ -68,19 +194,6 @@ public class EnemyAI : MonoBehaviour
anim.SetTrigger("Attack");
}
// --- TRIGGERED VIA ANIMATION EVENT ON THE ENEMY ATTACK CLIP ---
public void ExecuteEnemyHit()
{
if (isDead || SamplePlayerAnimationController.Instance == null) return;
// Simple check to see if the player is still in range when the attack lands
Vector3 offset = SamplePlayerAnimationController.Instance.transform.position - transform.position;
if (offset.sqrMagnitude <= attackRange * attackRange)
{
SamplePlayerAnimationController.Instance.GetComponent<PlayerHealth>().TakeDamage(attackDamage);
}
}
public void TakeDamage(int damage, Vector3 hitSource, float knockbackForce)
{
if (isDead) return;
@@ -121,8 +234,9 @@ public class EnemyAI : MonoBehaviour
{
isDead = true;
EnemySwarmManager.Instance.RemoveEnemy(transform);
EnemySwarmManager.Instance.RemoveEnemy(this);
ragdoll.ActivateRagdoll(hitSource, knockbackForce);
Destroy(stunVFX);
Destroy(gameObject, 3f);
this.enabled = false;
}
@@ -136,6 +250,10 @@ public class EnemyAI : MonoBehaviour
{
isStunned = true;
anim.SetTrigger("Stun"); // Requires a "Stun" trigger in your Animator
if (stunVFX != null && activeStunVFX == null)
{
activeStunVFX = Instantiate(stunVFX, transform.position, Quaternion.identity, transform);
}
// Detach from the Burst Compiler so they stop sliding toward you
EnemySwarmManager.Instance.PauseEnemyMovement(transform);
@@ -146,6 +264,7 @@ public class EnemyAI : MonoBehaviour
{
isStunned = false;
anim.SetTrigger("Recover"); // Requires a "Recover" trigger in your Animator
Destroy(activeStunVFX);
// Reattach to the Burst Compiler so they resume the chase
EnemySwarmManager.Instance.ResumeEnemyMovement(transform);