CapersProject/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovement.cs
2025-07-15 19:56:39 +09:00

179 lines
5.1 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 플레이어 배의 움직임을 제어하는 컴포넌트
/// 속도, 회전, 틸트, 파도 효과 등을 관리합니다.
/// </summary>
public class VoyagePlayerShipMovement : MonoBehaviour
{
#region Inspector Fields
[Header("기본 이동 설정")]
[Tooltip("배의 최대 이동 속도")]
[SerializeField] private float maxSpeed = 20f;
[SerializeField] private float accelerationRate = 1f;
[SerializeField] private float dragFactor = 0.98f;
[SerializeField] private float minSpeedThreshold = 0.1f;
[Header("회전 설정")]
[SerializeField] private float rotationSpeed = 270f;
[SerializeField] private float minRotationSpeed = 90f;
[SerializeField] private float rotationAccelerationRate = 5f;
[SerializeField] private float turnSpeedPenalty = 0.5f;
[SerializeField] private float maxTurnAngle = 180f;
#endregion
#region Private Fields
private Vector3 currentVelocity;
private Vector2 currentInput;
public Vector2 CurrentInput => currentInput;
private float currentRotationSpeed;
public float CurrentRotationSpeed => currentRotationSpeed;
private float targetSpeed;
private float currentSpeed;
public float CurrentSpeed => currentSpeed;
public float MaxSpeed => maxSpeed;
// 회전 틸트 관련
private float currentRotationTilt;
private float lastRotationY;
private float currentAngularVelocity;
// 가속 틸트 관련
private float currentAccelTilt;
private float accelTiltVelocity;
private float prevSpeed;
// 파도 효과 관련
private float waveTime;
private float waveRandomOffset;
private float currentWaveHeight;
#endregion
#region Unity Messages
private void FixedUpdate()
{
UpdateMovement();
}
#endregion
#region Movement Methods
private void UpdateMovement()
{
if (IsMoving())
{
HandleMovement();
HandleRotation();
}
else
{
DecelerateMovement();
}
ApplyDrag();
ApplyMovement();
}
private bool IsMoving()
{
return currentInput.magnitude > minSpeedThreshold;
}
private void HandleMovement()
{
float baseTargetSpeed = CalculateBaseTargetSpeed();
float turnPenaltyFactor = CalculateTurnPenaltyFactor();
targetSpeed = baseTargetSpeed * turnPenaltyFactor;
currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, accelerationRate * Time.fixedDeltaTime);
if (ShouldStop())
{
currentSpeed = 0f;
}
UpdateVelocityVector();
}
private void HandleRotation()
{
if (IsMoving())
{
Vector3 inputDirection = new Vector3(currentInput.x, 0, currentInput.y).normalized;
Quaternion targetRotation = Quaternion.LookRotation(inputDirection, Vector3.up);
// 회전 속도를 현재 속도에 비례하도록 설정
float desiredRotationSpeed = rotationSpeed * (currentSpeed / maxSpeed);
desiredRotationSpeed = Mathf.Max(desiredRotationSpeed, minRotationSpeed);
currentRotationSpeed = Mathf.Lerp(currentRotationSpeed, desiredRotationSpeed,
rotationAccelerationRate * Time.fixedDeltaTime);
// 기본 회전 적용 (오브젝트 전체)
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
currentRotationSpeed * Time.fixedDeltaTime
);
}
}
private float CalculateBaseTargetSpeed()
{
return Mathf.Clamp01(currentInput.magnitude) * maxSpeed;
}
private float CalculateTurnPenaltyFactor()
{
Vector3 inputDirection = new Vector3(currentInput.x, 0, currentInput.y).normalized;
float angleDifference = Vector3.Angle(transform.forward, inputDirection);
return Mathf.Clamp01(1f - (angleDifference / maxTurnAngle * turnSpeedPenalty));
}
private bool ShouldStop()
{
return currentSpeed < minSpeedThreshold && targetSpeed < minSpeedThreshold;
}
private void UpdateVelocityVector()
{
currentVelocity = transform.forward * currentSpeed;
}
#endregion
private void ApplyDrag()
{
currentSpeed *= dragFactor;
// 최소 속도 이하면 완전히 정지
if (currentSpeed < minSpeedThreshold)
{
currentSpeed = 0f;
}
// 현재 방향으로 감속된 속도 적용
currentVelocity = transform.forward * currentSpeed;
}
private void ApplyMovement()
{
transform.position += currentVelocity * Time.fixedDeltaTime;
}
private void DecelerateMovement()
{
// 입력이 없을 때는 서서히 감속
currentSpeed = Mathf.Lerp(currentSpeed, 0f, accelerationRate * Time.fixedDeltaTime);
currentRotationSpeed = 0;
}
#region Input Handling
public void OnMove(InputAction.CallbackContext context)
{
currentInput = context.ReadValue<Vector2>();
}
#endregion
}