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 detectedEnemies = new List(); private List detectedAllies = new List(); // Events for unit detection public System.Action OnEnemyDetected; public System.Action OnEnemyLost; public System.Action OnAllyDetected; public System.Action OnAllyLost; private void Awake() { InitializeComponents(); } private void Start() { ConfigureSightDetector(); } private void InitializeComponents() { if (unitController == null) unitController = GetComponent(); if (sightDetector == null) sightDetector = GetComponent(); 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(); 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(); if (targetUnit != null && IsEnemy(targetUnit)) { unitController.OnEnemyInSight(targetUnit); } } } } private void OnTargetLastExit(Collider target) { UnitController targetUnit = target.GetComponent(); 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 GetDetectedEnemies() { // Remove any null references detectedEnemies.RemoveAll(enemy => enemy == null); return new List(detectedEnemies); } public List GetDetectedAllies() { // Remove any null references detectedAllies.RemoveAll(ally => ally == null); return new List(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); } } } }