동굴재건

This commit is contained in:
2026-06-18 17:47:41 +09:00
parent 55add41bfb
commit f7cd7cbe18
274 changed files with 3666 additions and 8828 deletions

View File

@@ -17,11 +17,18 @@ public class RaftRiverController : MonoBehaviour
[SerializeField] private float turnSpeed = 4f;
[Header("Side Control")]
[SerializeField] private float sideMoveSpeed = 10f;
[SerializeField] private float sideMoveSpeed = 16f;
[SerializeField] private float sideAcceleration = 40f;
[Tooltip("강 중앙선 기준 좌우 이동 가능 범위입니다. 동굴 폭 46이면 14~16 추천.")]
[SerializeField] private float maxSideOffset = 16f;
[Header("Path Follow Feel")]
[SerializeField] private float pathFollowSmoothTime = 0.28f;
[Range(0f, 1f)]
[SerializeField] private float rotationVelocityBlend = 0.45f;
[SerializeField] private float steeringYawAngle = 18f;
[Header("Arrival")]
[SerializeField] private float pointReachDistance = 1.5f;
@@ -30,17 +37,29 @@ public class RaftRiverController : MonoBehaviour
private int currentPointIndex = 0;
private float sideOffset = 0f;
private float sideVelocity = 0f;
private float currentSteeringInput = 0f;
private Vector3 currentCenterPosition;
private Vector3 currentForward;
private Vector3 currentRight;
private Vector3 positionSmoothVelocity;
private Vector3 previousPosition;
private bool isFinished = false;
private bool warnedMissingSteeringKey;
public bool IsFinished => isFinished;
private void Awake()
{
ResolveSteeringKey();
}
private void Start()
{
ResolveSteeringKey();
if (pathPoints == null || pathPoints.Length == 0)
{
Debug.LogWarning("[RaftRiverController] Path Points가 비어 있습니다.", this);
@@ -49,6 +68,9 @@ private void Start()
}
currentCenterPosition = transform.position;
currentForward = transform.forward;
currentRight = transform.right;
previousPosition = transform.position;
// 첫 목표 지점은 0번이 아니라 1번부터 가는 것이 자연스러울 때가 많음.
// 단, 0번이 현재 위치와 다르면 그대로 0번부터 이동.
@@ -67,6 +89,8 @@ private void Update()
private void MoveAlongPath()
{
SkipMissingPathPoints();
if (currentPointIndex >= pathPoints.Length)
{
FinishRaftRide();
@@ -108,6 +132,7 @@ private void MoveAlongPath()
private void HandleSideControl()
{
float input = 0f;
ResolveSteeringKey();
if (steeringKey != null)
{
@@ -115,8 +140,13 @@ private void HandleSideControl()
}
else
{
// 키 연결 전 테스트용
input = Input.GetAxis("Horizontal");
input = ReadLegacyHorizontalInput();
if (!warnedMissingSteeringKey)
{
warnedMissingSteeringKey = true;
Debug.LogWarning("[RaftRiverController] SteeringKeyXR 참조가 없어 좌우 조향 없이 전진합니다.", this);
}
}
// 반전은 여기서만 처리
@@ -125,22 +155,60 @@ private void HandleSideControl()
input *= -1f;
}
sideOffset += input * sideMoveSpeed * Time.deltaTime;
currentSteeringInput = input;
float targetSideVelocity = input * sideMoveSpeed;
sideVelocity = Mathf.MoveTowards(
sideVelocity,
targetSideVelocity,
sideAcceleration * Time.deltaTime
);
sideOffset += sideVelocity * Time.deltaTime;
sideOffset = Mathf.Clamp(sideOffset, -maxSideOffset, maxSideOffset);
if (Mathf.Approximately(Mathf.Abs(sideOffset), maxSideOffset) &&
Mathf.Sign(sideVelocity) == Mathf.Sign(sideOffset))
{
sideVelocity = 0f;
}
}
private void ApplyRaftPositionAndRotation()
{
Vector3 finalPosition = currentCenterPosition + currentRight * sideOffset;
previousPosition = transform.position;
Vector3 targetPosition = currentCenterPosition + currentRight * sideOffset;
// 현재 뗏목 높이 유지
finalPosition.y = transform.position.y;
targetPosition.y = transform.position.y;
transform.position = finalPosition;
float smoothTime = Mathf.Max(0.01f, pathFollowSmoothTime);
transform.position = Vector3.SmoothDamp(
transform.position,
targetPosition,
ref positionSmoothVelocity,
smoothTime
);
if (currentForward != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(currentForward, Vector3.up);
Vector3 frameVelocity = transform.position - previousPosition;
frameVelocity.y = 0f;
Vector3 lookDirection = currentForward;
if (frameVelocity.sqrMagnitude > 0.0001f)
{
lookDirection = Vector3.Slerp(
currentForward,
frameVelocity.normalized,
rotationVelocityBlend
);
}
Quaternion targetRotation =
Quaternion.LookRotation(lookDirection, Vector3.up) *
Quaternion.Euler(0f, currentSteeringInput * steeringYawAngle, 0f);
transform.rotation = Quaternion.Slerp(
transform.rotation,
@@ -175,5 +243,31 @@ public void ResumeRaft()
public void SetSteeringKey(SteeringKeyXR newSteeringKey)
{
steeringKey = newSteeringKey;
warnedMissingSteeringKey = false;
}
}
private void ResolveSteeringKey()
{
if (steeringKey != null)
return;
steeringKey = GetComponentInChildren<SteeringKeyXR>(true);
}
private void SkipMissingPathPoints()
{
while (currentPointIndex < pathPoints.Length && pathPoints[currentPointIndex] == null)
{
currentPointIndex++;
}
}
private float ReadLegacyHorizontalInput()
{
#if ENABLE_LEGACY_INPUT_MANAGER
return Input.GetAxis("Horizontal");
#else
return 0f;
#endif
}
}

View File

@@ -1,10 +1,11 @@
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactables;
public class SteeringKeyXR : MonoBehaviour
{
[Header("References")]
[SerializeField] private UnityEngine.XR.Interaction.Toolkit.Interactables.XRGrabInteractable grabInteractable;
[SerializeField] private XRGrabInteractable grabInteractable;
[SerializeField] private Transform keyPivot;
[Header("Steering")]
@@ -16,6 +17,7 @@ public class SteeringKeyXR : MonoBehaviour
private Transform currentInteractor;
private bool isGrabbed;
private bool warnedMissingGrabInteractable;
private Quaternion initialPivotRotation;
private float currentAngle;
@@ -24,6 +26,9 @@ public float SteeringValue
{
get
{
if (Mathf.Approximately(maxAngle, 0f))
return 0f;
return Mathf.Clamp(currentAngle / maxAngle, -1f, 1f);
}
}
@@ -33,11 +38,14 @@ private void Awake()
if (keyPivot == null)
keyPivot = transform;
ResolveGrabInteractable();
initialPivotRotation = keyPivot.localRotation;
}
private void OnEnable()
{
ResolveGrabInteractable();
if (grabInteractable != null)
{
grabInteractable.selectEntered.AddListener(OnGrab);
@@ -82,7 +90,7 @@ private void OnRelease(SelectExitEventArgs args)
private void UpdateAngleFromHand()
{
Vector3 worldDir = currentInteractor.position - keyPivot.position;
Vector3 localDir = keyPivot.InverseTransformDirection(worldDir);
Vector3 localDir = GetInitialPivotSpaceDirection(worldDir);
float targetAngle = Mathf.Atan2(localDir.x, localDir.z) * Mathf.Rad2Deg;
@@ -96,4 +104,27 @@ private void ApplyRotation()
keyPivot.localRotation =
initialPivotRotation * Quaternion.Euler(0f, currentAngle, 0f);
}
}
private Vector3 GetInitialPivotSpaceDirection(Vector3 worldDirection)
{
Quaternion referenceRotation = keyPivot.parent != null
? keyPivot.parent.rotation * initialPivotRotation
: initialPivotRotation;
return Quaternion.Inverse(referenceRotation) * worldDirection;
}
private void ResolveGrabInteractable()
{
if (grabInteractable != null)
return;
grabInteractable = GetComponentInChildren<XRGrabInteractable>(true);
if (grabInteractable == null && !warnedMissingGrabInteractable)
{
warnedMissingGrabInteractable = true;
Debug.LogWarning("[SteeringKeyXR] XRGrabInteractable 참조가 없어 키를 잡아도 조향 이벤트를 받을 수 없습니다.", this);
}
}
}