인터랙션 및 액션 구조 생성
기본 채광 인터랙션 생성 채광 인터랙션을 위한 인터랙션 대상인 광산 생성 Kaykit Resource 애셋 추가
This commit is contained in:
115
Assets/Scripts/AttackAction.cs
Normal file
115
Assets/Scripts/AttackAction.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 액션 - 공격 (대상 없이도 실행 가능)
|
||||
/// </summary>
|
||||
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"; // 공격 애니메이션 트리거
|
||||
|
||||
[Header("Visual")]
|
||||
public GameObject attackEffectPrefab;
|
||||
public Transform attackPoint;
|
||||
|
||||
private float _lastAttackTime;
|
||||
private Animator _animator;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_animator = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
public bool CanExecute(ulong playerId)
|
||||
{
|
||||
return Time.time - _lastAttackTime >= attackCooldown;
|
||||
}
|
||||
|
||||
public void Execute(ulong playerId)
|
||||
{
|
||||
if (!CanExecute(playerId))
|
||||
return;
|
||||
|
||||
_lastAttackTime = Time.time;
|
||||
|
||||
// 애니메이션 재생
|
||||
PlayAttackAnimation();
|
||||
|
||||
// 범위 내 적이 있으면 데미지
|
||||
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 enemy = hit.GetComponent<IDamageable>();
|
||||
if (enemy != null)
|
||||
{
|
||||
var netObj = hit.GetComponent<NetworkObject>();
|
||||
if (netObj != null)
|
||||
{
|
||||
AttackServerRpc(playerId, netObj.NetworkObjectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"플레이어 {playerId} 공격! (적중: {hits.Length}개)");
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
private void AttackServerRpc(ulong playerId, ulong targetNetworkId)
|
||||
{
|
||||
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(targetNetworkId, out NetworkObject targetObj))
|
||||
{
|
||||
var damageable = targetObj.GetComponent<IDamageable>();
|
||||
damageable?.TakeDamage(attackDamage, playerId);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetActionName()
|
||||
{
|
||||
return "Attack";
|
||||
}
|
||||
|
||||
public string GetActionAnimation()
|
||||
{
|
||||
return attackAnimationTrigger;
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Vector3 attackOrigin = attackPoint != null ? attackPoint.position : transform.position;
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireSphere(attackOrigin, attackRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/AttackAction.cs.meta
Normal file
2
Assets/Scripts/AttackAction.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66ec3984614d8a64b8eae821376d038d
|
||||
112
Assets/Scripts/EquipmentSocket.cs
Normal file
112
Assets/Scripts/EquipmentSocket.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어의 장비 소켓 관리 (손, 등, 허리 등)
|
||||
/// </summary>
|
||||
public class EquipmentSocket : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Socket
|
||||
{
|
||||
public string socketName; // "RightHand", "LeftHand", "Back" 등
|
||||
public Transform socketTransform; // 실제 본 Transform
|
||||
[HideInInspector] public GameObject currentEquipment; // 현재 장착된 장비
|
||||
}
|
||||
|
||||
[Header("Available Sockets")]
|
||||
public List<Socket> sockets = new List<Socket>();
|
||||
|
||||
private Dictionary<string, Socket> _socketDict = new Dictionary<string, Socket>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 빠른 검색을 위한 딕셔너리 생성
|
||||
foreach (var socket in sockets)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(socket.socketName))
|
||||
{
|
||||
_socketDict[socket.socketName] = socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소켓에 장비 부착
|
||||
/// </summary>
|
||||
public GameObject AttachToSocket(string socketName, GameObject equipmentPrefab)
|
||||
{
|
||||
if (!_socketDict.TryGetValue(socketName, out Socket socket))
|
||||
{
|
||||
Debug.LogWarning($"소켓을 찾을 수 없습니다: {socketName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (socket.socketTransform == null)
|
||||
{
|
||||
Debug.LogWarning($"소켓 Transform이 없습니다: {socketName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 기존 장비 제거
|
||||
DetachFromSocket(socketName);
|
||||
|
||||
// 새 장비 생성
|
||||
if (equipmentPrefab != null)
|
||||
{
|
||||
GameObject equipment = Instantiate(equipmentPrefab, socket.socketTransform);
|
||||
equipment.transform.localPosition = Vector3.zero;
|
||||
equipment.transform.localRotation = Quaternion.identity;
|
||||
socket.currentEquipment = equipment;
|
||||
|
||||
return equipment;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소켓에서 장비 제거
|
||||
/// </summary>
|
||||
public void DetachFromSocket(string socketName)
|
||||
{
|
||||
if (!_socketDict.TryGetValue(socketName, out Socket socket))
|
||||
return;
|
||||
|
||||
if (socket.currentEquipment != null)
|
||||
{
|
||||
Destroy(socket.currentEquipment);
|
||||
socket.currentEquipment = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 소켓에서 장비 제거
|
||||
/// </summary>
|
||||
public void DetachAll()
|
||||
{
|
||||
foreach (var socket in sockets)
|
||||
{
|
||||
if (socket.currentEquipment != null)
|
||||
{
|
||||
Destroy(socket.currentEquipment);
|
||||
socket.currentEquipment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 소켓에 장비가 있는지 확인
|
||||
/// </summary>
|
||||
public bool HasEquipment(string socketName)
|
||||
{
|
||||
if (_socketDict.TryGetValue(socketName, out Socket socket))
|
||||
{
|
||||
return socket.currentEquipment != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/EquipmentSocket.cs.meta
Normal file
2
Assets/Scripts/EquipmentSocket.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac908541bf903c745a1794d409a5f048
|
||||
28
Assets/Scripts/IAction.cs
Normal file
28
Assets/Scripts/IAction.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 상호작용 대상 없이도 실행 가능한 행동 (공격, 점프 등)
|
||||
/// </summary>
|
||||
public interface IAction
|
||||
{
|
||||
/// <summary>
|
||||
/// 액션 실행 가능한지 여부 (쿨다운, 스테미나 등)
|
||||
/// </summary>
|
||||
bool CanExecute(ulong playerId);
|
||||
|
||||
/// <summary>
|
||||
/// 액션 실행
|
||||
/// </summary>
|
||||
void Execute(ulong playerId);
|
||||
|
||||
/// <summary>
|
||||
/// 액션 이름
|
||||
/// </summary>
|
||||
string GetActionName();
|
||||
|
||||
/// <summary>
|
||||
/// 플레이어가 재생할 애니메이션 트리거 이름 (없으면 null 또는 빈 문자열)
|
||||
/// </summary>
|
||||
string GetActionAnimation();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/IAction.cs.meta
Normal file
2
Assets/Scripts/IAction.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5177351de8fc83742b52f04dc18dd309
|
||||
10
Assets/Scripts/IDamageable.cs
Normal file
10
Assets/Scripts/IDamageable.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 데미지를 받을 수 있는 오브젝트
|
||||
/// </summary>
|
||||
public interface IDamageable
|
||||
{
|
||||
void TakeDamage(int damage, ulong attackerId);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/IDamageable.cs.meta
Normal file
2
Assets/Scripts/IDamageable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e51902811dc8f5046b56d46d872971f6
|
||||
40
Assets/Scripts/IInteractable.cs
Normal file
40
Assets/Scripts/IInteractable.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 상호작용 대상이 반드시 필요한 행동 (채광, 문 열기 등)
|
||||
/// </summary>
|
||||
public interface IInteractable
|
||||
{
|
||||
/// <summary>
|
||||
/// 상호작용 가능한지 여부
|
||||
/// </summary>
|
||||
bool CanInteract(ulong playerId);
|
||||
|
||||
/// <summary>
|
||||
/// 상호작용 실행
|
||||
/// </summary>
|
||||
void Interact(ulong playerId);
|
||||
|
||||
/// <summary>
|
||||
/// 상호작용 UI에 표시될 텍스트 (예: "[E] 채광하기")
|
||||
/// </summary>
|
||||
string GetInteractionPrompt();
|
||||
|
||||
/// <summary>
|
||||
/// 플레이어가 재생할 애니메이션 트리거 이름 (없으면 null 또는 빈 문자열)
|
||||
/// </summary>
|
||||
string GetInteractionAnimation();
|
||||
|
||||
/// <summary>
|
||||
/// 상호작용 시 사용할 장비 정보 (없으면 null)
|
||||
/// </summary>
|
||||
InteractionEquipmentData GetEquipmentData();
|
||||
|
||||
/// <summary>
|
||||
/// 상호작용 오브젝트의 Transform
|
||||
/// </summary>
|
||||
Transform GetTransform();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/IInteractable.cs.meta
Normal file
2
Assets/Scripts/IInteractable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 722ea71942d46274d9590acea8f70015
|
||||
23
Assets/Scripts/InteractionEquipmentData.cs
Normal file
23
Assets/Scripts/InteractionEquipmentData.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 상호작용 시 필요한 장비 정보
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class InteractionEquipmentData
|
||||
{
|
||||
[Tooltip("장비를 부착할 소켓 이름 (예: RightHand, LeftHand)")]
|
||||
public string socketName = "RightHand";
|
||||
|
||||
[Tooltip("부착할 장비 프리팹 (예: 곡괭이, 도끼)")]
|
||||
public GameObject equipmentPrefab;
|
||||
|
||||
[Tooltip("상호작용 시작 시 자동으로 부착")]
|
||||
public bool attachOnStart = true;
|
||||
|
||||
[Tooltip("상호작용 종료 시 자동으로 제거")]
|
||||
public bool detachOnEnd = true;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/InteractionEquipmentData.cs.meta
Normal file
2
Assets/Scripts/InteractionEquipmentData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 883a3042cf05b3b4e9629710b6f4e83f
|
||||
141
Assets/Scripts/Mine.cs
Normal file
141
Assets/Scripts/Mine.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 상호작용 대상 - 광산 (채광하기)
|
||||
/// </summary>
|
||||
public class Mine : NetworkBehaviour, IInteractable
|
||||
{
|
||||
[Header("Mine Settings")]
|
||||
public bool infiniteResources = false; // 무제한 자원
|
||||
public int maxResources = 100;
|
||||
public int resourcesPerMining = 10;
|
||||
public float miningCooldown = 2f;
|
||||
public string resourceName = "광석";
|
||||
|
||||
[Header("Animation")]
|
||||
public string interactionAnimationTrigger = "Mining"; // 플레이어 애니메이션 트리거
|
||||
|
||||
[Header("Equipment")]
|
||||
public InteractionEquipmentData equipmentData = new InteractionEquipmentData
|
||||
{
|
||||
socketName = "RightHand",
|
||||
attachOnStart = true,
|
||||
detachOnEnd = true
|
||||
};
|
||||
|
||||
[Header("Visual")]
|
||||
public GameObject miningEffectPrefab;
|
||||
public Transform effectSpawnPoint;
|
||||
|
||||
private NetworkVariable<int> _currentResources = new NetworkVariable<int>(
|
||||
100,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
private float _lastMiningTime;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
_currentResources.Value = maxResources;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanInteract(ulong playerId)
|
||||
{
|
||||
// 무제한 자원이면 항상 채굴 가능
|
||||
if (infiniteResources)
|
||||
return Time.time - _lastMiningTime >= miningCooldown;
|
||||
|
||||
if (_currentResources.Value <= 0)
|
||||
return false;
|
||||
|
||||
return Time.time - _lastMiningTime >= miningCooldown;
|
||||
}
|
||||
|
||||
public void Interact(ulong playerId)
|
||||
{
|
||||
if (!CanInteract(playerId))
|
||||
return;
|
||||
|
||||
MineResourceServerRpc(playerId);
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
private void MineResourceServerRpc(ulong playerId)
|
||||
{
|
||||
if (!CanInteract(playerId))
|
||||
return;
|
||||
|
||||
int minedAmount = resourcesPerMining;
|
||||
|
||||
// 무제한이 아니면 자원 감소
|
||||
if (!infiniteResources)
|
||||
{
|
||||
minedAmount = Mathf.Min(resourcesPerMining, _currentResources.Value);
|
||||
_currentResources.Value -= minedAmount;
|
||||
}
|
||||
|
||||
_lastMiningTime = Time.time;
|
||||
|
||||
Debug.Log($"플레이어 {playerId}가 {minedAmount} {resourceName}을(를) 채굴했습니다. " +
|
||||
(infiniteResources ? "(무제한)" : $"남은 자원: {_currentResources.Value}"));
|
||||
|
||||
ShowMiningEffectClientRpc();
|
||||
|
||||
if (!infiniteResources && _currentResources.Value <= 0)
|
||||
{
|
||||
OnResourcesDepleted();
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void ShowMiningEffectClientRpc()
|
||||
{
|
||||
if (miningEffectPrefab != null && effectSpawnPoint != null)
|
||||
{
|
||||
GameObject effect = Instantiate(miningEffectPrefab, effectSpawnPoint.position, effectSpawnPoint.rotation);
|
||||
Destroy(effect, 2f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnResourcesDepleted()
|
||||
{
|
||||
Debug.Log("광산이 고갈되었습니다!");
|
||||
}
|
||||
|
||||
public string GetInteractionPrompt()
|
||||
{
|
||||
if (infiniteResources)
|
||||
{
|
||||
return $"[E] {resourceName} 채굴 (무제한)";
|
||||
}
|
||||
|
||||
if (_currentResources.Value > 0)
|
||||
{
|
||||
return $"[E] {resourceName} 채굴 ({_currentResources.Value}/{maxResources})";
|
||||
}
|
||||
return "고갈된 광산";
|
||||
}
|
||||
|
||||
public string GetInteractionAnimation()
|
||||
{
|
||||
return interactionAnimationTrigger;
|
||||
}
|
||||
|
||||
public InteractionEquipmentData GetEquipmentData()
|
||||
{
|
||||
return equipmentData;
|
||||
}
|
||||
|
||||
public Transform GetTransform()
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Mine.cs.meta
Normal file
2
Assets/Scripts/Mine.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e31364bc942ad7e41be627b7a00b206e
|
||||
91
Assets/Scripts/PlayerActionSystem.cs
Normal file
91
Assets/Scripts/PlayerActionSystem.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 상호작용 대상 없이 실행 가능한 액션들을 관리
|
||||
/// </summary>
|
||||
public class PlayerActionSystem : NetworkBehaviour
|
||||
{
|
||||
[Header("Actions")]
|
||||
public List<MonoBehaviour> actionComponents = new List<MonoBehaviour>();
|
||||
|
||||
[Header("Animation")]
|
||||
public bool playAnimations = true;
|
||||
|
||||
private PlayerInputActions _inputActions;
|
||||
private Dictionary<string, IAction> _actions = new Dictionary<string, IAction>();
|
||||
private Animator _animator;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_animator = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
|
||||
// 액션 컴포넌트들을 딕셔너리에 등록
|
||||
foreach (var component in actionComponents)
|
||||
{
|
||||
if (component is IAction action)
|
||||
{
|
||||
_actions[action.GetActionName()] = action;
|
||||
}
|
||||
}
|
||||
|
||||
_inputActions = new PlayerInputActions();
|
||||
_inputActions.Player.Attack.performed += OnAttack;
|
||||
// 다른 액션들도 여기에 바인딩
|
||||
_inputActions.Enable();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
if (IsOwner && _inputActions != null)
|
||||
{
|
||||
_inputActions.Player.Attack.performed -= OnAttack;
|
||||
_inputActions.Disable();
|
||||
_inputActions.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttack(InputAction.CallbackContext context)
|
||||
{
|
||||
ExecuteAction("Attack");
|
||||
}
|
||||
|
||||
public void ExecuteAction(string actionName)
|
||||
{
|
||||
if (_actions.TryGetValue(actionName, out IAction action))
|
||||
{
|
||||
if (action.CanExecute(OwnerClientId))
|
||||
{
|
||||
// 애니메이션 재생 (액션 실행 전)
|
||||
if (playAnimations && _animator != null)
|
||||
{
|
||||
string animTrigger = action.GetActionAnimation();
|
||||
if (!string.IsNullOrEmpty(animTrigger))
|
||||
{
|
||||
_animator.SetTrigger(animTrigger);
|
||||
}
|
||||
}
|
||||
|
||||
action.Execute(OwnerClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_inputActions != null)
|
||||
{
|
||||
_inputActions.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/PlayerActionSystem.cs.meta
Normal file
2
Assets/Scripts/PlayerActionSystem.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f99d8299ac0f91a468d410437a017482
|
||||
246
Assets/Scripts/PlayerInteraction.cs
Normal file
246
Assets/Scripts/PlayerInteraction.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어가 월드의 오브젝트와 상호작용하는 시스템
|
||||
/// </summary>
|
||||
public class PlayerInteraction : NetworkBehaviour
|
||||
{
|
||||
[Header("Interaction Settings")]
|
||||
public float interactionRange = 3f;
|
||||
public LayerMask interactableLayer = ~0;
|
||||
|
||||
[Header("Detection")]
|
||||
public Transform rayOrigin;
|
||||
public bool useForwardDirection = true;
|
||||
|
||||
[Header("Animation")]
|
||||
public bool playAnimations = true;
|
||||
public bool useAnimationEvents = true;
|
||||
public bool blockDuringAnimation = true;
|
||||
|
||||
[Header("Equipment")]
|
||||
public bool useEquipment = true;
|
||||
|
||||
[Header("Debug")]
|
||||
public bool showDebugRay = true;
|
||||
|
||||
private PlayerInputActions _inputActions;
|
||||
private IInteractable _currentInteractable;
|
||||
private Camera _mainCamera;
|
||||
private Animator _animator;
|
||||
private EquipmentSocket _equipmentSocket;
|
||||
|
||||
private InteractionEquipmentData _pendingEquipmentData;
|
||||
private string _currentEquipmentSocket;
|
||||
private bool _isInteracting = false;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
|
||||
_mainCamera = Camera.main;
|
||||
_animator = GetComponent<Animator>();
|
||||
_equipmentSocket = GetComponent<EquipmentSocket>();
|
||||
|
||||
if (rayOrigin == null)
|
||||
rayOrigin = transform;
|
||||
|
||||
_inputActions = new PlayerInputActions();
|
||||
_inputActions.Player.Interact.performed += OnInteract;
|
||||
_inputActions.Enable();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
if (IsOwner && _inputActions != null)
|
||||
{
|
||||
_inputActions.Player.Interact.performed -= OnInteract;
|
||||
_inputActions.Disable();
|
||||
_inputActions.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
DetectInteractable();
|
||||
}
|
||||
|
||||
private void DetectInteractable()
|
||||
{
|
||||
Vector3 origin = rayOrigin.position;
|
||||
Vector3 direction = useForwardDirection ? transform.forward : _mainCamera.transform.forward;
|
||||
|
||||
Ray ray = new Ray(origin, direction);
|
||||
|
||||
if (showDebugRay)
|
||||
{
|
||||
Debug.DrawRay(ray.origin, ray.direction * interactionRange,
|
||||
_currentInteractable != null ? Color.green : Color.yellow);
|
||||
}
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, interactionRange, interactableLayer))
|
||||
{
|
||||
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
|
||||
|
||||
if (interactable == null)
|
||||
interactable = hit.collider.GetComponentInParent<IInteractable>();
|
||||
|
||||
if (interactable != null && interactable.CanInteract(OwnerClientId))
|
||||
{
|
||||
_currentInteractable = interactable;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_currentInteractable = null;
|
||||
}
|
||||
|
||||
private void OnInteract(InputAction.CallbackContext context)
|
||||
{
|
||||
if (blockDuringAnimation && _isInteracting)
|
||||
return;
|
||||
|
||||
if (_currentInteractable != null)
|
||||
{
|
||||
_isInteracting = true;
|
||||
_pendingEquipmentData = _currentInteractable.GetEquipmentData();
|
||||
|
||||
if (!useAnimationEvents && useEquipment && _equipmentSocket != null && _pendingEquipmentData != null)
|
||||
{
|
||||
if (_pendingEquipmentData.attachOnStart && _pendingEquipmentData.equipmentPrefab != null)
|
||||
{
|
||||
AttachEquipment();
|
||||
|
||||
if (_pendingEquipmentData.detachOnEnd)
|
||||
{
|
||||
StartCoroutine(DetachEquipmentAfterDelay(2f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playAnimations && _animator != null)
|
||||
{
|
||||
string animTrigger = _currentInteractable.GetInteractionAnimation();
|
||||
if (!string.IsNullOrEmpty(animTrigger))
|
||||
{
|
||||
_animator.SetTrigger(animTrigger);
|
||||
}
|
||||
}
|
||||
|
||||
_currentInteractable.Interact(OwnerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Animation Event 함수들
|
||||
// ========================================
|
||||
|
||||
public void OnEquipTool()
|
||||
{
|
||||
if (!useAnimationEvents || !useEquipment) return;
|
||||
AttachEquipment();
|
||||
}
|
||||
|
||||
public void OnEquipTool(string socketName)
|
||||
{
|
||||
if (!useAnimationEvents || !useEquipment) return;
|
||||
AttachEquipment(socketName);
|
||||
}
|
||||
|
||||
public void OnUnequipTool()
|
||||
{
|
||||
if (!useAnimationEvents || !useEquipment) return;
|
||||
DetachEquipment();
|
||||
}
|
||||
|
||||
public void OnUnequipTool(string socketName)
|
||||
{
|
||||
if (!useAnimationEvents || !useEquipment) return;
|
||||
DetachEquipment(socketName);
|
||||
}
|
||||
|
||||
public void OnInteractionComplete()
|
||||
{
|
||||
_isInteracting = false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 내부 헬퍼 함수들
|
||||
// ========================================
|
||||
|
||||
private void AttachEquipment(string socketName = null)
|
||||
{
|
||||
if (_equipmentSocket == null || _pendingEquipmentData == null)
|
||||
return;
|
||||
|
||||
if (_pendingEquipmentData.equipmentPrefab == null)
|
||||
return;
|
||||
|
||||
string socket = socketName ?? _pendingEquipmentData.socketName;
|
||||
_equipmentSocket.AttachToSocket(socket, _pendingEquipmentData.equipmentPrefab);
|
||||
_currentEquipmentSocket = socket;
|
||||
}
|
||||
|
||||
private void DetachEquipment(string socketName = null)
|
||||
{
|
||||
if (_equipmentSocket == null)
|
||||
return;
|
||||
|
||||
string socket = socketName ?? _currentEquipmentSocket;
|
||||
|
||||
if (!string.IsNullOrEmpty(socket))
|
||||
{
|
||||
_equipmentSocket.DetachFromSocket(socket);
|
||||
|
||||
if (socket == _currentEquipmentSocket)
|
||||
_currentEquipmentSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator DetachEquipmentAfterDelay(float delay)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
DetachEquipment();
|
||||
|
||||
if (!useAnimationEvents)
|
||||
{
|
||||
_isInteracting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!IsOwner || _currentInteractable == null) return;
|
||||
|
||||
GUIStyle style = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 24,
|
||||
alignment = TextAnchor.MiddleCenter
|
||||
};
|
||||
style.normal.textColor = Color.white;
|
||||
|
||||
string prompt = _currentInteractable.GetInteractionPrompt();
|
||||
|
||||
if (_isInteracting)
|
||||
{
|
||||
prompt += " (진행 중...)";
|
||||
style.normal.textColor = Color.yellow;
|
||||
}
|
||||
|
||||
GUI.Label(new Rect(Screen.width / 2 - 200, Screen.height - 100, 400, 50), prompt, style);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_inputActions != null)
|
||||
{
|
||||
_inputActions.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/PlayerInteraction.cs.meta
Normal file
2
Assets/Scripts/PlayerInteraction.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8af14bc868e5822428f5a0a89b2bbb44
|
||||
Reference in New Issue
Block a user