224 lines
7.5 KiB
C#
224 lines
7.5 KiB
C#
using UnityEngine;
|
|
|
|
[RequireComponent(typeof(VoyagePlayerShipMovement))]
|
|
public class VoyagePlayerShipMovementVisual : MonoBehaviour
|
|
{
|
|
[Header("메시 설정")]
|
|
[SerializeField] private Transform meshTransform;
|
|
public Transform MeshTransform => meshTransform;
|
|
|
|
[Header("회전 틸트 설정")]
|
|
[SerializeField] private float maxRotationTiltAngle = 15f;
|
|
[SerializeField] private float rotationTiltSpeed = 5f;
|
|
[SerializeField] private float rotationTiltReturnSpeed = 3f;
|
|
[SerializeField] private float angularVelocityMultiplier = 2f;
|
|
|
|
[Header("가속 틸트 설정")]
|
|
[SerializeField] private float maxAccelTiltAngle = 15f;
|
|
[SerializeField] private float accelTiltForce = 15f;
|
|
[SerializeField] private float accelTiltDamping = 0.9f;
|
|
[SerializeField] private float accelTiltSpeed = 10f;
|
|
[SerializeField] private float springStiffness = 30f;
|
|
[SerializeField] private float springDamping = 15f;
|
|
|
|
[Header("파도 효과 설정")]
|
|
[SerializeField] private float minSpeedWaveHeight = 0.2f;
|
|
public float MinSpeedWaveHeight => minSpeedWaveHeight;
|
|
[SerializeField] private float maxSpeedWaveHeight = 0.05f;
|
|
public float MaxSpeedWaveHeight => maxSpeedWaveHeight;
|
|
[SerializeField] private float baseWaveFrequency = 1f;
|
|
public float BaseWaveFrequency => baseWaveFrequency;
|
|
[SerializeField] private float speedWaveMultiplier = 5f;
|
|
[SerializeField] private float randomWaveOffset = 0.5f;
|
|
[SerializeField] private float waveUnitSpeed = 10f;
|
|
public float WaveUnitSpeed => waveUnitSpeed;
|
|
|
|
private VoyagePlayerShipMovement movement;
|
|
private Quaternion originalMeshRotation;
|
|
private Vector3 originalMeshPosition;
|
|
|
|
// 틸트 관련 변수들
|
|
private float currentRotationTilt;
|
|
private float lastRotationY;
|
|
private float currentAngularVelocity;
|
|
private float currentAccelTilt;
|
|
private float accelTiltVelocity;
|
|
private float prevSpeed;
|
|
|
|
// 파도 관련 변수들
|
|
private float waveTime;
|
|
public float WaveTime => waveTime;
|
|
private float waveRandomOffset;
|
|
private float currentWaveHeight;
|
|
public float CurrentWaveHeight => currentWaveHeight;
|
|
|
|
private void Start()
|
|
{
|
|
InitializeComponents();
|
|
InitializeMeshTransform();
|
|
InitializeWaveEffect();
|
|
}
|
|
|
|
#region Initialization
|
|
|
|
private void InitializeWaveEffect()
|
|
{
|
|
waveTime = 0f;
|
|
waveRandomOffset = Random.Range(-randomWaveOffset, randomWaveOffset);
|
|
}
|
|
|
|
private void ValidateMeshTransform()
|
|
{
|
|
if (Application.isEditor && !Application.isPlaying && meshTransform is null)
|
|
{
|
|
Debug.LogWarning("Mesh Transform을 Inspector에서 할당해주세요.");
|
|
}
|
|
}
|
|
|
|
private void InitializeMeshTransform()
|
|
{
|
|
if (meshTransform is null)
|
|
{
|
|
Debug.LogError("Mesh Transform이 할당되지 않았습니다.");
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
originalMeshPosition = meshTransform.localPosition;
|
|
originalMeshRotation = meshTransform.localRotation;
|
|
lastRotationY = transform.eulerAngles.y;
|
|
}
|
|
|
|
private void InitializeComponents()
|
|
{
|
|
movement = GetComponent<VoyagePlayerShipMovement>();
|
|
|
|
if (meshTransform == null)
|
|
{
|
|
Debug.LogError("Mesh Transform이 할당되지 않았습니다.");
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
originalMeshPosition = meshTransform.localPosition;
|
|
originalMeshRotation = meshTransform.localRotation;
|
|
lastRotationY = transform.eulerAngles.y;
|
|
waveTime = 0f;
|
|
waveRandomOffset = Random.Range(-randomWaveOffset, randomWaveOffset);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
if (meshTransform is null) return;
|
|
|
|
UpdateMeshRotationTilt();
|
|
UpdateAccelerationTilt();
|
|
ApplyMeshTilt();
|
|
UpdateWaveMotion();
|
|
ApplyMeshOffset();
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
ValidateMeshTransform();
|
|
}
|
|
|
|
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, -maxRotationTiltAngle, maxRotationTiltAngle);
|
|
|
|
// 틸트 적용 또는 복귀
|
|
if (Mathf.Abs(currentAngularVelocity) > 0.1f)
|
|
{
|
|
currentRotationTilt =
|
|
Mathf.Lerp(currentRotationTilt, targetTilt, rotationTiltSpeed * Time.fixedDeltaTime);
|
|
}
|
|
else
|
|
{
|
|
// 입력이 없을 때는 원래 자세로 천천히 복귀
|
|
currentRotationTilt = Mathf.Lerp(currentRotationTilt, 0f, rotationTiltReturnSpeed * Time.fixedDeltaTime);
|
|
}
|
|
|
|
lastRotationY = currentRotationY;
|
|
}
|
|
|
|
private void UpdateAccelerationTilt()
|
|
{
|
|
// 가속도 계산
|
|
float acceleration = (GetCurrentSpeed() - prevSpeed) / Time.fixedDeltaTime;
|
|
|
|
// 스프링 물리 시스템 구현
|
|
float springForce = -springStiffness * currentAccelTilt; // 복원력
|
|
float dampingForce = -springDamping * accelTiltVelocity; // 감쇠력
|
|
float accelerationForce = -acceleration * accelTiltForce; // 가속에 의한 힘
|
|
|
|
// 전체 힘 계산
|
|
float totalForce = springForce + dampingForce + accelerationForce;
|
|
|
|
// 가속도 계산 (F = ma, 질량은 1로 가정)
|
|
float tiltAcceleration = totalForce;
|
|
|
|
// 속도 업데이트
|
|
accelTiltVelocity += tiltAcceleration;
|
|
accelTiltVelocity *= accelTiltDamping; // 감쇠 적용
|
|
accelTiltVelocity *= Time.fixedDeltaTime;
|
|
|
|
// 위치(각도) 업데이트
|
|
currentAccelTilt = Mathf.Lerp(currentAccelTilt, currentAccelTilt + accelTiltVelocity,
|
|
accelTiltSpeed * Time.fixedDeltaTime);
|
|
currentAccelTilt = Mathf.Clamp(currentAccelTilt, -maxAccelTiltAngle, maxAccelTiltAngle);
|
|
|
|
prevSpeed = GetCurrentSpeed();
|
|
}
|
|
|
|
private void ApplyMeshTilt()
|
|
{
|
|
if (meshTransform is null) return;
|
|
|
|
// 회전 틸트와 가속 틸트를 조합
|
|
// 메시에 최종 틸트 적용
|
|
meshTransform.localRotation = originalMeshRotation * Quaternion.Euler(
|
|
currentAccelTilt, // X축 (가속 틸트)
|
|
0, // Y축
|
|
currentRotationTilt // Z축 (회전 틸트)
|
|
);
|
|
}
|
|
|
|
private void UpdateWaveMotion()
|
|
{
|
|
if (meshTransform is null) return;
|
|
|
|
// 현재 속도에 비례하여 파도 주기 조절
|
|
float waveSpeedFactor = 1f + (GetCurrentSpeed() / waveUnitSpeed) * speedWaveMultiplier;
|
|
waveTime += Time.fixedDeltaTime * baseWaveFrequency * waveSpeedFactor;
|
|
float currentSpeedByUnit = GetCurrentSpeed() / waveUnitSpeed;
|
|
currentSpeedByUnit = Mathf.Clamp01(currentSpeedByUnit);
|
|
float waveHeight = Mathf.Lerp(minSpeedWaveHeight, maxSpeedWaveHeight, currentSpeedByUnit);
|
|
|
|
currentWaveHeight = waveHeight * Mathf.Sin(waveTime + waveRandomOffset);
|
|
}
|
|
|
|
private void ApplyMeshOffset()
|
|
{
|
|
if (meshTransform is null) return;
|
|
|
|
Vector3 position = originalMeshPosition + (Vector3.up * currentWaveHeight);
|
|
meshTransform.localPosition = position;
|
|
}
|
|
|
|
private float GetCurrentSpeed()
|
|
{
|
|
return movement.CurrentSpeed;
|
|
}
|
|
} |