CapersProject/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovement.cs
2025-07-15 13:40:43 +09:00

265 lines
9.4 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
public class VoyagePlayerShipMovement : MonoBehaviour
{
[Header("Movement Settings")]
[SerializeField] private float maxSpeed = 10f;
[SerializeField] private float rotationSpeed = 120f;
[SerializeField] private float accelerationRate = 2f;
[SerializeField] private float minSpeedThreshold = 0.1f;
[SerializeField] private float dragFactor = 0.98f;
[Header("Turn Settings")]
[SerializeField] private float turnSpeedPenalty = 0.5f; // 선회 시 감속 정도 (0: 감속 없음, 1: 완전 정지)
[SerializeField] private float maxTurnAngle = 180f; // 최대 감속이 적용되는 각도
#if UNITY_EDITOR
[Header("Debug Settings")]
[SerializeField] private bool showDebugLines = true;
[SerializeField] private float debugLineLength = 4f;
[SerializeField] private float debugLineHeightStep = 0.1f;
private LineRenderer velocityLine;
private LineRenderer forwardDirectionLine;
private LineRenderer upDirectionLine;
private LineRenderer inputDirectionLine;
private bool lineRendererCreated = false;
#endif
private Vector3 currentVelocity;
private Vector2 currentInput;
private float targetSpeed;
private float currentSpeed;
[Header("Tilt Settings")]
[SerializeField] private float maxTiltAngle = 15f;
[SerializeField] private float tiltSpeed = 5f;
[SerializeField] private float returnSpeed = 3f; // 원래 자세로 돌아오는 속도
[SerializeField] private float angularVelocityMultiplier = 2f; // 각속도 영향력
[SerializeField] private string meshObjectName = "Ship_Mesh";
private Transform _meshTransform;
private float _currentTilt = 0f;
private Quaternion _originalMeshRotation;
private float _lastRotationY; // 이전 프레임의 Y축 회전값
private float _currentAngularVelocity; // 현재 각속도
private void Start()
{
_meshTransform = transform.Find(meshObjectName);
if (_meshTransform == null)
{
Debug.LogError($"메시 오브젝트를 찾을 수 없습니다: {meshObjectName}");
enabled = false;
return;
}
_originalMeshRotation = _meshTransform.localRotation;
_lastRotationY = transform.eulerAngles.y;
}
private void FixedUpdate()
{
if (currentInput.magnitude > minSpeedThreshold)
{
HandleMovement();
HandleRotation();
}
else
{
// 입력이 없을 때는 서서히 감속
currentSpeed = Mathf.Lerp(currentSpeed, 0f, accelerationRate * Time.fixedDeltaTime);
}
UpdateMeshRotationTilt();
ApplyDrag();
ApplyMovement();
#if UNITY_EDITOR
if (showDebugLines)
{
UpdateAllDebugLines();
}
#endif
}
private void HandleMovement()
{
// 기본 목표 속도 계산 (입력 크기에 비례)
float baseTargetSpeed = Mathf.Clamp01(currentInput.magnitude) * maxSpeed;
// 현재 방향과 목표 방향 사이의 각도 계산
Vector3 inputDirection = new Vector3(currentInput.x, 0, currentInput.y).normalized;
float angleDifference = Vector3.Angle(transform.forward, inputDirection);
// 각도에 따른 속도 페널티 계산 (각도가 클수록 더 큰 감속)
float turnPenaltyFactor = 1f - (angleDifference / maxTurnAngle * turnSpeedPenalty);
turnPenaltyFactor = Mathf.Clamp01(turnPenaltyFactor);
// 최종 목표 속도 계산 (기본 속도에 선회 페널티 적용)
targetSpeed = baseTargetSpeed * turnPenaltyFactor;
// 현재 속도를 목표 속도로 부드럽게 보간
currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, accelerationRate * Time.fixedDeltaTime);
// 최소 임계값 이하면 완전히 정지
if (currentSpeed < minSpeedThreshold && targetSpeed < minSpeedThreshold)
{
currentSpeed = 0f;
}
// 현재 바라보는 방향으로 속도 벡터 업데이트
currentVelocity = transform.forward * currentSpeed;
}
private void HandleRotation()
{
if (currentInput.magnitude > minSpeedThreshold)
{
Vector3 inputDirection = new Vector3(currentInput.x, 0, currentInput.y).normalized;
Quaternion targetRotation = Quaternion.LookRotation(inputDirection, Vector3.up);
// 회전 속도를 현재 속도에 비례하도록 설정
float currentRotationSpeed = rotationSpeed * (currentSpeed / maxSpeed);
currentRotationSpeed = Mathf.Max(currentRotationSpeed, rotationSpeed * 0.3f);
// 기본 회전 적용 (오브젝트 전체)
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
currentRotationSpeed * Time.fixedDeltaTime
);
}
}
private void UpdateMeshRotationTilt()
{
if (_meshTransform is null) return;
// 현재 Y축 회전값과 각속도 계산
float currentRotationY = transform.eulerAngles.y;
float deltaRotation = Mathf.DeltaAngle(_lastRotationY, currentRotationY);
_currentAngularVelocity = deltaRotation / Time.fixedDeltaTime;
// 목표 틸트 각도 계산
float targetTilt = -_currentAngularVelocity * angularVelocityMultiplier;
targetTilt = Mathf.Clamp(targetTilt, -maxTiltAngle, maxTiltAngle);
// 틸트 적용 또는 복귀
if (Mathf.Abs(_currentAngularVelocity) > 0.1f)
{
_currentTilt = Mathf.Lerp(_currentTilt, targetTilt, tiltSpeed * Time.fixedDeltaTime);
}
else
{
// 입력이 없을 때는 원래 자세로 천천히 복귀
_currentTilt = Mathf.Lerp(_currentTilt, 0f, returnSpeed * Time.fixedDeltaTime);
}
// 메시에 틸트 적용
_meshTransform.localRotation = _originalMeshRotation * Quaternion.Euler(0, 0, _currentTilt);
_lastRotationY = currentRotationY;
}
private void ApplyDrag()
{
currentSpeed *= dragFactor;
// 최소 속도 이하면 완전히 정지
if (currentSpeed < minSpeedThreshold)
{
currentSpeed = 0f;
}
// 현재 방향으로 감속된 속도 적용
currentVelocity = transform.forward * currentSpeed;
}
private void ApplyMovement()
{
transform.position += currentVelocity * Time.fixedDeltaTime;
}
public void OnMove(InputAction.CallbackContext context)
{
currentInput = context.ReadValue<Vector2>();
// Debug Log this
Debug.Log(currentInput);
}
#if UNITY_EDITOR
private void UpdateAllDebugLines()
{
if (lineRendererCreated == false)
{
lineRendererCreated = true;
forwardDirectionLine = CreateLineRenderer("CurrentDirectionLine", Color.green);
upDirectionLine = CreateLineRenderer("UpDirectionLine", Color.yellow);
inputDirectionLine = CreateLineRenderer("InputDirectionLine", Color.red);
velocityLine = CreateLineRenderer("VelocityLine", Color.blue);
}
// 전방 방향 표시 (기본 높이)
DrawDebugLine(forwardDirectionLine, transform.forward, debugLineLength, 0);
// 메시의 위쪽 방향 표시 (틸팅 반영)
if (_meshTransform is not null)
{
DrawDebugLine(upDirectionLine, _meshTransform.up, debugLineLength, debugLineHeightStep);
}
// 입력 방향 표시 (두 단계 위)
Vector3 inputDirection = new Vector3(currentInput.x, 0, currentInput.y).normalized;
DrawDebugLine(inputDirectionLine, inputDirection, debugLineLength * currentInput.magnitude, debugLineHeightStep * 2);
// 속도 벡터 표시 (세 단계 위)
DrawDebugLine(velocityLine, currentVelocity.normalized, currentVelocity.magnitude, debugLineHeightStep * 3);
}
private LineRenderer CreateLineRenderer(string name, Color color)
{
GameObject lineObj = new GameObject(name);
lineObj.transform.SetParent(transform);
LineRenderer line = lineObj.AddComponent<LineRenderer>();
line.startWidth = 0.1f;
line.endWidth = 0.1f;
line.material = new Material(Shader.Find("Universal Render Pipeline/Unlit"));
line.startColor = color;
line.endColor = color;
line.positionCount = 2;
// 라인 렌더러가 다른 오브젝트를 가리지 않도록 설정
line.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
line.receiveShadows = false;
line.material.color = color;
return line;
}
private void DrawDebugLine(LineRenderer renderer, Vector3 direction, float length, float heightOffset)
{
if (!renderer) return;
Vector3 position = transform.position + Vector3.up * heightOffset;
renderer.SetPosition(0, position);
renderer.SetPosition(1, position + direction * length);
}
#endif
private void OnValidate()
{
// 에디터에서 메시 오브젝트 이름이 변경될 때 자동으로 찾기
if (Application.isEditor && !Application.isPlaying)
{
_meshTransform = transform.Find(meshObjectName);
}
}
}