Files
TopDown/Assets/Scripts/CombatAI.cs
2026-08-05 08:18:15 +01:00

175 lines
5.3 KiB
C#

using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(CharacterStats))]
[RequireComponent(typeof(CombatController))]
public class CombatAI : MonoBehaviour
{
[Header("Aggro & Retaliation")]
[Tooltip("If true, automatically attacks target when taking damage.")]
[SerializeField] private bool autoRetaliate = true;
[Tooltip("Detection distance for hostile eyesight vision.")]
[SerializeField] private float eyesightRange = 8.0f;
[Tooltip("Layer mask for targeting (default Player).")]
[SerializeField] private LayerMask targetLayer = ~0;
[Header("Movement")]
[SerializeField] private float chaseSpeed = 3.5f;
private CharacterStats stats;
private CombatController combat;
private NavMeshAgent navAgent;
private NPC npcComponent;
private void Awake()
{
stats = GetComponent<CharacterStats>();
combat = GetComponent<CombatController>();
navAgent = GetComponent<NavMeshAgent>();
npcComponent = GetComponent<NPC>();
stats.onDeath.AddListener(OnDeath);
stats.onHealthChanged.AddListener(OnHealthChanged);
}
private void Update()
{
if (stats.IsDead) return;
bool isHostile = (npcComponent != null)
? npcComponent.Relationship == NPCRelationship.Hostile
: false;
// Hostile eyesight check if no active target
if (isHostile && combat.CurrentTarget == null)
{
ScanForHostileTarget();
}
// Process combat chase & attack
if (combat.CurrentTarget != null)
{
if (combat.CurrentTarget.IsDead)
{
combat.ClearTarget();
StopMovement();
SetNPCState(NPCState.Idle);
return;
}
// Set NPC state to Attacking to stop wandering
SetNPCState(NPCState.Attacking);
float distanceToTarget = Vector3.Distance(transform.position, combat.CurrentTarget.transform.position);
if (distanceToTarget > stats.AttackRange)
{
// Chase target
MoveToPosition(combat.CurrentTarget.transform.position);
}
else
{
// In attack range
StopMovement();
combat.TryExecuteAttack();
}
}
}
private void SetNPCState(NPCState newState)
{
if (npcComponent != null && npcComponent.CurrentState != newState)
{
npcComponent.CurrentState = newState;
}
}
private void ScanForHostileTarget()
{
Collider[] hits = Physics.OverlapSphere(transform.position, eyesightRange, targetLayer);
foreach (var hit in hits)
{
if (hit.CompareTag("Player"))
{
CharacterStats playerStats = hit.GetComponent<CharacterStats>();
if (playerStats != null && !playerStats.IsDead)
{
combat.SetTarget(playerStats);
Debug.Log($"[{gameObject.name}] Aggroed on Player '{playerStats.CharacterName}' via Eyesight Range!");
break;
}
}
}
}
private void OnHealthChanged(float currentHealth, float maxHealth)
{
// Auto Retaliation on being attacked
if (autoRetaliate && combat.CurrentTarget == null)
{
// Find attacker by scanning nearby player or closest damage dealer
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null)
{
CharacterStats playerStats = playerObj.GetComponent<CharacterStats>();
if (playerStats != null && !playerStats.IsDead)
{
combat.SetTarget(playerStats);
Debug.Log($"[{gameObject.name}] Auto-retaliating against '{playerStats.CharacterName}'!");
}
}
}
}
private void MoveToPosition(Vector3 position)
{
if (navAgent != null && navAgent.enabled)
{
navAgent.speed = chaseSpeed;
navAgent.stoppingDistance = stats.AttackRange * 0.8f;
navAgent.SetDestination(position);
}
else
{
Vector3 dir = (position - transform.position);
dir.y = 0;
if (dir.magnitude > stats.AttackRange)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10f * Time.deltaTime);
transform.position += dir.normalized * chaseSpeed * Time.deltaTime;
}
}
}
private void StopMovement()
{
if (navAgent != null && navAgent.enabled)
{
if (navAgent.hasPath) navAgent.ResetPath();
}
}
private void OnDeath(GameObject killer)
{
combat.ClearTarget();
StopMovement();
SetNPCState(NPCState.Dead);
enabled = false;
}
private void OnDrawGizmosSelected()
{
bool isHostileGizmo = (npcComponent != null)
? npcComponent.Relationship == NPCRelationship.Hostile
: false;
if (isHostileGizmo)
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, eyesightRange);
}
}
}