플레이어/적/몬스터 팀 시스템 생성

몬스터 및 적 AI 구현
This commit is contained in:
2026-01-27 15:30:02 +09:00
parent 9a47af4317
commit 194845a9e1
33 changed files with 2519 additions and 445 deletions

View File

@@ -0,0 +1,177 @@
using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
/// <summary>
/// 자동으로 적을 탐지하고 공격하는 시스템
/// </summary>
public class AutoTargetSystem : NetworkBehaviour
{
[Header("Targeting")]
[Tooltip("적을 감지하는 범위")]
public float detectionRange = 15f;
[Tooltip("공격 가능한 범위")]
public float attackRange = 10f;
[Tooltip("공격 간격 (초)")]
public float attackInterval = 1f;
[Tooltip("탐지할 레이어")]
public LayerMask targetLayer = ~0;
[Header("Combat")]
[Tooltip("공격 데미지")]
public int attackDamage = 10;
[Header("Debug")]
[Tooltip("디버그 정보 표시")]
public bool showDebugInfo = true;
private ITeamMember _teamMember;
private float _lastAttackTime;
private void Awake()
{
_teamMember = GetComponent<ITeamMember>();
if (_teamMember == null)
{
Debug.LogError($"<color=red>[AutoTargetSystem] {gameObject.name}에 ITeamMember 컴포넌트가 없습니다!</color>");
}
}
private void Update()
{
if (!IsServer) return;
if (_teamMember == null) return;
if (Time.time - _lastAttackTime >= attackInterval)
{
FindAndAttackEnemy();
}
}
private void FindAndAttackEnemy()
{
// 범위 내 모든 콜라이더 탐지
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)
{
damageable.TakeDamage(attackDamage, NetworkObjectId);
_lastAttackTime = Time.time;
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>");
}
}
private void OnDrawGizmos()
{
// 탐지 범위 (노란색)
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, detectionRange);
// 공격 범위 (빨간색)
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
}
private void OnDrawGizmosSelected()
{
OnDrawGizmos();
#if UNITY_EDITOR
if (_teamMember != null && Application.isPlaying)
{
UnityEditor.Handles.Label(transform.position + Vector3.up * 3f,
$"Auto Target\nTeam: {TeamManager.GetTeamName(_teamMember.GetTeam())}\nDetection: {detectionRange}m\nAttack: {attackRange}m");
}
#endif
}
}
}