using UnityEngine; using Akila.FPSFramework; public class NPCController : BaseAIController { private EnemyController targetEnemy; // The enemy currently being targeted [Header("Enemy Detection")] public float engagementDistance = 10f; // Distance at which NPC will engage detected enemies public LayerMask enemyLayerMask = -1; // Layer mask for enemy detection // Start is called once before the first execution of Update after the MonoBehaviour is created protected override void Start() { base.Start(); // Call the base class Start method } // Update is called once per frame void Update() { if (isDead) { HandleDeath(); return; } UpdateTimers(); float yStore = theRB.linearVelocity.y; if (targetEnemy != null && ShouldEngageEnemy()) { float distance = Vector3.Distance(targetEnemy.transform.position, transform.position); HandleChaseAndAttack(distance); } else { HandlePatrolling(); } PreserveVerticalVelocity(yStore); } // Event methods that can be assigned to SightDetector events in the Unity Editor /// /// Called when SightDetector detects a new collider. Assign this to SightDetector's OnNewCollider event. /// public void OnEnemyDetected(Collider detectedCollider) { if (detectedCollider == null) return; var enemy = detectedCollider.GetComponent(); if (enemy == null) return; float distance = Vector3.Distance(transform.position, enemy.transform.position); // Only engage if within engagement distance if (distance <= engagementDistance) { // If no current target or this enemy is closer, set as new target if (targetEnemy == null || distance < Vector3.Distance(transform.position, targetEnemy.transform.position)) { targetEnemy = enemy; Debug.Log($"NPC {gameObject.name} now targeting enemy: {enemy.gameObject.name}"); } } } /// /// Called when SightDetector loses sight of a collider. Assign this to SightDetector's OnLostCollider event. /// public void OnEnemyLost(Collider lostCollider) { if (lostCollider == null) return; var enemy = lostCollider.GetComponent(); if (enemy == null) return; // If we lost sight of our current target, clear it if (targetEnemy == enemy) { targetEnemy = null; Debug.Log($"NPC {gameObject.name} lost target: {enemy.gameObject.name}"); } } /// /// Called continuously while SightDetector detects colliders. Assign this to SightDetector's OnDetectCollider event. /// public void OnEnemyInSight(Collider detectedCollider) { if (detectedCollider == null) return; var enemy = detectedCollider.GetComponent(); if (enemy == null) return; float distance = Vector3.Distance(transform.position, enemy.transform.position); // Update target if this enemy is closer and within engagement distance if (distance <= engagementDistance) { if (targetEnemy == null || distance < Vector3.Distance(transform.position, targetEnemy.transform.position)) { targetEnemy = enemy; } } else if (targetEnemy == enemy) { // If current target moved out of engagement range, clear it targetEnemy = null; } } private bool HasClearLineOfSight(Vector3 targetPosition) { Vector3 direction = (targetPosition - transform.position).normalized; float distance = Vector3.Distance(transform.position, targetPosition); // Raycast to check for obstacles (excluding enemy layer) LayerMask obstacleLayer = ~enemyLayerMask; if (Physics.Raycast(transform.position + Vector3.up * 0.5f, direction, distance - 0.5f, obstacleLayer)) { return false; // There's an obstacle in the way } return true; // Clear line of sight } private bool ShouldEngageEnemy() { if (targetEnemy == null) return false; float distance = Vector3.Distance(targetEnemy.transform.position, transform.position); return distance <= engagementDistance; } private bool ShouldChaseEnemy(float distance) { return distance < chaseRange && playerDamageable.health > 0; } private void HandleChaseAndAttack(float distance) { LookAt(targetEnemy?.transform); if (distance > stopCloseRange) { MoveTowardsTarget(targetEnemy.transform, 1f); // Running speed } else { AttackTarget(); } } // This method will be called by Animation Events at the specific frame in the attack animation public void DealDamage() { // Check if enemy is still in range when the damage frame occurs if (targetEnemy != null && Vector3.Distance(targetEnemy.transform.position, transform.position) < 2f) { targetEnemy.TakeDamage(damageAmount); // Deal damage to the enemy Debug.Log("NPC dealt " + damageAmount + " damage to " + targetEnemy.gameObject.name); // Log the damage dealt } } }