using System.Collections; using Unity.Netcode; using UnityEngine; namespace Northbound { /// /// 자동으로 적을 탐지하고 공격하는 시스템 /// public class AutoTargetSystem : NetworkBehaviour { [Header("Targeting")] [Tooltip("탐지할 레이어")] public LayerMask targetLayer = ~0; [Header("Beam Effect")] [Tooltip("빔 효과 LineRenderer 프리팹 (없으면 자동 생성)")] public LineRenderer beamPrefab; [Tooltip("빔 색상")] public Color beamColor = Color.red; [Tooltip("빔 시작 너비")] public float beamStartWidth = 0.1f; [Tooltip("빔 끝 너비")] public float beamEndWidth = 0.05f; [Tooltip("빔 표시 시간")] public float beamDuration = 0.15f; [Tooltip("빔 발사 위치 (null이면 건물 중심)")] public Transform firePoint; [Header("Debug")] [Tooltip("디버그 정보 표시")] public bool showDebugInfo = true; private Building _building; private ITeamMember _teamMember; private float _lastAttackTime; private LineRenderer _beamRenderer; private Coroutine _beamCoroutine; private void Awake() { _building = GetComponent(); _teamMember = GetComponent(); if (_building == null) { Debug.LogError($"[AutoTargetSystem] {gameObject.name}에 Building 컴포넌트가 없습니다!"); } if (_teamMember == null) { Debug.LogError($"[AutoTargetSystem] {gameObject.name}에 ITeamMember 컴포넌트가 없습니다!"); } } private void Update() { if (!IsServer) return; if (_building == null || _teamMember == null) return; if (Time.time - _lastAttackTime >= _building.buildingData.atkIntervalSec) { FindAndAttackEnemy(); } } private void FindAndAttackEnemy() { float detectionRange = _building.buildingData.atkRange; float attackRange = _building.buildingData.atkRange; int attackDamage = _building.buildingData.atkDamage; // 범위 내 모든 콜라이더 탐지 Collider[] colliders = Physics.OverlapSphere(transform.position, detectionRange, targetLayer); if (showDebugInfo && colliders.Length > 0) { Debug.Log($"[AutoTarget] {gameObject.name}이(가) {colliders.Length}개의 오브젝트를 감지했습니다."); } GameObject closestEnemy = null; float closestDistance = float.MaxValue; foreach (Collider col in colliders) { // 자기 자신 제외 if (col.transform.root == transform.root) continue; // 팀 확인 ITeamMember targetTeam = col.GetComponent(); if (targetTeam == null) { // 부모나 자식에서 찾기 targetTeam = col.GetComponentInParent(); if (targetTeam == null) { targetTeam = col.GetComponentInChildren(); } } if (targetTeam == null) { if (showDebugInfo) { Debug.Log($"[AutoTarget] {col.gameObject.name}에 ITeamMember가 없습니다."); } continue; } // 적대 관계 확인 bool canAttack = TeamManager.CanAttack(_teamMember, targetTeam); if (showDebugInfo) { Debug.Log($"[AutoTarget] {gameObject.name} ({TeamManager.GetTeamName(_teamMember.GetTeam())}) → {col.gameObject.name} ({TeamManager.GetTeamName(targetTeam.GetTeam())}): 공격가능={canAttack}"); } if (!canAttack) continue; // 가장 가까운 적 찾기 float distance = Vector3.Distance(transform.position, col.transform.position); if (distance < closestDistance && distance <= attackRange) { closestDistance = distance; closestEnemy = col.gameObject; } } // 공격 if (closestEnemy != null) { IDamageable damageable = closestEnemy.GetComponent(); if (damageable == null) { damageable = closestEnemy.GetComponentInParent(); if (damageable == null) { damageable = closestEnemy.GetComponentInChildren(); } } if (damageable != null) { // 빔 시작점 계산 Vector3 beamStart = firePoint != null ? firePoint.position : transform.position + Vector3.up * 2f; Vector3 beamEnd = closestEnemy.transform.position + Vector3.up * 1f; // 타겟 중앙 damageable.TakeDamage(attackDamage, NetworkObjectId); _lastAttackTime = Time.time; // 모든 클라이언트에 빔 효과 표시 ShowAttackBeamClientRpc(beamStart, beamEnd); var targetTeam = closestEnemy.GetComponent() ?? closestEnemy.GetComponentInParent() ?? closestEnemy.GetComponentInChildren(); Debug.Log($"[AutoTarget] {gameObject.name} ({TeamManager.GetTeamName(_teamMember.GetTeam())})이(가) {closestEnemy.name} ({TeamManager.GetTeamName(targetTeam?.GetTeam() ?? TeamType.Neutral)})을(를) 공격! (거리: {closestDistance:F2}m, 데미지: {attackDamage})"); } else { Debug.LogWarning($"[AutoTarget] {closestEnemy.name}에 IDamageable이 없습니다."); } } else if (showDebugInfo && colliders.Length > 0) { Debug.Log($"[AutoTarget] {gameObject.name}이(가) 공격 가능한 적을 찾지 못했습니다."); } } #region Beam Effect /// /// 빔 효과를 모든 클라이언트에 표시 /// [ClientRpc] private void ShowAttackBeamClientRpc(Vector3 start, Vector3 end) { if (_beamCoroutine != null) { StopCoroutine(_beamCoroutine); } _beamCoroutine = StartCoroutine(ShowBeamCoroutine(start, end)); } private IEnumerator ShowBeamCoroutine(Vector3 start, Vector3 end) { // LineRenderer 초기화 if (_beamRenderer == null) { InitializeBeamRenderer(); } if (_beamRenderer != null) { _beamRenderer.enabled = true; _beamRenderer.SetPosition(0, start); _beamRenderer.SetPosition(1, end); yield return new WaitForSeconds(beamDuration); _beamRenderer.enabled = false; } } private void InitializeBeamRenderer() { if (beamPrefab != null) { // 프리팹 사용 GameObject beamObj = Instantiate(beamPrefab.gameObject, transform); _beamRenderer = beamObj.GetComponent(); } else { // 자동 생성 GameObject beamObj = new GameObject("AttackBeam"); beamObj.transform.SetParent(transform); _beamRenderer = beamObj.AddComponent(); // 기본 설정 _beamRenderer.positionCount = 2; _beamRenderer.startWidth = beamStartWidth; _beamRenderer.endWidth = beamEndWidth; _beamRenderer.material = new Material(Shader.Find("Sprites/Default")); _beamRenderer.startColor = beamColor; _beamRenderer.endColor = beamColor; _beamRenderer.enabled = false; } } #endregion private void OnDrawGizmos() { if (_building == null || _building.buildingData == null) return; float range = _building.buildingData.atkRange; // 탐지 범위 (노란색) Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, range); // 공격 범위 (빨간색) Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, range); } private void OnDrawGizmosSelected() { OnDrawGizmos(); #if UNITY_EDITOR if (_teamMember != null && _building != null && _building.buildingData != null && Application.isPlaying) { UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, $"Auto Target\nTeam: {TeamManager.GetTeamName(_teamMember.GetTeam())}\nRange: {_building.buildingData.atkRange}m\nDamage: {_building.buildingData.atkDamage}"); } #endif } } }