// EnemyBase.cs using System.Collections; using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(HealthManager))] public abstract class EnemyBase : MonoBehaviour , IFreezable { public EnemyConfig config; [Header("Targeting")] public Transform target; // assign player at runtime or via tag lookup public string playerTag = "Player"; // optional: auto-find protected NavMeshAgent agent; protected HealthManager health; protected float lastAttackTime = -999f; protected bool hasAggro; public bool isFrozen { get; set; } Coroutine _freezeCo; float _origSpeed, _origAccel, _origAngSpeed; protected virtual void Awake() { agent = GetComponent(); health = GetComponent(); } protected virtual void Start() { if (!target && !string.IsNullOrEmpty(playerTag)) { var go = GameObject.FindGameObjectWithTag(playerTag); if (go) target = go.transform; } ApplyConfig(); _origSpeed = agent.speed; _origAccel = agent.acceleration; _origAngSpeed = agent.angularSpeed; } protected virtual void Update() { if (!target || health.IsDead()) { Idle(); return; } float dist = Vector3.Distance(transform.position, target.position); if (!hasAggro) { if (dist <= config.aggroRange) hasAggro = true; else { Idle(); return; } } else // has aggro { if (dist >= config.loseAggroRange) { hasAggro = false; Idle(); return; } } // move / attack if (dist > config.attackRange) { Chase(); } else { FaceTarget(); TryAttack(); } } protected virtual void Idle() { if (agent && agent.enabled) { agent.isStopped = true; agent.ResetPath(); } } protected virtual void Chase() { if (!agent || !agent.enabled) return; agent.isStopped = false; agent.stoppingDistance = config.stoppingDistance; agent.SetDestination(target.position); } protected void FaceTarget() { Vector3 to = target.position - transform.position; to.y = 0f; if (to.sqrMagnitude > 0.001f) { var look = Quaternion.LookRotation(to); transform.rotation = Quaternion.RotateTowards(transform.rotation, look, config.angularSpeed * Time.deltaTime); } } protected void ApplyConfig() { if (!agent || config == null) return; agent.speed = config.moveSpeed; agent.angularSpeed = config.angularSpeed; agent.acceleration = config.accel; agent.stoppingDistance = config.stoppingDistance; } // ====== Attacking (implemented in child classes) ====== protected abstract void TryAttack(); // ====== IDamageable (forward to HealthManager) ====== public void ApplyDamage(float amount) => health.TakeDamage(amount); public bool IsDead() => health.IsDead(); // Handy debug [ContextMenu("Debug/Take 10")] void DebugHit() => ApplyDamage(10); public void Freeze(float duration) { if (_freezeCo != null) StopCoroutine(_freezeCo); _freezeCo = StartCoroutine(FreezeRoutine(duration)); } IEnumerator FreezeRoutine(float duration) { // Enter freeze if (!isFrozen) EnterFreeze(); // Wait for duration float t = duration; while (t > 0f) { t -= Time.deltaTime; yield return null; } // Exit ExitFreeze(); _freezeCo = null; } void EnterFreeze() { isFrozen = true; // Stop movement immediately if (agent) { agent.isStopped = true; // halts pathfinding updates agent.ResetPath(); // clear current path agent.velocity = Vector3.zero; // Optional: zero out movement-related params to avoid jitter agent.speed = 0f; agent.acceleration = 0f; agent.angularSpeed = 0f; } } void ExitFreeze() { isFrozen = false; if (agent) { // Restore movement settings agent.speed = _origSpeed; agent.acceleration = _origAccel; agent.angularSpeed = _origAngSpeed; agent.isStopped = false; } } }