170 lines
5.5 KiB
C#
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
|
|
}
|
|
}
|
|
}
|
|
|