266 lines
9.8 KiB
C#
266 lines
9.8 KiB
C#
using System.Collections;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
namespace Northbound
|
|
{
|
|
/// <summary>
|
|
/// 자동으로 적을 탐지하고 공격하는 시스템
|
|
/// </summary>
|
|
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<Building>();
|
|
_teamMember = GetComponent<ITeamMember>();
|
|
|
|
if (_building == null)
|
|
{
|
|
Debug.LogError($"<color=red>[AutoTargetSystem] {gameObject.name}에 Building 컴포넌트가 없습니다!</color>");
|
|
}
|
|
|
|
if (_teamMember == null)
|
|
{
|
|
Debug.LogError($"<color=red>[AutoTargetSystem] {gameObject.name}에 ITeamMember 컴포넌트가 없습니다!</color>");
|
|
}
|
|
}
|
|
|
|
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($"<color=cyan>[AutoTarget] {gameObject.name}이(가) {colliders.Length}개의 오브젝트를 감지했습니다.</color>");
|
|
}
|
|
|
|
GameObject closestEnemy = null;
|
|
float closestDistance = float.MaxValue;
|
|
|
|
foreach (Collider col in colliders)
|
|
{
|
|
// 자기 자신 제외
|
|
if (col.transform.root == transform.root)
|
|
continue;
|
|
|
|
// 팀 확인
|
|
ITeamMember targetTeam = col.GetComponent<ITeamMember>();
|
|
|
|
if (targetTeam == null)
|
|
{
|
|
// 부모나 자식에서 찾기
|
|
targetTeam = col.GetComponentInParent<ITeamMember>();
|
|
if (targetTeam == null)
|
|
{
|
|
targetTeam = col.GetComponentInChildren<ITeamMember>();
|
|
}
|
|
}
|
|
|
|
if (targetTeam == null)
|
|
{
|
|
if (showDebugInfo)
|
|
{
|
|
Debug.Log($"<color=yellow>[AutoTarget] {col.gameObject.name}에 ITeamMember가 없습니다.</color>");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// 적대 관계 확인
|
|
bool canAttack = TeamManager.CanAttack(_teamMember, targetTeam);
|
|
|
|
if (showDebugInfo)
|
|
{
|
|
Debug.Log($"<color=yellow>[AutoTarget] {gameObject.name} ({TeamManager.GetTeamName(_teamMember.GetTeam())}) → {col.gameObject.name} ({TeamManager.GetTeamName(targetTeam.GetTeam())}): 공격가능={canAttack}</color>");
|
|
}
|
|
|
|
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<IDamageable>();
|
|
|
|
if (damageable == null)
|
|
{
|
|
damageable = closestEnemy.GetComponentInParent<IDamageable>();
|
|
if (damageable == null)
|
|
{
|
|
damageable = closestEnemy.GetComponentInChildren<IDamageable>();
|
|
}
|
|
}
|
|
|
|
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<ITeamMember>() ??
|
|
closestEnemy.GetComponentInParent<ITeamMember>() ??
|
|
closestEnemy.GetComponentInChildren<ITeamMember>();
|
|
|
|
Debug.Log($"<color=red>[AutoTarget] {gameObject.name} ({TeamManager.GetTeamName(_teamMember.GetTeam())})이(가) {closestEnemy.name} ({TeamManager.GetTeamName(targetTeam?.GetTeam() ?? TeamType.Neutral)})을(를) 공격! (거리: {closestDistance:F2}m, 데미지: {attackDamage})</color>");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"<color=orange>[AutoTarget] {closestEnemy.name}에 IDamageable이 없습니다.</color>");
|
|
}
|
|
}
|
|
else if (showDebugInfo && colliders.Length > 0)
|
|
{
|
|
Debug.Log($"<color=yellow>[AutoTarget] {gameObject.name}이(가) 공격 가능한 적을 찾지 못했습니다.</color>");
|
|
}
|
|
}
|
|
|
|
#region Beam Effect
|
|
|
|
/// <summary>
|
|
/// 빔 효과를 모든 클라이언트에 표시
|
|
/// </summary>
|
|
[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<LineRenderer>();
|
|
}
|
|
else
|
|
{
|
|
// 자동 생성
|
|
GameObject beamObj = new GameObject("AttackBeam");
|
|
beamObj.transform.SetParent(transform);
|
|
_beamRenderer = beamObj.AddComponent<LineRenderer>();
|
|
|
|
// 기본 설정
|
|
_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
|
|
}
|
|
}
|
|
} |