using Unity.Netcode; using UnityEngine; namespace Northbound { /// /// 액션 - 공격 (팀 시스템 + 장비 시스템 적용) /// public class AttackAction : NetworkBehaviour, IAction { [Header("Attack Settings")] public float attackRange = 2f; public int attackDamage = 10; public float attackCooldown = 0.5f; public LayerMask attackableLayer = ~0; [Header("Animation")] public string attackAnimationTrigger = "Attack"; public bool useAnimationEvents = true; public bool blockDuringAnimation = true; [Header("Equipment")] public bool useEquipment = true; public EquipmentData equipmentData; [Header("Visual")] public GameObject attackEffectPrefab; public Transform attackPoint; private float _lastAttackTime; private Animator _animator; private ITeamMember _teamMember; private EquipmentSocket _equipmentSocket; private bool _isAttacking = false; private bool _isWeaponEquipped = false; private void Awake() { _animator = GetComponent(); _teamMember = GetComponent(); _equipmentSocket = GetComponent(); } public bool CanExecute(ulong playerId) { if (blockDuringAnimation && _isAttacking) return false; return Time.time - _lastAttackTime >= attackCooldown; } public void Execute(ulong playerId) { if (!CanExecute(playerId)) return; _lastAttackTime = Time.time; _isAttacking = true; // 장비 장착 (애니메이션 이벤트 사용 안 할 경우) if (!useAnimationEvents && useEquipment && !_isWeaponEquipped) { if (equipmentData != null && equipmentData.attachOnStart) { AttachWeapon(); } } // 애니메이션 재생 PlayAttackAnimation(); // 애니메이션이 없으면 즉시 공격 실행 if (_animator == null || string.IsNullOrEmpty(attackAnimationTrigger)) { PerformAttack(); _isAttacking = false; } } private void PerformAttack() { Vector3 attackOrigin = attackPoint != null ? attackPoint.position : transform.position; Collider[] hits = Physics.OverlapSphere(attackOrigin, attackRange, attackableLayer); foreach (Collider hit in hits) { if (hit.transform.root == transform.root) continue; var targetDamageable = hit.GetComponent(); var targetTeamMember = hit.GetComponent(); if (targetDamageable != null) { if (_teamMember != null && targetTeamMember != null) { if (!TeamManager.CanAttack(_teamMember, targetTeamMember)) { continue; } } var netObj = hit.GetComponent(); if (netObj != null) { AttackServerRpc(NetworkObjectId, netObj.NetworkObjectId); } } } } [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] private void AttackServerRpc(ulong attackerNetworkId, ulong targetNetworkId) { if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(targetNetworkId, out NetworkObject targetObj)) { var damageable = targetObj.GetComponent(); damageable?.TakeDamage(attackDamage, attackerNetworkId); } } private void PlayAttackAnimation() { if (_animator != null && !string.IsNullOrEmpty(attackAnimationTrigger)) { _animator.SetTrigger(attackAnimationTrigger); } if (attackEffectPrefab != null && attackPoint != null) { GameObject effect = Instantiate(attackEffectPrefab, attackPoint.position, attackPoint.rotation); Destroy(effect, 1f); } } // ======================================== // Animation Event 함수들 // ======================================== public void OnEquipWeapon() { if (!useAnimationEvents || !useEquipment) return; AttachWeapon(); } public void OnEquipWeapon(string socketName) { if (!useAnimationEvents || !useEquipment) return; AttachWeapon(socketName); } public void OnUnequipWeapon() { if (!useAnimationEvents || !useEquipment) return; DetachWeapon(); } public void OnUnequipWeapon(string socketName) { if (!useAnimationEvents || !useEquipment) return; DetachWeapon(socketName); } public void OnAttackHit() { PerformAttack(); } public void OnAttackComplete() { _isAttacking = false; if (useEquipment && equipmentData != null && equipmentData.detachOnEnd && !equipmentData.keepEquipped) { DetachWeapon(); } Debug.Log("[AttackAction] 공격 완료"); } // ======================================== // 장비 관리 함수들 // ======================================== private void AttachWeapon(string socketName = null) { if (_equipmentSocket == null || equipmentData == null) { Debug.LogWarning("[AttackAction] EquipmentSocket 또는 EquipmentData가 없습니다."); return; } if (equipmentData.equipmentPrefab == null) { Debug.LogWarning("[AttackAction] 무기 프리팹이 설정되지 않았습니다."); return; } string socket = socketName ?? equipmentData.socketName; _equipmentSocket.AttachToSocket(socket, equipmentData.equipmentPrefab); _isWeaponEquipped = true; Debug.Log($"[AttackAction] 무기 장착: {socket}"); } private void DetachWeapon(string socketName = null) { if (_equipmentSocket == null) return; string socket = socketName ?? equipmentData?.socketName; if (!string.IsNullOrEmpty(socket)) { _equipmentSocket.DetachFromSocket(socket); _isWeaponEquipped = false; Debug.Log($"[AttackAction] 무기 해제: {socket}"); } } public void EquipWeapon() { if (!_isWeaponEquipped && useEquipment) { AttachWeapon(); } } public void UnequipWeapon() { if (_isWeaponEquipped) { DetachWeapon(); } } public string GetActionName() { return "Attack"; } public string GetActionAnimation() { return attackAnimationTrigger; } public EquipmentData GetEquipmentData() { return equipmentData; } private void OnDrawGizmosSelected() { Vector3 attackOrigin = attackPoint != null ? attackPoint.position : transform.position; Gizmos.color = Color.red; Gizmos.DrawWireSphere(attackOrigin, attackRange); } public override void OnDestroy() { // 무기 정리 if (_isWeaponEquipped) { DetachWeapon(); } base.OnDestroy(); } public bool IsAttacking => _isAttacking; } }