From 4a06cc0565e5c3669dc862d907fce4b4b9665be2 Mon Sep 17 00:00:00 2001 From: Jeonghyeon Ha Date: Tue, 15 Jul 2025 19:56:39 +0900 Subject: [PATCH] =?UTF-8?q?DDD-43=20=EB=B0=B0=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/0_Voyage/Ship/Player/PlayerShip.prefab | 28 +- .../_Scripts/Ship/VoyagePlayerShipMovement.cs | 406 +----------------- .../Ship/VoyagePlayerShipMovementDebug.cs | 222 ++++++++++ .../VoyagePlayerShipMovementDebug.cs.meta | 3 + .../Ship/VoyagePlayerShipMovementVisual.cs | 224 ++++++++++ .../VoyagePlayerShipMovementVisual.cs.meta | 3 + 6 files changed, 497 insertions(+), 389 deletions(-) create mode 100644 Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs create mode 100644 Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs.meta create mode 100644 Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs create mode 100644 Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs.meta diff --git a/Assets/0_Voyage/Ship/Player/PlayerShip.prefab b/Assets/0_Voyage/Ship/Player/PlayerShip.prefab index de2a26ea0..e104f8cec 100644 --- a/Assets/0_Voyage/Ship/Player/PlayerShip.prefab +++ b/Assets/0_Voyage/Ship/Player/PlayerShip.prefab @@ -12,6 +12,8 @@ GameObject: - component: {fileID: 1657872600039613395} - component: {fileID: 2479726504690309911} - component: {fileID: -1082383067592254908} + - component: {fileID: 7722456790212551628} + - component: {fileID: 3140672700990605547} m_Layer: 0 m_Name: PlayerShip m_TagString: Untagged @@ -114,6 +116,19 @@ MonoBehaviour: rotationAccelerationRate: 5 turnSpeedPenalty: 0.5 maxTurnAngle: 180 +--- !u!114 &7722456790212551628 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1553910019582315619} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a09f8b932e18409aa7f5d2a221921f45, type: 3} + m_Name: + m_EditorClassIdentifier: + meshTransform: {fileID: 2507610487897935131} maxRotationTiltAngle: 15 rotationTiltSpeed: 5 rotationTiltReturnSpeed: 3 @@ -130,7 +145,18 @@ MonoBehaviour: speedWaveMultiplier: 5 randomWaveOffset: 0.5 waveUnitSpeed: 10 - meshTransform: {fileID: 2507610487897935131} +--- !u!114 &3140672700990605547 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1553910019582315619} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3d5c5f51b32b4633b887d096554c6cd9, type: 3} + m_Name: + m_EditorClassIdentifier: showDebugVisuals: 1 debugLineLength: 5 debugLineWidth: 0.1 diff --git a/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovement.cs b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovement.cs index 95691d6f5..3096a1231 100644 --- a/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovement.cs +++ b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovement.cs @@ -22,41 +22,20 @@ public class VoyagePlayerShipMovement : MonoBehaviour [SerializeField] private float rotationAccelerationRate = 5f; [SerializeField] private float turnSpeedPenalty = 0.5f; [SerializeField] private float maxTurnAngle = 180f; - - [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; - [SerializeField] private float maxSpeedWaveHeight = 0.05f; - [SerializeField] private float baseWaveFrequency = 1f; - [SerializeField] private float speedWaveMultiplier = 5f; - [SerializeField] private float randomWaveOffset = 0.5f; - [SerializeField] private float waveUnitSpeed = 10f; - - [Header("메시 설정")] - [SerializeField] private Transform meshTransform; - + #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; @@ -73,41 +52,16 @@ public class VoyagePlayerShipMovement : MonoBehaviour private float waveRandomOffset; private float currentWaveHeight; - // 메시 원본 상태 - private Quaternion originalMeshRotation; - private Vector3 originalMeshPosition; - #endregion #region Unity Messages - - private void Start() - { - InitializeMeshTransform(); - InitializeWaveEffect(); -#if UNITY_EDITOR - InitializeDebugVisuals(); -#endif - } - private void FixedUpdate() { UpdateMovement(); - UpdateVisualEffects(); -#if UNITY_EDITOR - UpdateDebugVisuals(); -#endif } - - private void OnValidate() - { - ValidateMeshTransform(); - } - #endregion #region Movement Methods - private void UpdateMovement() { if (IsMoving()) @@ -145,43 +99,6 @@ public class VoyagePlayerShipMovement : MonoBehaviour UpdateVelocityVector(); } - 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 - - #region Visual Effects - - private void UpdateVisualEffects() - { - if (meshTransform is null) return; - - UpdateMeshRotationTilt(); - UpdateAccelerationTilt(); - ApplyMeshTilt(); - UpdateWaveMotion(); - ApplyMeshOffset(); - } - private void HandleRotation() { if (IsMoving()) @@ -203,99 +120,30 @@ public class VoyagePlayerShipMovement : MonoBehaviour ); } } - - private void UpdateMeshRotationTilt() + + private float CalculateBaseTargetSpeed() { - 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; + return Mathf.Clamp01(currentInput.magnitude) * maxSpeed; } - private void UpdateAccelerationTilt() + private float CalculateTurnPenaltyFactor() { - // 가속도 계산 - float acceleration = (currentSpeed - 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 = currentSpeed; + 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 void ApplyMeshTilt() + private bool ShouldStop() { - if (meshTransform is null) return; - - // 회전 틸트와 가속 틸트를 조합 - // 메시에 최종 틸트 적용 - meshTransform.localRotation = originalMeshRotation * Quaternion.Euler( - currentAccelTilt, // X축 (가속 틸트) - 0, // Y축 - currentRotationTilt // Z축 (회전 틸트) - ); + return currentSpeed < minSpeedThreshold && targetSpeed < minSpeedThreshold; } - private void UpdateWaveMotion() + private void UpdateVelocityVector() { - if (meshTransform is null) return; - - // 현재 속도에 비례하여 파도 주기 조절 - float waveSpeedFactor = 1f + (currentSpeed / waveUnitSpeed) * speedWaveMultiplier; - waveTime += Time.fixedDeltaTime * baseWaveFrequency * waveSpeedFactor; - float currentSpeedByUnit = currentSpeed / waveUnitSpeed; - currentSpeedByUnit = Mathf.Clamp01(currentSpeedByUnit); - float waveHeight = Mathf.Lerp(minSpeedWaveHeight, maxSpeedWaveHeight, currentSpeedByUnit); - - currentWaveHeight = waveHeight * Mathf.Sin(waveTime + waveRandomOffset); + currentVelocity = transform.forward * currentSpeed; } - - private void ApplyMeshOffset() - { - if (meshTransform is null) return; - - Vector3 position = originalMeshPosition + (Vector3.up * currentWaveHeight); - meshTransform.localPosition = position; - } - + #endregion + private void ApplyDrag() { currentSpeed *= dragFactor; @@ -310,7 +158,6 @@ public class VoyagePlayerShipMovement : MonoBehaviour currentVelocity = transform.forward * currentSpeed; } - private void ApplyMovement() { transform.position += currentVelocity * Time.fixedDeltaTime; @@ -323,227 +170,10 @@ public class VoyagePlayerShipMovement : MonoBehaviour currentRotationSpeed = 0; } - #endregion - #region Input Handling - public void OnMove(InputAction.CallbackContext context) { currentInput = context.ReadValue(); } - #endregion - - #region Initialization - - 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 InitializeWaveEffect() - { - waveTime = 0f; - waveRandomOffset = Random.Range(-randomWaveOffset, randomWaveOffset); - } - - private void ValidateMeshTransform() - { - if (Application.isEditor && !Application.isPlaying && meshTransform is null) - { - Debug.LogWarning("Mesh Transform을 Inspector에서 할당해주세요."); - } - } - - #endregion - -#if UNITY_EDITOR - [Header("Debug Visualization")] - [SerializeField] private bool showDebugVisuals = true; - - [SerializeField] private float debugLineLength = 5f; - [SerializeField] private float debugLineWidth = 0.1f; - - private LineRenderer _speedLineRenderer; - private LineRenderer _rotationSpeedLineRenderer; - private LineRenderer _rotationDeltaLineRenderer; - private LineRenderer _TiltLineRenderer; - private LineRenderer _waveHeightLineRenderer; - private LineRenderer _wavePatternLineRenderer; - - private void InitializeDebugVisuals() - { - if (!showDebugVisuals) return; - - // 속도 표시 - _speedLineRenderer = CreateLineRenderer("SpeedLine", Color.green); - // 회전 방향 표시 - _rotationSpeedLineRenderer = CreateLineRenderer("RotationSpeedLine", Color.magenta); - // 회전 방향 표시 - _rotationDeltaLineRenderer = CreateLineRenderer("RotationDeltaLine", Color.yellow); - // 틸트 표시 - _TiltLineRenderer = CreateLineRenderer("TiltLine", Color.red); - // 파도 높이 표시 - _waveHeightLineRenderer = CreateLineRenderer("WaveHeightLine", Color.blue); - // 파도 패턴 표시 - _wavePatternLineRenderer = CreateLineRenderer("WavePatternLine", Color.cyan); - _wavePatternLineRenderer.positionCount = 50; // 파도 패턴을 위한 더 많은 점 - } - - private void UpdateDebugVisuals() - { - if (!showDebugVisuals) return; - - // 속도 벡터 표시 - UpdateSpeedLine(); - - // 회전 방향 및 각속도 표시 - UpdateRotationSpeedLine(); - UpdateRotationDeltaLine(); - // 회전 틸트 표시 - UpdateTiltLine(); - // 파도 높이와 패턴 표시 - UpdateWaveVisualization(); - } - - private void UpdateSpeedLine() - { - Vector3 start = transform.position + Vector3.up * 1.5f; - Vector3 end = start + transform.forward * (currentSpeed / maxSpeed) * debugLineLength * 2; - DrawLine(_speedLineRenderer, start, end); - } - - private void UpdateRotationSpeedLine() - { - Vector3 start = transform.position + Vector3.up * 1.2f; - // 각속도를 호로 표현 - if (Mathf.Abs(currentRotationSpeed) > 0.1f) - { - Vector3[] arcPoints = new Vector3[10]; - float radius = debugLineLength * 1f; - float angleStep = currentRotationSpeed * 1f / (arcPoints.Length - 1); - - for (int i = 0; i < arcPoints.Length; i++) - { - float angle = angleStep * i; - Vector3 point = start + Quaternion.Euler(0, angle, 0) * transform.forward * radius; - arcPoints[i] = point; - } - - _rotationSpeedLineRenderer.positionCount = arcPoints.Length; - _rotationSpeedLineRenderer.SetPositions(arcPoints); - } - else - { - _rotationSpeedLineRenderer.positionCount = 0; - } - } - - private void UpdateRotationDeltaLine() - { - float deltaAngle = 0f; - if (currentInput.magnitude > minSpeedThreshold) - { - Vector3 inputDirection = new Vector3(currentInput.x, 0, currentInput.y).normalized; - Quaternion targetRotation = Quaternion.LookRotation(inputDirection, Vector3.up); - deltaAngle = Quaternion.Angle(transform.rotation, targetRotation); - } - - Vector3 start = transform.position + Vector3.up * 1.2f; - // 각속도를 호로 표현 - if (Mathf.Abs(deltaAngle) > 0.1f) - { - Vector3[] arcPoints = new Vector3[10]; - float radius = debugLineLength * 1.05f; - float angleStep = deltaAngle * 1f / (arcPoints.Length - 1); - - for (int i = 0; i < arcPoints.Length; i++) - { - float angle = angleStep * i; - Vector3 point = start + Quaternion.Euler(0, angle, 0) * transform.forward * radius; - arcPoints[i] = point; - } - - _rotationDeltaLineRenderer.positionCount = arcPoints.Length; - _rotationDeltaLineRenderer.SetPositions(arcPoints); - } - else - { - _rotationDeltaLineRenderer.positionCount = 0; - } - } - - private void UpdateTiltLine() - { - Vector3 start = transform.position + Vector3.up * 1.5f; - Vector3 tiltDirection = meshTransform.up; - DrawLine(_TiltLineRenderer, start, start + tiltDirection * debugLineLength * 0.4f); - } - - private void UpdateWaveVisualization() - { - // 현재 파도 높이 표시 - Vector3 waveStart = transform.position + Vector3.up * 1.5f - transform.forward * 1.5f; - Vector3 waveEnd = waveStart + Vector3.up * currentWaveHeight * debugLineLength; - DrawLine(_waveHeightLineRenderer, waveStart, waveEnd); - - // 파도 패턴 시각화 - Vector3[] wavePoints = new Vector3[_wavePatternLineRenderer.positionCount]; - float waveLength = debugLineLength * 2f; - - for (int i = 0; i < wavePoints.Length; i++) - { - float t = (float)i / (_wavePatternLineRenderer.positionCount - 1); - float x = t * waveLength - waveLength * 0.5f; - float currentSpeedByUnit = currentSpeed / waveUnitSpeed; - currentSpeedByUnit = Mathf.Clamp01(currentSpeedByUnit); - float waveHeight = Mathf.Lerp(minSpeedWaveHeight, maxSpeedWaveHeight, currentSpeedByUnit); - float y = Mathf.Sin((waveTime + x) * baseWaveFrequency) * waveHeight; - - wavePoints[i] = transform.position + - Vector3.right * x + - Vector3.up * (y + 2f); // 높이 오프셋 - wavePoints[i] += Vector3.back * 3f + Vector3.down * 1f; - } - - _wavePatternLineRenderer.SetPositions(wavePoints); - } - - private LineRenderer CreateLineRenderer(string name, Color color) - { - GameObject lineObj = new GameObject(name); - lineObj.transform.SetParent(transform); - LineRenderer line = lineObj.AddComponent(); - - line.startWidth = debugLineWidth; - line.endWidth = debugLineWidth; - 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 DrawLine(LineRenderer line, Vector3 start, Vector3 end) - { - if (line is null) return; - line.positionCount = 2; - line.SetPosition(0, start); - line.SetPosition(1, end); - } -#endif } \ No newline at end of file diff --git a/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs new file mode 100644 index 000000000..58d3e2ceb --- /dev/null +++ b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs @@ -0,0 +1,222 @@ +using UnityEngine; + +namespace Voyage +{ +#if UNITY_EDITOR + using UnityEngine; + + [RequireComponent(typeof(VoyagePlayerShipMovement)), RequireComponent(typeof(VoyagePlayerShipMovementVisual))] + public class VoyagePlayerShipDebug : MonoBehaviour + { + [Header("Debug Visualization")] + [SerializeField] private bool showDebugVisuals = true; + [SerializeField] private float debugLineLength = 5f; + [SerializeField] private float debugLineWidth = 0.1f; + + private LineRenderer _speedLineRenderer; + private LineRenderer _rotationSpeedLineRenderer; + private LineRenderer _rotationDeltaLineRenderer; + private LineRenderer _TiltLineRenderer; + private LineRenderer _waveHeightLineRenderer; + private LineRenderer _wavePatternLineRenderer; + + private VoyagePlayerShipMovement movement; + private VoyagePlayerShipMovementVisual movementVisual; + + + private void Start() + { + if (!showDebugVisuals) return; + + movement = GetComponent(); + movementVisual = GetComponent(); + + InitializeDebugVisuals(); + } + + private void Update() + { + if (!showDebugVisuals) return; + UpdateDebugVisuals(); + } + + private void InitializeDebugVisuals() + { + if (!showDebugVisuals) return; + + // 속도 표시 + _speedLineRenderer = CreateLineRenderer("SpeedLine", Color.green); + // 회전 방향 표시 + _rotationSpeedLineRenderer = CreateLineRenderer("RotationSpeedLine", Color.magenta); + // 회전 방향 표시 + _rotationDeltaLineRenderer = CreateLineRenderer("RotationDeltaLine", Color.yellow); + // 틸트 표시 + _TiltLineRenderer = CreateLineRenderer("TiltLine", Color.red); + // 파도 높이 표시 + _waveHeightLineRenderer = CreateLineRenderer("WaveHeightLine", Color.blue); + // 파도 패턴 표시 + _wavePatternLineRenderer = CreateLineRenderer("WavePatternLine", Color.cyan); + _wavePatternLineRenderer.positionCount = 50; // 파도 패턴을 위한 더 많은 점 + } + + private void UpdateDebugVisuals() + { + if (!showDebugVisuals) return; + + // 속도 벡터 표시 + UpdateSpeedLine(); + + // 회전 방향 및 각속도 표시 + UpdateRotationSpeedLine(); + UpdateRotationDeltaLine(); + // 회전 틸트 표시 + UpdateTiltLine(); + // 파도 높이와 패턴 표시 + UpdateWaveVisualization(); + } + + private float GetCurrentSpeed() => movement.CurrentSpeed; + private float GetMaxSpeed() => movement.MaxSpeed; + private float GetRotationSpeed() => movement.CurrentRotationSpeed; + private Vector2 GetCurrentInput() => movement.CurrentInput; + + private void UpdateSpeedLine() + { + Vector3 start = transform.position + Vector3.up * 1.5f; + Vector3 end = start + transform.forward * ((GetCurrentSpeed() / GetMaxSpeed()) * debugLineLength * 2); + DrawLine(_speedLineRenderer, start, end); + } + + private void UpdateRotationSpeedLine() + { + Vector3 start = transform.position + Vector3.up * 1.2f; + // 각속도를 호로 표현 + if (Mathf.Abs(GetRotationSpeed()) > 0.1f) + { + Vector3[] arcPoints = new Vector3[10]; + float radius = debugLineLength * 1f; + float angleStep = GetRotationSpeed() * 1f / (arcPoints.Length - 1); + + for (int i = 0; i < arcPoints.Length; i++) + { + float angle = angleStep * i; + Vector3 point = start + Quaternion.Euler(0, angle, 0) * transform.forward * radius; + arcPoints[i] = point; + } + + _rotationSpeedLineRenderer.positionCount = arcPoints.Length; + _rotationSpeedLineRenderer.SetPositions(arcPoints); + } + else + { + _rotationSpeedLineRenderer.positionCount = 0; + } + } + + private void UpdateRotationDeltaLine() + { + float deltaAngle = 0f; + if (GetCurrentInput().magnitude > 0.1f) + { + Vector3 inputDirection = new Vector3(GetCurrentInput().x, 0, GetCurrentInput().y).normalized; + Quaternion targetRotation = Quaternion.LookRotation(inputDirection, Vector3.up); + deltaAngle = Quaternion.Angle(transform.rotation, targetRotation); + } + + Vector3 start = transform.position + Vector3.up * 1.2f; + // 각속도를 호로 표현 + if (Mathf.Abs(deltaAngle) > 0.1f) + { + Vector3[] arcPoints = new Vector3[10]; + float radius = debugLineLength * 1.05f; + float angleStep = deltaAngle * 1f / (arcPoints.Length - 1); + + for (int i = 0; i < arcPoints.Length; i++) + { + float angle = angleStep * i; + Vector3 point = start + Quaternion.Euler(0, angle, 0) * transform.forward * radius; + arcPoints[i] = point; + } + + _rotationDeltaLineRenderer.positionCount = arcPoints.Length; + _rotationDeltaLineRenderer.SetPositions(arcPoints); + } + else + { + _rotationDeltaLineRenderer.positionCount = 0; + } + } + + private void UpdateTiltLine() + { + Vector3 start = transform.position + Vector3.up * 1.5f; + Vector3 tiltDirection = movementVisual.MeshTransform.up; + DrawLine(_TiltLineRenderer, start, start + tiltDirection * (debugLineLength * 0.4f)); + } + + private void UpdateWaveVisualization() + { + // 현재 파도 높이 표시 + Vector3 waveStart = transform.position + Vector3.up * 1.5f - transform.forward * 1.5f; + Vector3 waveEnd = waveStart + Vector3.up * (GetCurrentWaveHeight() * debugLineLength); + DrawLine(_waveHeightLineRenderer, waveStart, waveEnd); + + // 파도 패턴 시각화 + Vector3[] wavePoints = new Vector3[_wavePatternLineRenderer.positionCount]; + float waveLength = debugLineLength * 2f; + + for (int i = 0; i < wavePoints.Length; i++) + { + float t = (float)i / (_wavePatternLineRenderer.positionCount - 1); + float x = t * waveLength - waveLength * 0.5f; + float currentSpeedByUnit = GetCurrentSpeed() / GetWaveUnitSpeed(); + currentSpeedByUnit = Mathf.Clamp01(currentSpeedByUnit); + float waveHeight = Mathf.Lerp(GetMinSpeedWaveHeight(), GetMaxSpeedWaveHeight(), currentSpeedByUnit); + float y = Mathf.Sin((GetWaveTime() + x) * GetBaseWaveFrequency()) * waveHeight; + + wavePoints[i] = transform.position + + Vector3.right * x + + Vector3.up * (y + 2f); // 높이 오프셋 + wavePoints[i] += Vector3.back * 3f + Vector3.down * 1f; + } + + _wavePatternLineRenderer.SetPositions(wavePoints); + } + + private float GetCurrentWaveHeight() => movementVisual.CurrentWaveHeight; + private float GetWaveUnitSpeed() => movementVisual.WaveUnitSpeed; + private float GetMinSpeedWaveHeight() => movementVisual.MinSpeedWaveHeight; + private float GetMaxSpeedWaveHeight() => movementVisual.MaxSpeedWaveHeight; + private float GetWaveTime() => movementVisual.WaveTime; + private float GetBaseWaveFrequency() => movementVisual.BaseWaveFrequency; + + private LineRenderer CreateLineRenderer(string name, Color color) + { + GameObject lineObj = new GameObject(name); + lineObj.transform.SetParent(transform); + LineRenderer line = lineObj.AddComponent(); + + line.startWidth = debugLineWidth; + line.endWidth = debugLineWidth; + 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 DrawLine(LineRenderer line, Vector3 start, Vector3 end) + { + if (line is null) return; + line.positionCount = 2; + line.SetPosition(0, start); + line.SetPosition(1, end); + } + } +#endif +} \ No newline at end of file diff --git a/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs.meta b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs.meta new file mode 100644 index 000000000..5d9d4fbda --- /dev/null +++ b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementDebug.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3d5c5f51b32b4633b887d096554c6cd9 +timeCreated: 1752575924 \ No newline at end of file diff --git a/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs new file mode 100644 index 000000000..914dd31ba --- /dev/null +++ b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs @@ -0,0 +1,224 @@ +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(); + + 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; + } +} \ No newline at end of file diff --git a/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs.meta b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs.meta new file mode 100644 index 000000000..e76d76bf4 --- /dev/null +++ b/Assets/0_Voyage/_Scripts/Ship/VoyagePlayerShipMovementVisual.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a09f8b932e18409aa7f5d2a221921f45 +timeCreated: 1752575398 \ No newline at end of file