322 lines
10 KiB
C#
322 lines
10 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using RaycastPro.Detectors;
|
|
|
|
[RequireComponent(typeof(UnitController))]
|
|
[RequireComponent(typeof(SightDetector))]
|
|
public class UnitSightSystem : MonoBehaviour
|
|
{
|
|
[Header("References")]
|
|
public UnitController unitController;
|
|
public SightDetector sightDetector;
|
|
|
|
[Header("Detection Settings")]
|
|
public LayerMask enemyLayerMask = -1;
|
|
public LayerMask allyLayerMask = -1;
|
|
public LayerMask obstacleLayerMask = -1;
|
|
|
|
[Header("Debug")]
|
|
public bool showDebugInfo = false;
|
|
|
|
// Cached detected units
|
|
private List<UnitController> detectedEnemies = new List<UnitController>();
|
|
private List<UnitController> detectedAllies = new List<UnitController>();
|
|
|
|
// Events for unit detection
|
|
public System.Action<UnitController> OnEnemyDetected;
|
|
public System.Action<UnitController> OnEnemyLost;
|
|
public System.Action<UnitController> OnAllyDetected;
|
|
public System.Action<UnitController> OnAllyLost;
|
|
|
|
private void Awake()
|
|
{
|
|
InitializeComponents();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
ConfigureSightDetector();
|
|
}
|
|
|
|
private void InitializeComponents()
|
|
{
|
|
if (unitController == null)
|
|
unitController = GetComponent<UnitController>();
|
|
|
|
if (sightDetector == null)
|
|
sightDetector = GetComponent<SightDetector>();
|
|
|
|
if (sightDetector == null)
|
|
{
|
|
Debug.LogError($"SightDetector component not found on {gameObject.name}. Please add a SightDetector component.");
|
|
return;
|
|
}
|
|
|
|
// Subscribe to SightDetector events
|
|
sightDetector.onNewCollider.AddListener(OnTargetFirstEnter);
|
|
sightDetector.onLostCollider.AddListener(OnTargetLastExit);
|
|
}
|
|
|
|
private void ConfigureSightDetector()
|
|
{
|
|
if (sightDetector == null || unitController == null || unitController.unitData == null)
|
|
return;
|
|
|
|
UnitData unitData = unitController.unitData;
|
|
|
|
if (!unitData.autoConfigureSightDetector)
|
|
return;
|
|
|
|
// Configure SightDetector based on UnitData
|
|
sightDetector.radius = unitData.detectionRange;
|
|
sightDetector.angleX = unitData.sightAngleX;
|
|
sightDetector.angleY = unitData.sightAngleY;
|
|
sightDetector.fullAwareness = unitData.fullAwarenessRadius;
|
|
sightDetector.minRadius = unitData.minDetectionRadius;
|
|
|
|
// Configure pulse settings
|
|
// Note: RaycastPro handles pulse timing internally
|
|
// The usePulseDetection flag is mainly for our reference
|
|
if (unitData.usePulseDetection)
|
|
{
|
|
// Pulse mode is controlled by RaycastPro's internal systems
|
|
// We can trigger manual pulses if needed via TriggerDetectionPulse()
|
|
}
|
|
|
|
// Configure target limits
|
|
sightDetector.Limited = unitData.limitDetectedTargets;
|
|
if (unitData.limitDetectedTargets)
|
|
{
|
|
sightDetector.LimitCount = unitData.maxDetectedTargets;
|
|
}
|
|
|
|
// Set up layer masks
|
|
sightDetector.detectLayer = enemyLayerMask | allyLayerMask;
|
|
sightDetector.blockLayer = obstacleLayerMask;
|
|
|
|
if (showDebugInfo)
|
|
{
|
|
Debug.Log($"Configured SightDetector for {gameObject.name}: " +
|
|
$"Range={sightDetector.radius}, AngleX={sightDetector.angleX}, AngleY={sightDetector.angleY}");
|
|
}
|
|
}
|
|
|
|
private void OnTargetFirstEnter(Collider target)
|
|
{
|
|
UnitController targetUnit = target.GetComponent<UnitController>();
|
|
if (targetUnit == null) return;
|
|
|
|
if (IsEnemy(targetUnit))
|
|
{
|
|
if (!detectedEnemies.Contains(targetUnit))
|
|
{
|
|
detectedEnemies.Add(targetUnit);
|
|
OnEnemyDetected?.Invoke(targetUnit);
|
|
|
|
if (showDebugInfo)
|
|
Debug.Log($"{gameObject.name} detected enemy: {targetUnit.gameObject.name}");
|
|
}
|
|
}
|
|
else if (IsAlly(targetUnit))
|
|
{
|
|
if (!detectedAllies.Contains(targetUnit))
|
|
{
|
|
detectedAllies.Add(targetUnit);
|
|
OnAllyDetected?.Invoke(targetUnit);
|
|
|
|
if (showDebugInfo)
|
|
Debug.Log($"{gameObject.name} detected ally: {targetUnit.gameObject.name}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: Continuous tracking is handled through Update method checking DetectedColliders
|
|
// since RaycastPro doesn't have OnStay events
|
|
|
|
private void Update()
|
|
{
|
|
// Continuously update unit controller with current targets in sight
|
|
if (sightDetector != null && unitController != null)
|
|
{
|
|
foreach (Collider detectedCollider in sightDetector.DetectedColliders)
|
|
{
|
|
UnitController targetUnit = detectedCollider.GetComponent<UnitController>();
|
|
if (targetUnit != null && IsEnemy(targetUnit))
|
|
{
|
|
unitController.OnEnemyInSight(targetUnit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnTargetLastExit(Collider target)
|
|
{
|
|
UnitController targetUnit = target.GetComponent<UnitController>();
|
|
if (targetUnit == null) return;
|
|
|
|
if (IsEnemy(targetUnit) && detectedEnemies.Contains(targetUnit))
|
|
{
|
|
detectedEnemies.Remove(targetUnit);
|
|
OnEnemyLost?.Invoke(targetUnit);
|
|
|
|
if (showDebugInfo)
|
|
Debug.Log($"{gameObject.name} lost sight of enemy: {targetUnit.gameObject.name}");
|
|
}
|
|
else if (IsAlly(targetUnit) && detectedAllies.Contains(targetUnit))
|
|
{
|
|
detectedAllies.Remove(targetUnit);
|
|
OnAllyLost?.Invoke(targetUnit);
|
|
|
|
if (showDebugInfo)
|
|
Debug.Log($"{gameObject.name} lost sight of ally: {targetUnit.gameObject.name}");
|
|
}
|
|
}
|
|
|
|
private bool IsEnemy(UnitController targetUnit)
|
|
{
|
|
return targetUnit.team != unitController.team;
|
|
}
|
|
|
|
private bool IsAlly(UnitController targetUnit)
|
|
{
|
|
return targetUnit.team == unitController.team && targetUnit != unitController;
|
|
}
|
|
|
|
// Public methods for UnitController to access
|
|
public List<UnitController> GetDetectedEnemies()
|
|
{
|
|
// Remove any null references
|
|
detectedEnemies.RemoveAll(enemy => enemy == null);
|
|
return new List<UnitController>(detectedEnemies);
|
|
}
|
|
|
|
public List<UnitController> GetDetectedAllies()
|
|
{
|
|
// Remove any null references
|
|
detectedAllies.RemoveAll(ally => ally == null);
|
|
return new List<UnitController>(detectedAllies);
|
|
}
|
|
|
|
public UnitController GetClosestEnemy()
|
|
{
|
|
UnitController closest = null;
|
|
float closestDistance = float.MaxValue;
|
|
|
|
foreach (UnitController enemy in detectedEnemies)
|
|
{
|
|
if (enemy == null || enemy.currentHealth <= 0) continue;
|
|
|
|
float distance = Vector3.Distance(transform.position, enemy.transform.position);
|
|
if (distance < closestDistance)
|
|
{
|
|
closest = enemy;
|
|
closestDistance = distance;
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
public UnitController GetClosestAlly()
|
|
{
|
|
UnitController closest = null;
|
|
float closestDistance = float.MaxValue;
|
|
|
|
foreach (UnitController ally in detectedAllies)
|
|
{
|
|
if (ally == null || ally.currentHealth <= 0) continue;
|
|
|
|
float distance = Vector3.Distance(transform.position, ally.transform.position);
|
|
if (distance < closestDistance)
|
|
{
|
|
closest = ally;
|
|
closestDistance = distance;
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
public bool CanSeeTarget(Transform target)
|
|
{
|
|
if (sightDetector == null || target == null) return false;
|
|
|
|
// Check if target is in the detected colliders list
|
|
foreach (Collider detectedCollider in sightDetector.DetectedColliders)
|
|
{
|
|
if (detectedCollider != null && detectedCollider.transform == target)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool IsTargetInAttackRange(Transform target)
|
|
{
|
|
if (target == null || unitController == null || unitController.unitData == null)
|
|
return false;
|
|
|
|
float distance = Vector3.Distance(transform.position, target.position);
|
|
return distance <= unitController.unitData.attackRange;
|
|
}
|
|
|
|
// Update sight detector configuration at runtime
|
|
public void UpdateSightConfiguration()
|
|
{
|
|
ConfigureSightDetector();
|
|
}
|
|
|
|
// Manual pulse trigger for performance optimization
|
|
public void TriggerDetectionPulse()
|
|
{
|
|
if (sightDetector != null)
|
|
{
|
|
// The OnPulse method is part of IPulse interface
|
|
// It's typically called automatically by RaycastPro, but can be triggered manually
|
|
sightDetector.OnPulse();
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
// Unsubscribe from events
|
|
if (sightDetector != null)
|
|
{
|
|
sightDetector.onNewCollider.RemoveListener(OnTargetFirstEnter);
|
|
sightDetector.onLostCollider.RemoveListener(OnTargetLastExit);
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
if (!showDebugInfo) return;
|
|
|
|
// Draw attack range
|
|
if (unitController != null && unitController.unitData != null)
|
|
{
|
|
Gizmos.color = Color.red;
|
|
Gizmos.DrawWireSphere(transform.position, unitController.unitData.attackRange);
|
|
}
|
|
|
|
// Draw connections to detected enemies
|
|
Gizmos.color = Color.red;
|
|
foreach (UnitController enemy in detectedEnemies)
|
|
{
|
|
if (enemy != null)
|
|
{
|
|
Gizmos.DrawLine(transform.position, enemy.transform.position);
|
|
}
|
|
}
|
|
|
|
// Draw connections to detected allies
|
|
Gizmos.color = Color.green;
|
|
foreach (UnitController ally in detectedAllies)
|
|
{
|
|
if (ally != null)
|
|
{
|
|
Gizmos.DrawLine(transform.position, ally.transform.position);
|
|
}
|
|
}
|
|
}
|
|
}
|