From 6ecf799d1896cd3326efaf7d1869a1b2069b1fb6 Mon Sep 17 00:00:00 2001 From: dal4segno Date: Wed, 25 Feb 2026 20:50:34 +0900 Subject: [PATCH] =?UTF-8?q?=ED=81=AC=EB=A6=BD=20=EC=BA=A0=ED=94=84=20?= =?UTF-8?q?=EB=82=B4=20=EB=AA=A8=EB=93=A0=20=ED=81=AC=EB=A6=BD=EC=9D=B4=20?= =?UTF-8?q?=ED=95=9C=EB=B2=88=EC=97=90=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EB=A5=BC=20=EC=9D=B8=EC=8B=9D=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/CreepCamp.cs | 45 ++++++++++++++++++++++++++++ Assets/Scripts/EnemyAIController.cs | 46 +++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/Assets/Scripts/CreepCamp.cs b/Assets/Scripts/CreepCamp.cs index 8d7532a..28180b3 100644 --- a/Assets/Scripts/CreepCamp.cs +++ b/Assets/Scripts/CreepCamp.cs @@ -28,6 +28,11 @@ namespace Northbound private ResourcePickup _resourcePickup; private readonly Dictionary> _deathHandlers = new Dictionary>(); + // 중복 경고 방지용 + private GameObject _lastAlertTarget; + private float _lastAlertTime; + private const float ALERT_COOLDOWN = 0.5f; + public override void OnNetworkSpawn() { if (IsServer) @@ -224,6 +229,39 @@ namespace Northbound } } + /// + /// 캠프 내 모든 크립에게 경고 전파 + /// + /// 감지된 타겟 + /// 경고를 보낸 크립 (자신에게는 다시 보내지 않음) + public void AlertAllCreeps(GameObject target, EnemyAIController alertingCreep) + { + if (!IsServer) return; + if (target == null) return; + + // 중복 경고 방지 (짧은 시간 내 같은 타겟에 대한 경고 무시) + if (_lastAlertTarget == target && Time.time - _lastAlertTime < ALERT_COOLDOWN) + { + return; + } + _lastAlertTarget = target; + _lastAlertTime = Time.time; + + // 캠프 내 모든 크립에게 경고 전파 + foreach (var enemyUnit in _spawnedCreeps) + { + if (enemyUnit == null) continue; + + EnemyAIController aiController = enemyUnit.GetComponent(); + if (aiController == null) continue; + + // 경고를 보낸 크립은 제외 (이미 타겟을 설정했음) + if (aiController == alertingCreep) continue; + + aiController.ReceiveAlert(target); + } + } + [Rpc(SendTo.ClientsAndHost)] private void EnableResourcePickupClientRpc() { @@ -286,6 +324,13 @@ namespace Northbound enemyUnit.OnDeath += handler; } + // EnemyAIController에 캠프 참조 설정 (경고 전파 시스템용) + EnemyAIController aiController = creep.GetComponent(); + if (aiController != null) + { + aiController.creepCamp = this; + } + networkObj.SpawnWithOwnership(NetworkManager.Singleton.LocalClientId); } diff --git a/Assets/Scripts/EnemyAIController.cs b/Assets/Scripts/EnemyAIController.cs index 08df0c6..f5a5b62 100644 --- a/Assets/Scripts/EnemyAIController.cs +++ b/Assets/Scripts/EnemyAIController.cs @@ -61,6 +61,10 @@ namespace Northbound [Header("Events")] public System.Action OnAttackPerformed; + [Header("Camp Alert System")] + [Tooltip("이 크립이 속한 캠프 (null이면 독립 크립)")] + public CreepCamp creepCamp; + private NavMeshAgent _agent; private EnemyUnit _enemyUnit; private Core _core; @@ -164,6 +168,13 @@ namespace Northbound if (player != null) { SetTargetPlayer(player); + + // 캠프 내 모든 크립에게 경고 전파 + if (creepCamp != null) + { + creepCamp.AlertAllCreeps(player, this); + } + TransitionToState(EnemyAIState.ChasePlayer); } } @@ -189,6 +200,13 @@ namespace Northbound if (detectedPlayer != null) { SetTargetPlayer(detectedPlayer); + + // 캠프 내 모든 크립에게 경고 전파 + if (creepCamp != null) + { + creepCamp.AlertAllCreeps(detectedPlayer, this); + } + TransitionToState(EnemyAIState.ChasePlayer); return; } @@ -723,6 +741,34 @@ namespace Northbound return _currentState.Value; } + /// + /// 캠프 내 다른 크립으로부터 경고 전파 받음 + /// + public void ReceiveAlert(GameObject target) + { + if (!IsServer) return; + if (target == null) return; + + // 이미 해당 타겟을 추적 중이면 무시 + if (_currentState.Value == EnemyAIState.ChasePlayer || _currentState.Value == EnemyAIState.Attack) + { + GameObject currentTarget = GetTargetPlayer(); + if (currentTarget == target) return; + } + + // 이미 사망한 타겟은 무시 + IDamageable damageable = target.GetComponentInParent(); + if (damageable != null && damageable.IsDead()) return; + + // 타겟 설정 및 추적 시작 + SetTargetPlayer(target); + + if (_currentState.Value == EnemyAIState.Idle || _currentState.Value == EnemyAIState.MoveToCore) + { + TransitionToState(EnemyAIState.ChasePlayer); + } + } + #endregion #region Gizmos (기존 코드 유지)