동굴 수정

This commit is contained in:
2026-06-16 16:41:50 +09:00
parent 8369e2ab51
commit 35d206038f
179 changed files with 11335 additions and 2 deletions

View 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] 이제 플레이어가 육지로 이동해 조개 미션을 진행할 수 있습니다.");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4ab95bcbad7a49c4d9030027474e9c39

View 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;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b40ec2ae87ea039488ef1e34f33579f5

View 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);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a92559a555ebf114f8a208b13935a3e1