액션 및 인터랙션 정의 및 기존 인터랙션 및 채광 코드 구조 개선
This commit is contained in:
@@ -2,6 +2,5 @@ using UnityEngine;
|
||||
|
||||
public interface IInteractable
|
||||
{
|
||||
// 누가 상호작용을 시도했는지 알려주기 위해 GameObject를 인자로 받습니다.
|
||||
void Interact(GameObject user);
|
||||
void Interact(GameObject interactor);
|
||||
}
|
||||
13
Assets/Scripts/Player/IActionProvider.cs
Normal file
13
Assets/Scripts/Player/IActionProvider.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
[System.Serializable]
|
||||
public class ActionDescriptor
|
||||
{
|
||||
public float duration = 0.5f;
|
||||
public string animTrigger = "Interact";
|
||||
// 필요하다면 여기에 사운드 이펙트나 파티클 정보를 추가할 수 있습니다.
|
||||
}
|
||||
|
||||
// 명세를 제공하는 인터페이스
|
||||
public interface IActionProvider
|
||||
{
|
||||
ActionDescriptor GetActionDescriptor();
|
||||
}
|
||||
2
Assets/Scripts/Player/IActionProvider.cs.meta
Normal file
2
Assets/Scripts/Player/IActionProvider.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 142278604af401248b9afd92554cfb0c
|
||||
17
Assets/Scripts/Player/MiningActionData.cs
Normal file
17
Assets/Scripts/Player/MiningActionData.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(menuName = "Actions/Mining")]
|
||||
public class MiningActionData : PlayerActionData
|
||||
{
|
||||
public int damage = 50;
|
||||
|
||||
public override void ExecuteEffect(GameObject performer, GameObject target)
|
||||
{
|
||||
if (target.TryGetComponent<MineableBlock>(out var block))
|
||||
{
|
||||
// 서버 RPC 호출은 블록 내부의 로직을 그대로 사용합니다.
|
||||
block.TakeDamageRpc(damage);
|
||||
block.PlayHitEffectClientRpc();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player/MiningActionData.cs.meta
Normal file
2
Assets/Scripts/Player/MiningActionData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16b4dd78bd34c8e4885c9b160e4c25a2
|
||||
11
Assets/Scripts/Player/PlayerActionData.cs
Normal file
11
Assets/Scripts/Player/PlayerActionData.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
public abstract class PlayerActionData : ScriptableObject
|
||||
{
|
||||
public string actionName;
|
||||
public float duration; // 액션 자체가 시간을 가짐
|
||||
public string animTrigger;
|
||||
|
||||
// 대상(target)은 있을 수도 있고(채광), 없을 수도 있음(회복/대쉬)
|
||||
public abstract void ExecuteEffect(GameObject performer, GameObject target);
|
||||
}
|
||||
2
Assets/Scripts/Player/PlayerActionData.cs.meta
Normal file
2
Assets/Scripts/Player/PlayerActionData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 510ffea359d2cc448924f064aa29ace8
|
||||
54
Assets/Scripts/Player/PlayerActionHandler.cs
Normal file
54
Assets/Scripts/Player/PlayerActionHandler.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
public class PlayerActionHandler : NetworkBehaviour
|
||||
{
|
||||
private bool _isBusy;
|
||||
public bool IsBusy => _isBusy;
|
||||
private Animator _animator;
|
||||
|
||||
void Awake() => _animator = GetComponent<Animator>();
|
||||
|
||||
// [통로 1] 인터랙션 실행 (대상 중심)
|
||||
public void PerformInteraction(IInteractable target)
|
||||
{
|
||||
if (_isBusy || target == null) return;
|
||||
|
||||
// 대상으로부터 정보를 가져옴
|
||||
var provider = (target as MonoBehaviour).GetComponent<IActionProvider>();
|
||||
ActionDescriptor desc = provider?.GetActionDescriptor();
|
||||
|
||||
StartCoroutine(InteractionRoutine(desc, target));
|
||||
}
|
||||
|
||||
// [통로 2] 액션 실행 (행동 중심)
|
||||
public void PerformAction(PlayerActionData actionData, GameObject target = null)
|
||||
{
|
||||
if (_isBusy || actionData == null) return;
|
||||
|
||||
StartCoroutine(ActionRoutine(actionData, target));
|
||||
}
|
||||
|
||||
private IEnumerator InteractionRoutine(ActionDescriptor desc, IInteractable target)
|
||||
{
|
||||
_isBusy = true;
|
||||
if (desc != null) _animator.SetTrigger(desc.animTrigger);
|
||||
|
||||
target.Interact(gameObject); // 로직 실행
|
||||
|
||||
yield return new WaitForSeconds(desc?.duration ?? 0.1f);
|
||||
_isBusy = false;
|
||||
}
|
||||
|
||||
private IEnumerator ActionRoutine(PlayerActionData data, GameObject target)
|
||||
{
|
||||
_isBusy = true;
|
||||
_animator.SetTrigger(data.animTrigger);
|
||||
|
||||
data.ExecuteEffect(gameObject, target); // 로직 실행
|
||||
|
||||
yield return new WaitForSeconds(data.duration);
|
||||
_isBusy = false;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player/PlayerActionHandler.cs.meta
Normal file
2
Assets/Scripts/Player/PlayerActionHandler.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59175e0893d83ae4198b22adcdbd6de5
|
||||
@@ -31,7 +31,6 @@ public class PlayerMovement : MonoBehaviour
|
||||
_inputActions = new PlayerInputActions();
|
||||
|
||||
_inputActions.Player.Jump.performed += ctx => OnJump();
|
||||
_inputActions.Player.Attack.performed += ctx => OnAttack();
|
||||
_inputActions.Player.Interact.performed += ctx => OnInteract();
|
||||
}
|
||||
|
||||
@@ -104,12 +103,6 @@ public class PlayerMovement : MonoBehaviour
|
||||
if (_isGrounded) _velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
||||
}
|
||||
|
||||
private void OnAttack()
|
||||
{
|
||||
if (_traveler != null && _traveler.IsTraveling) return;
|
||||
_animator.SetTrigger("Attack");
|
||||
}
|
||||
|
||||
private void OnInteract()
|
||||
{
|
||||
// 터널 이동 중에는 상호작용 금지
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unity.Netcode;
|
||||
using Unity.Netcode.Components;
|
||||
using System;
|
||||
using Unity.Cinemachine;
|
||||
using System.Collections;
|
||||
|
||||
[RequireComponent(typeof(NetworkObject))]
|
||||
public class PlayerNetworkController : NetworkBehaviour
|
||||
@@ -39,6 +40,10 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
[SerializeField] private float visionRadius = 5f; // 시야 반경
|
||||
private float _lastRevealTime;
|
||||
|
||||
[Header("Action System")]
|
||||
[SerializeField] private PlayerActionData miningAction; // 채광에 대한 시간/애니메이션/효과 데이터
|
||||
private PlayerActionHandler _actionHandler; // 새 핸들러 연결[Header("Action Data")]
|
||||
|
||||
private RectTransform _crosshairRect;
|
||||
private MineableBlock _currentTargetBlock; // 현재 강조 중인 블록 저장
|
||||
private MineableBlock _lastHighlightedBlock;
|
||||
@@ -54,6 +59,12 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
private bool _isGrounded;
|
||||
private bool _isHoldingInteract = false;
|
||||
|
||||
// 현재 플레이어가 어떤 행동을 하고 있는지 나타내는 상태
|
||||
public enum ActionState { Idle, Busy }
|
||||
private ActionState _currentState = ActionState.Idle;
|
||||
|
||||
public bool IsBusy => _currentState == ActionState.Busy;
|
||||
|
||||
// 디버그 변수
|
||||
private Vector3 _debugOrigin;
|
||||
private Vector3 _debugDir;
|
||||
@@ -89,7 +100,7 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
|
||||
_inputActions = new PlayerInputActions();
|
||||
_inputActions.Player.Jump.performed += ctx => OnJump();
|
||||
_inputActions.Player.Attack.performed += ctx => OnAttackInput();
|
||||
_inputActions.Player.Action.performed += ctx => OnActionInput();
|
||||
_inputActions.Player.Interact.performed += ctx => OnInteractTap(); // 탭 상호작용
|
||||
|
||||
_inputActions.Player.Interact.started += ctx => _isHoldingInteract = true;
|
||||
@@ -103,6 +114,7 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
_controller = GetComponent<CharacterController>();
|
||||
_animator = GetComponent<Animator>();
|
||||
_traveler = GetComponent<TunnelTraveler>();
|
||||
_actionHandler = GetComponent<PlayerActionHandler>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
@@ -163,20 +175,26 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
if (_isGrounded) _velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
||||
}
|
||||
|
||||
private void OnAttackInput()
|
||||
// 1. 액션 (좌클릭) - 대상이 없어도 나감
|
||||
private void OnActionInput()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
if (!IsOwner || _actionHandler.IsBusy) return;
|
||||
|
||||
// 현재 하이라이트 중인 블록이 있다면 그 녀석이 바로 공격 대상입니다!
|
||||
if (_lastHighlightedBlock != null)
|
||||
// 조준 중인 블록이 있으면 넘기고, 없으면 null을 넘겨서 실행
|
||||
GameObject target = _lastHighlightedBlock?.gameObject;
|
||||
_actionHandler.PerformAction(miningAction, target);
|
||||
}
|
||||
|
||||
// 2. 인터랙션 (F키) - 대상이 없으면 아예 시작 안 함
|
||||
private void OnInteractTap()
|
||||
{
|
||||
if (!IsOwner || _actionHandler.IsBusy) return;
|
||||
|
||||
IInteractable target = GetClosestInteractable();
|
||||
if (target != null)
|
||||
{
|
||||
if (_lastHighlightedBlock.TryGetComponent<NetworkObject>(out var netObj))
|
||||
{
|
||||
ApplyMiningDamageServerRpc(netObj.NetworkObjectId);
|
||||
}
|
||||
_actionHandler.PerformInteraction(target);
|
||||
}
|
||||
|
||||
_animator.SetTrigger("Attack");
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
@@ -199,38 +217,6 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
[ClientRpc]
|
||||
private void OnAttackClientRpc() => _animator.SetTrigger("Attack");
|
||||
|
||||
private void OnInteractTap()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
_animator.SetTrigger("Interact");
|
||||
|
||||
// 1. 캐릭터 주변 반경 내의 모든 콜라이더 감지
|
||||
Collider[] colliders = Physics.OverlapSphere(transform.position, interactRange, interactableLayer);
|
||||
|
||||
IInteractable closestTarget = null;
|
||||
float minDistance = float.MaxValue;
|
||||
|
||||
foreach (var col in colliders)
|
||||
{
|
||||
// 2. 방향 조건 없이 거리만 체크하여 가장 가까운 것 선택
|
||||
float dist = Vector3.Distance(transform.position, col.transform.position);
|
||||
if (dist < minDistance)
|
||||
{
|
||||
IInteractable interactable = col.GetComponentInParent<IInteractable>();
|
||||
if (interactable != null)
|
||||
{
|
||||
minDistance = dist;
|
||||
closestTarget = interactable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 가장 가까운 대상이 있다면 상호작용 실행
|
||||
if (closestTarget != null)
|
||||
{
|
||||
closestTarget.Interact(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 건설 지원 로직 (범위 내 지속 작업이므로 OverlapSphere 유지 또는 Raycast로 변경 가능)
|
||||
private void PerformConstructionSupport()
|
||||
@@ -402,6 +388,54 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ActionRoutine(float duration, string animTrigger, Action actionLogic)
|
||||
{
|
||||
// 1. 상태 잠금
|
||||
_currentState = ActionState.Busy;
|
||||
|
||||
// 2. 애니메이션 실행
|
||||
if (!string.IsNullOrEmpty(animTrigger))
|
||||
{
|
||||
_animator.SetTrigger(animTrigger);
|
||||
}
|
||||
|
||||
// 3. 실제 로직 실행 (상호작용, 아이템 사용 등)
|
||||
actionLogic?.Invoke();
|
||||
|
||||
// 4. 지정된 시간만큼 대기
|
||||
yield return new WaitForSeconds(duration);
|
||||
|
||||
// 5. 상태 해제
|
||||
_currentState = ActionState.Idle;
|
||||
}
|
||||
|
||||
private IInteractable GetClosestInteractable()
|
||||
{
|
||||
// 1. 지정된 레이어 내의 콜라이더 탐색
|
||||
Collider[] colliders = Physics.OverlapSphere(transform.position, interactRange, interactableLayer);
|
||||
|
||||
IInteractable closest = null;
|
||||
float minDistance = float.MaxValue;
|
||||
|
||||
foreach (var col in colliders)
|
||||
{
|
||||
// 2. IInteractable 인터페이스를 가지고 있는지 확인 (부모 포함)
|
||||
IInteractable interactable = col.GetComponentInParent<IInteractable>();
|
||||
|
||||
if (interactable != null)
|
||||
{
|
||||
float dist = Vector3.Distance(transform.position, col.transform.position);
|
||||
if (dist < minDistance)
|
||||
{
|
||||
minDistance = dist;
|
||||
closest = interactable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!Application.isPlaying || !IsOwner) return;
|
||||
|
||||
Reference in New Issue
Block a user