using Extensions; using UnityEngine; using NaughtyAttributes; using Ktyl.Util; using System.Collections; using UnityEngine.UI; public class PlayerController : MonoBehaviour { [Header("Config")] [SerializeField] [Expandable] private PlayerMovementSettings _movementSettings; [Header("References")] [SerializeField] private PlayerInputHandler _inputHandler; [SerializeField] private CharacterController _controller; [SerializeField] private PlayerAnimationController _animController; [SerializeField] private PlayerPowers _powers; [SerializeField] private Transform _graphics; [SerializeField] private Renderer[] _renderers; [Header( "Debug" )] [SerializeField] private Color _groundedColor; [SerializeField] private Color _jumpColor; [SerializeField] private Color _boostColor; [SerializeField] private Color _exhaustedColor; [ShowNonSerializedField] private Vector2 _surfVelocity; [ShowNonSerializedField] private Vector2 _overrideDelta; [ShowNonSerializedField] private float _lookAngle; [ShowNonSerializedField] private float _yVelocity; [ShowNonSerializedField] private bool _grounded; [ShowNativeProperty] private bool Still => Mathf.Approximately(_inputHandler?.InputState?.Move.Value.sqrMagnitude ?? 0f, 0f); [ShowNonSerializedField] private float _boostTime; [ShowNonSerializedField] private float _fallTime; public bool CanJump => _grounded || _fallTime < _movementSettings.CoyoteTime; private const float PI = Mathf.PI; private const float TAU = Mathf.PI * 2f; private const float HALF_PI = Mathf.PI / 2f; private float timeSinceStop; [Header("Power Freeze")] [SerializeField] private SerialFloat objectTimeScale; [SerializeField] private SerialFloat frozenTime; [SerializeField] private DialogueSystem _dialogueSystem; [SerializeField] private string _freezeFirstUseDialogueKey; [SerializeField] private GameObject _freezeVfx; [SerializeField] [FMODUnity.EventRef] private string _freezeSfx; private enum JumpState { None, Jump, Boost, Exhausted, COUNT, } [ShowNonSerializedField] private JumpState _jumpState; private void Start() { _freezeVfx.SetActive( false ); } private Color GetDebugColor() { switch ( _jumpState ) { case JumpState.None: return _groundedColor; case JumpState.Jump: return _jumpColor; case JumpState.Boost: return _boostColor; case JumpState.Exhausted: return _exhaustedColor; } return Color.red; } private float normaliseAngle( float angle ) { while ( angle > PI ) { angle -= TAU; } while ( angle < -PI ) { angle += TAU; } return angle; } private void Land() { _powers.Blink.Reset(); _powers.Boost.Reset(); } private void UpdateLook( float dt ) { if ( _surfVelocity.sqrMagnitude > 0.01f ) { float targetAngle = -Vector2.SignedAngle( Vector2.up, _surfVelocity.normalized ) * Mathf.Deg2Rad; float delta = targetAngle - _lookAngle; float ls = _movementSettings.LookSpeed * dt; _lookAngle += Mathf.Clamp( normaliseAngle(delta), -ls, ls ); _lookAngle = normaliseAngle( _lookAngle ); } } private void UpdateBlink( float dt ) { if ( _powers.Blink.CanConsume && _inputHandler.InputState.Blink.GetDown() ) { _powers.Blink.Consume(); Vector2 blinkDirection = Vector2.up.Rotate( -_lookAngle ); _overrideDelta += blinkDirection * _movementSettings.BlinkDistance; _surfVelocity += blinkDirection * _movementSettings.BlinkVelocity; } } private void UpdateFreeze( float dt) { if ( _powers.Freeze.CanConsume && _inputHandler.InputState.Freeze.GetRawValue() ) { _powers.Freeze.Consume(); objectTimeScale.Value = 0f; _freezeVfx.SetActive( true ); FMODUnity.RuntimeManager.PlayOneShot( _freezeSfx ); Debug.Log( "[PlayerController] time frozen" ); } if ( _powers.Freeze.timeSinceConsume > frozenTime && TimeIsFrozen ) { UnfreezeTime(); _dialogueSystem.PlayLine( _freezeFirstUseDialogueKey, true ); _freezeVfx.SetActive( false ); Debug.Log( "[PlayerController] time unfrozen" ); } } public bool TimeIsFrozen => objectTimeScale.AsBool; private void UnfreezeTime() { objectTimeScale.Value = 1f; } private void FreezeReset() { _powers.Freeze.Reset(); } private void UpdateJump(float dt) { _fallTime = _grounded ? 0f : (_fallTime + dt); switch ( _jumpState ) { case JumpState.None: if ( CanJump ) { if ( _inputHandler.InputState.Jump.GetDown() ) { _grounded = false; _jumpState = JumpState.Jump; _yVelocity = Mathf.Sqrt( 2f * _movementSettings.JumpHeight * _movementSettings.UpGravity ); // always make sure we can blink once per jump _powers.Blink.Reset(); } } else { _jumpState = JumpState.Jump; } break; case JumpState.Jump: if ( _powers.Boost.CanConsume && _inputHandler.InputState.Jump.GetDown( consume: false ) ) { _powers.Boost.Consume(); _boostTime = 0f; _jumpState = JumpState.Boost; if ( _yVelocity < _movementSettings.MinBoostVelocity ) { _yVelocity = _movementSettings.MinBoostVelocity; } } break; case JumpState.Boost: if ( _inputHandler.InputState.Jump.GetRawValue() ) { _yVelocity += _movementSettings.BoostForce * dt; _boostTime += dt; if ( _boostTime > _movementSettings.BoostTime ) { _jumpState = JumpState.Exhausted; } } break; default: break; } } private void UpdateGravity( float dt ) { if ( _grounded ) { _yVelocity = _movementSettings.GroundedYVelocity; _jumpState = JumpState.None; } else { if ( _yVelocity > 0f && _jumpState == JumpState.Jump ) { _yVelocity -= ( _movementSettings.UpGravity * dt ); } else { _yVelocity -= ( _movementSettings.DownGravity * dt ); } } } private void UpdateDrag( float dt ) { Vector2 drag = -_surfVelocity; float coefficient = _grounded ? ( Still ? _movementSettings.StillDragCoefficient : _movementSettings.DragCoefficient ) : ( _jumpState == JumpState.Boost ? _movementSettings.BoostDragCoefficient : _movementSettings.AirDragCoefficient ); _surfVelocity += drag * (dt * coefficient); } private void UpdateMovement( float dt ) { Vector2 moveVector = _inputHandler.InputState.Move.Value; _surfVelocity += moveVector * (_movementSettings.BaseMovementForce * dt); } private void UpdateDebug() { Color debugColor = GetDebugColor(); for ( int i = 0; i < _renderers.Length; ++i ) { _renderers[ i ].material.color = debugColor; } } private void UpdateAnims() { var animParams = new PlayerAnimationController.AnimationParams(); animParams.IsWalking = _controller.velocity.sqrMagnitude > 0.01f; animParams.InJumpState = _jumpState != JumpState.None; animParams.InBoostState = _jumpState == JumpState.Boost || _jumpState == JumpState.Exhausted; _animController.ProcessAnimUpdate( animParams ); } private void FixedUpdate() { float dt = Time.fixedDeltaTime; _overrideDelta = Vector2.zero; if ( !_grounded && _controller.isGrounded ) { Land(); } _grounded = _controller.isGrounded; _powers.UpdatePowers( dt, _grounded ); UpdateJump( dt ); UpdateLook( dt ); UpdateBlink( dt ); UpdateFreeze( dt ); UpdateGravity( dt ); UpdateDrag( dt ); UpdateMovement( dt ); UpdateDebug(); UpdateAnims(); _controller.Move( new Vector3( _surfVelocity.x * dt + _overrideDelta.x, _yVelocity * dt, _surfVelocity.y * dt + _overrideDelta.y ) ); _graphics.rotation = Quaternion.Euler( 0f, _lookAngle * Mathf.Rad2Deg, 0f ); } }