동굴 수정
This commit is contained in:
57
Assets/02_Scripts/Cave/RaftRideEndHandler.cs
Normal file
57
Assets/02_Scripts/Cave/RaftRideEndHandler.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class RaftRideEndHandler : MonoBehaviour
|
||||
{
|
||||
[Header("Player")]
|
||||
[SerializeField] private Transform xrOrigin;
|
||||
|
||||
[Tooltip("도착 후 XR Origin을 뗏목에서 분리할지 여부")]
|
||||
[SerializeField] private bool detachPlayerFromRaft = true;
|
||||
|
||||
[Header("Exit / Land")]
|
||||
[SerializeField] private Transform exitPoint;
|
||||
|
||||
[Tooltip("도착 후 켤 육지 이동 구역, 안내 오브젝트, 길 표시 등")]
|
||||
[SerializeField] private GameObject landAreaObject;
|
||||
|
||||
[Header("Next Mission")]
|
||||
[Tooltip("도착 후 활성화할 조개 미션 오브젝트")]
|
||||
[SerializeField] private GameObject clamMissionObject;
|
||||
|
||||
[Tooltip("처음에는 꺼두었다가 나중에 기억의 조각 획득 후 켤 문")]
|
||||
[SerializeField] private GameObject nextDoorObject;
|
||||
|
||||
public void OnRaftArrived()
|
||||
{
|
||||
Debug.Log("[RaftRideEndHandler] 뗏목 도착 처리 시작.");
|
||||
|
||||
if (detachPlayerFromRaft && xrOrigin != null)
|
||||
{
|
||||
xrOrigin.SetParent(null);
|
||||
}
|
||||
|
||||
if (exitPoint != null && xrOrigin != null)
|
||||
{
|
||||
// 강제로 이동시키고 싶지 않으면 이 부분은 주석 처리해도 됨.
|
||||
// xrOrigin.position = exitPoint.position;
|
||||
// xrOrigin.rotation = exitPoint.rotation;
|
||||
}
|
||||
|
||||
if (landAreaObject != null)
|
||||
{
|
||||
landAreaObject.SetActive(true);
|
||||
}
|
||||
|
||||
if (clamMissionObject != null)
|
||||
{
|
||||
clamMissionObject.SetActive(true);
|
||||
}
|
||||
|
||||
if (nextDoorObject != null)
|
||||
{
|
||||
nextDoorObject.SetActive(false);
|
||||
}
|
||||
|
||||
Debug.Log("[RaftRideEndHandler] 이제 플레이어가 육지로 이동해 조개 미션을 진행할 수 있습니다.");
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Cave/RaftRideEndHandler.cs.meta
Normal file
2
Assets/02_Scripts/Cave/RaftRideEndHandler.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ab95bcbad7a49c4d9030027474e9c39
|
||||
179
Assets/02_Scripts/Cave/RaftRiverController.cs
Normal file
179
Assets/02_Scripts/Cave/RaftRiverController.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class RaftRiverController : MonoBehaviour
|
||||
{
|
||||
[Header("Path")]
|
||||
[SerializeField] private Transform[] pathPoints;
|
||||
|
||||
[Header("Steering Input")]
|
||||
[SerializeField] private SteeringKeyXR steeringKey;
|
||||
|
||||
[Tooltip("체크하면 키 움직임과 반대로 뗏목이 좌우 이동합니다.")]
|
||||
[SerializeField] private bool reverseControl = true;
|
||||
|
||||
[Header("Move Speed")]
|
||||
[SerializeField] private float forwardSpeed = 5f;
|
||||
[SerializeField] private float turnSpeed = 4f;
|
||||
|
||||
[Header("Side Control")]
|
||||
[SerializeField] private float sideMoveSpeed = 10f;
|
||||
|
||||
[Tooltip("강 중앙선 기준 좌우 이동 가능 범위입니다. 동굴 폭 46이면 14~16 추천.")]
|
||||
[SerializeField] private float maxSideOffset = 16f;
|
||||
|
||||
[Header("Arrival")]
|
||||
[SerializeField] private float pointReachDistance = 1.5f;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent onArrived;
|
||||
|
||||
private int currentPointIndex = 0;
|
||||
private float sideOffset = 0f;
|
||||
|
||||
private Vector3 currentCenterPosition;
|
||||
private Vector3 currentForward;
|
||||
private Vector3 currentRight;
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public bool IsFinished => isFinished;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (pathPoints == null || pathPoints.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[RaftRiverController] Path Points가 비어 있습니다.", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
currentCenterPosition = transform.position;
|
||||
|
||||
// 첫 목표 지점은 0번이 아니라 1번부터 가는 것이 자연스러울 때가 많음.
|
||||
// 단, 0번이 현재 위치와 다르면 그대로 0번부터 이동.
|
||||
currentPointIndex = 0;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (isFinished)
|
||||
return;
|
||||
|
||||
MoveAlongPath();
|
||||
HandleSideControl();
|
||||
ApplyRaftPositionAndRotation();
|
||||
}
|
||||
|
||||
private void MoveAlongPath()
|
||||
{
|
||||
if (currentPointIndex >= pathPoints.Length)
|
||||
{
|
||||
FinishRaftRide();
|
||||
return;
|
||||
}
|
||||
|
||||
Transform targetPoint = pathPoints[currentPointIndex];
|
||||
|
||||
Vector3 toTarget = targetPoint.position - currentCenterPosition;
|
||||
toTarget.y = 0f;
|
||||
|
||||
float distance = toTarget.magnitude;
|
||||
|
||||
if (distance < pointReachDistance)
|
||||
{
|
||||
currentPointIndex++;
|
||||
|
||||
if (currentPointIndex >= pathPoints.Length)
|
||||
{
|
||||
FinishRaftRide();
|
||||
return;
|
||||
}
|
||||
|
||||
targetPoint = pathPoints[currentPointIndex];
|
||||
toTarget = targetPoint.position - currentCenterPosition;
|
||||
toTarget.y = 0f;
|
||||
}
|
||||
|
||||
if (toTarget.sqrMagnitude < 0.001f)
|
||||
return;
|
||||
|
||||
currentForward = toTarget.normalized;
|
||||
currentCenterPosition += currentForward * forwardSpeed * Time.deltaTime;
|
||||
|
||||
// 진행 방향 기준 오른쪽 벡터
|
||||
currentRight = Vector3.Cross(Vector3.up, currentForward).normalized;
|
||||
}
|
||||
|
||||
private void HandleSideControl()
|
||||
{
|
||||
float input = 0f;
|
||||
|
||||
if (steeringKey != null)
|
||||
{
|
||||
input = steeringKey.SteeringValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 키 연결 전 테스트용
|
||||
input = Input.GetAxis("Horizontal");
|
||||
}
|
||||
|
||||
// 반전은 여기서만 처리
|
||||
if (reverseControl)
|
||||
{
|
||||
input *= -1f;
|
||||
}
|
||||
|
||||
sideOffset += input * sideMoveSpeed * Time.deltaTime;
|
||||
sideOffset = Mathf.Clamp(sideOffset, -maxSideOffset, maxSideOffset);
|
||||
}
|
||||
|
||||
private void ApplyRaftPositionAndRotation()
|
||||
{
|
||||
Vector3 finalPosition = currentCenterPosition + currentRight * sideOffset;
|
||||
|
||||
// 현재 뗏목 높이 유지
|
||||
finalPosition.y = transform.position.y;
|
||||
|
||||
transform.position = finalPosition;
|
||||
|
||||
if (currentForward != Vector3.zero)
|
||||
{
|
||||
Quaternion targetRotation = Quaternion.LookRotation(currentForward, Vector3.up);
|
||||
|
||||
transform.rotation = Quaternion.Slerp(
|
||||
transform.rotation,
|
||||
targetRotation,
|
||||
turnSpeed * Time.deltaTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void FinishRaftRide()
|
||||
{
|
||||
if (isFinished)
|
||||
return;
|
||||
|
||||
isFinished = true;
|
||||
|
||||
Debug.Log("[RaftRiverController] 목적지 도착. 뗏목 정지.");
|
||||
|
||||
onArrived?.Invoke();
|
||||
}
|
||||
|
||||
public void StopRaft()
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public void ResumeRaft()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public void SetSteeringKey(SteeringKeyXR newSteeringKey)
|
||||
{
|
||||
steeringKey = newSteeringKey;
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Cave/RaftRiverController.cs.meta
Normal file
2
Assets/02_Scripts/Cave/RaftRiverController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b40ec2ae87ea039488ef1e34f33579f5
|
||||
99
Assets/02_Scripts/Cave/SteeringKeyXR.cs
Normal file
99
Assets/02_Scripts/Cave/SteeringKeyXR.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
public class SteeringKeyXR : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private UnityEngine.XR.Interaction.Toolkit.Interactables.XRGrabInteractable grabInteractable;
|
||||
[SerializeField] private Transform keyPivot;
|
||||
|
||||
[Header("Steering")]
|
||||
[SerializeField] private float maxAngle = 45f;
|
||||
|
||||
[Header("Return")]
|
||||
[SerializeField] private bool returnToCenter = true;
|
||||
[SerializeField] private float returnSpeed = 5f;
|
||||
|
||||
private Transform currentInteractor;
|
||||
private bool isGrabbed;
|
||||
|
||||
private Quaternion initialPivotRotation;
|
||||
private float currentAngle;
|
||||
|
||||
public float SteeringValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return Mathf.Clamp(currentAngle / maxAngle, -1f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (keyPivot == null)
|
||||
keyPivot = transform;
|
||||
|
||||
initialPivotRotation = keyPivot.localRotation;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (grabInteractable != null)
|
||||
{
|
||||
grabInteractable.selectEntered.AddListener(OnGrab);
|
||||
grabInteractable.selectExited.AddListener(OnRelease);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (grabInteractable != null)
|
||||
{
|
||||
grabInteractable.selectEntered.RemoveListener(OnGrab);
|
||||
grabInteractable.selectExited.RemoveListener(OnRelease);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (isGrabbed && currentInteractor != null)
|
||||
{
|
||||
UpdateAngleFromHand();
|
||||
}
|
||||
else if (returnToCenter)
|
||||
{
|
||||
currentAngle = Mathf.Lerp(currentAngle, 0f, returnSpeed * Time.deltaTime);
|
||||
ApplyRotation();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGrab(SelectEnterEventArgs args)
|
||||
{
|
||||
isGrabbed = true;
|
||||
currentInteractor = args.interactorObject.transform;
|
||||
}
|
||||
|
||||
private void OnRelease(SelectExitEventArgs args)
|
||||
{
|
||||
isGrabbed = false;
|
||||
currentInteractor = null;
|
||||
}
|
||||
|
||||
private void UpdateAngleFromHand()
|
||||
{
|
||||
Vector3 worldDir = currentInteractor.position - keyPivot.position;
|
||||
Vector3 localDir = keyPivot.InverseTransformDirection(worldDir);
|
||||
|
||||
float targetAngle = Mathf.Atan2(localDir.x, localDir.z) * Mathf.Rad2Deg;
|
||||
|
||||
currentAngle = Mathf.Clamp(targetAngle, -maxAngle, maxAngle);
|
||||
|
||||
ApplyRotation();
|
||||
}
|
||||
|
||||
private void ApplyRotation()
|
||||
{
|
||||
keyPivot.localRotation =
|
||||
initialPivotRotation * Quaternion.Euler(0f, currentAngle, 0f);
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Cave/SteeringKeyXR.cs.meta
Normal file
2
Assets/02_Scripts/Cave/SteeringKeyXR.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a92559a555ebf114f8a208b13935a3e1
|
||||
Reference in New Issue
Block a user