using System; using System.Collections; using UnityEngine; using UnityEngine.InputSystem; using Vector2 = UnityEngine.Vector2; using Vector3 = UnityEngine.Vector3; namespace DDD { public class RestaurantPlayerMovement : RestaurantCharacterMovement { #region Fields private Rigidbody _rigidbody; private BoxCollider _boxCollider; private RestaurantPlayerDataSo _playerDataSo; private Vector3 _inputDirection; private Vector3 _currentDirection; private Vector3 _currentVelocity; private bool _isMoving; private bool _isDashing; private bool _isDashCooldown; private bool _isInitialized; public Action OnMoving; public Action OnDashing; #if UNITY_EDITOR private MovementDebugVisualizer _debugVisualizer; #endif #endregion #region Unity Lifecycle private void Awake() { InitializeComponents(); } private async void Start() { await InitializePlayerData(); } private void FixedUpdate() { if (!_isInitialized) return; HandleMovement(); #if UNITY_EDITOR HandleDebugVisualization(); #endif } private void OnDestroy() { UnsubscribeFromInputEvents(); } #endregion #region Initialization private void InitializeComponents() { _rigidbody = GetComponent(); _boxCollider = GetComponent(); #if UNITY_EDITOR _debugVisualizer = new MovementDebugVisualizer(transform); #endif } private async System.Threading.Tasks.Task InitializePlayerData() { try { _playerDataSo = await AssetManager.LoadAsset(DataConstants.RestaurantPlayerDataSo); SubscribeToInputEvents(); _isInitialized = true; } catch (Exception e) { Debug.LogError($"Player data load failed\n{e}"); } } private void SubscribeToInputEvents() { _playerDataSo.MoveActionReference.action.performed += OnMove; _playerDataSo.MoveActionReference.action.canceled += OnMove; _playerDataSo.DashActionReference.action.performed += OnDash; } private void UnsubscribeFromInputEvents() { if (!_playerDataSo) return; _playerDataSo.MoveActionReference.action.performed -= OnMove; _playerDataSo.MoveActionReference.action.canceled -= OnMove; _playerDataSo.DashActionReference.action.performed -= OnDash; } #endregion #region Movement private void HandleMovement() { if (CanMove()) { Move(); } } public override bool CanMove() { return base.CanMove() && _playerDataSo.IsMoveEnabled && !_isDashing; } private void Move() { SetCurrentDirection(_inputDirection); UpdateMovementState(); UpdateVelocity(); ApplyVelocity(); } private void UpdateMovementState() { _isMoving = _inputDirection != Vector3.zero; OnMoving?.Invoke(_isMoving); } private void UpdateVelocity() { if (_isMoving) { Vector3 slideDirection = GetSlideAdjustedDirection(_inputDirection.normalized); Vector3 targetVelocity = slideDirection * _playerDataSo.MoveSpeed; _currentVelocity = Vector3.MoveTowards(_currentVelocity, targetVelocity, _playerDataSo.Acceleration * Time.fixedDeltaTime); } else { _currentVelocity = Vector3.MoveTowards(_currentVelocity, Vector3.zero, _playerDataSo.Deceleration * Time.fixedDeltaTime); } } private void ApplyVelocity() { _rigidbody.linearVelocity = _currentVelocity; } #endregion #region Dash private void OnDash(InputAction.CallbackContext context) { if (CanDash()) { StartCoroutine(DashCoroutine()); } } private bool CanDash() => _playerDataSo.IsDashEnabled && !_isDashing && !_isDashCooldown; private IEnumerator DashCoroutine() { StartDash(); yield return new WaitForSeconds(_playerDataSo.DashTime); EndDash(); yield return new WaitForSeconds(_playerDataSo.DashCooldown); ResetDashCooldown(); } private void StartDash() { _isDashing = true; _isDashCooldown = true; OnDashing?.Invoke(_playerDataSo.DashTime); Vector3 slideDashDirection = GetSlideAdjustedDirection(_currentDirection.normalized); _rigidbody.linearVelocity = slideDashDirection * _playerDataSo.DashSpeed; } private void EndDash() { _isDashing = false; } private void ResetDashCooldown() { _isDashCooldown = false; } #endregion #region Public Methods private void SetCurrentDirection(Vector3 normalDirection) { if (_inputDirection == Vector3.zero) return; _currentDirection = normalDirection; } public Vector3 GetCurrentDirection() => _currentDirection; #endregion #region Input Handling private void OnMove(InputAction.CallbackContext context) { Vector2 movementInput = context.ReadValue(); _inputDirection = new Vector3(movementInput.x, 0f, movementInput.y); } #endregion #region Collision Handling private Vector3 GetSlideAdjustedDirection(Vector3 inputDirection) { if (!TryGetCollisionInfo(inputDirection, out RaycastHit hit)) return inputDirection; Vector3 slide = Vector3.ProjectOnPlane(inputDirection, hit.normal).normalized; float slideFactor = CalculateSlideFactor(inputDirection, hit.normal); return slideFactor < _playerDataSo.MinSlideFactorThreshold ? Vector3.zero : slide * slideFactor; } private bool TryGetCollisionInfo(Vector3 direction, out RaycastHit hit) { Vector3 origin = _boxCollider.bounds.center; Vector3 halfExtents = _boxCollider.bounds.extents; float distance = Mathf.Min(_boxCollider.bounds.size.x, _boxCollider.bounds.size.z); int layerMask = ~_playerDataSo.IgnoreSlidingLayerMask; return Physics.BoxCast(origin, halfExtents * _playerDataSo.BoxCastExtentScale, direction, out hit, transform.rotation, distance, layerMask, QueryTriggerInteraction.Ignore); } private float CalculateSlideFactor(Vector3 direction, Vector3 normal) { float dot = Vector3.Dot(direction.normalized, normal); return Mathf.Pow(1f - Mathf.Abs(dot), _playerDataSo.SlidingThreshold); } #endregion #if UNITY_EDITOR private void HandleDebugVisualization() { if (_playerDataSo.IsDrawLineDebug) { _debugVisualizer.UpdateVisualization(transform.position, _inputDirection, _currentVelocity, _playerDataSo); } } #endif } #if UNITY_EDITOR public class MovementDebugVisualizer { private const string InputDebugLineRenderer = "DebugLine_Input"; private const string VelocityDebugLineRenderer = "DebugLine_Velocity"; private const string SpriteDefaultShader = "Sprites/Default"; private LineRenderer _inputLineRenderer; private LineRenderer _velocityLineRenderer; private readonly Transform _transform; public MovementDebugVisualizer(Transform transform) { _transform = transform; } public void UpdateVisualization(Vector3 position, Vector3 inputDirection, Vector3 currentVelocity, RestaurantPlayerDataSo playerDataSo) { UpdateInputLine(position, inputDirection, playerDataSo); UpdateVelocityLine(position, currentVelocity, playerDataSo); } private void UpdateInputLine(Vector3 origin, Vector3 inputDirection, RestaurantPlayerDataSo playerDataSo) { if (inputDirection != Vector3.zero) { var target = origin + inputDirection.normalized * playerDataSo.InputLineLength; if (_inputLineRenderer == null) { _inputLineRenderer = CreateDebugLineRenderer( InputDebugLineRenderer, playerDataSo.InputLineSortingOrder, playerDataSo.InputLineWidth, Color.blue); } UpdateLineRenderer(_inputLineRenderer, origin, target); _inputLineRenderer.enabled = true; } else if (_inputLineRenderer != null) { _inputLineRenderer.enabled = false; } } private void UpdateVelocityLine(Vector3 origin, Vector3 velocity, RestaurantPlayerDataSo playerDataSo) { float speed = velocity.magnitude; if (speed > playerDataSo.VelocityMinThreshold) { Vector3 target = origin + velocity.normalized * (speed * playerDataSo.VelocityLineScale); if (_velocityLineRenderer == null) { _velocityLineRenderer = CreateDebugLineRenderer( VelocityDebugLineRenderer, playerDataSo.VelocityLineSortingOrder, playerDataSo.VelocityLineWidth, Color.red); } UpdateLineRenderer(_velocityLineRenderer, origin, target); _velocityLineRenderer.enabled = true; } else if (_velocityLineRenderer != null) { _velocityLineRenderer.enabled = false; } } private LineRenderer CreateDebugLineRenderer(string name, int sortingIndex, float width, Color color) { Transform existing = _transform.Find(name); if (existing != null) { var lr = existing.GetComponent(); if (lr != null) { lr.startColor = lr.endColor = color; return lr; } } var newGameObject = new GameObject(name); newGameObject.transform.SetParent(_transform); newGameObject.transform.localPosition = Vector3.zero; var lineRenderer = newGameObject.AddComponent(); lineRenderer.positionCount = 2; lineRenderer.material = new Material(Shader.Find(SpriteDefaultShader)); lineRenderer.sortingOrder = sortingIndex; lineRenderer.startWidth = lineRenderer.endWidth = width; lineRenderer.startColor = lineRenderer.endColor = color; lineRenderer.useWorldSpace = true; return lineRenderer; } private void UpdateLineRenderer(LineRenderer lr, Vector3 start, Vector3 end) { lr.SetPosition(0, start); lr.SetPosition(1, end); } } #endif }