Files
CartoonFPS/Assets/Scripts/NPCController.cs
2025-08-06 23:18:38 +01:00

170 lines
5.5 KiB
C#

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
/// <summary>
/// Called when SightDetector detects a new collider. Assign this to SightDetector's OnNewCollider event.
/// </summary>
public void OnEnemyDetected(Collider detectedCollider)
{
if (detectedCollider == null) return;
var enemy = detectedCollider.GetComponent<EnemyController>();
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}");
}
}
}
/// <summary>
/// Called when SightDetector loses sight of a collider. Assign this to SightDetector's OnLostCollider event.
/// </summary>
public void OnEnemyLost(Collider lostCollider)
{
if (lostCollider == null) return;
var enemy = lostCollider.GetComponent<EnemyController>();
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}");
}
}
/// <summary>
/// Called continuously while SightDetector detects colliders. Assign this to SightDetector's OnDetectCollider event.
/// </summary>
public void OnEnemyInSight(Collider detectedCollider)
{
if (detectedCollider == null) return;
var enemy = detectedCollider.GetComponent<EnemyController>();
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
}
}
}