181 lines
4.6 KiB
C#

// 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<NavMeshAgent>();
health = GetComponent<HealthManager>();
}
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;
}
}
}