using Godot; using System.Collections; using System.Collections.Generic; using Godot.Collections; using Rokojori.Extensions; namespace Rokojori { [Tool] [GlobalClass] public partial class CharacterMovement:CharacterControllerAction { [Export] public float onFloorMultiply = 1f; [Export] public float inAirMultiply = 0.01f; [Export] public CameraTargetOffset cameraTargetOffset; [ExportGroup( "Direction Source" )] [Export] public Node3D directionSource; public enum DirectionProcessing { None, Zero_Y_And_Normalize, Project_On_TransformPlane } [ExportGroup("Movement")] [Export] public float moveSpeed = 10f; [Export] public float minSpeed = 0f; [Export] public CharacterMovementType controllerMovementType; [Export] public CharacterMovementType mouseKeyboardMovementType; [Export] public CharacterMovementType currentMovementType; [Export] public Smoothing onFloorMovementSmoothing = new FrameSmoothing(); [Export] public Smoothing inAirMovementSmoothing = new FrameSmoothing(); [ExportGroup("Deceleration")] [Export] public float absoluteDecelerationSpeedMinimum = 1; [Export(PropertyHint.Range, "0,100,0.1,suffix:%")] public float relativeDecelerationSpeedOffset = 0; [Export] public Smoothing onFloorDecelerationSmoothing = new FrameSmoothing(); [Export] public Smoothing inAirDecelerationSmoothing = new FrameSmoothing(); [Export] public CharacterMovementType[] deviceMovementTypes = []; [ExportGroup( "Manual Movement Type Switch" )] [Export] public Sensor movementTypeSwitch; [Export] public CharacterMovementType switchedMovementType; bool _onFloor = false; bool _moving = false; [Export] public float movingSpeedTreshold = 0.01f; [Export] public float currentSpeed = 0f; [Export] public Action onStartedMoving; [Export] public Action onStoppedMoving; public bool isOnFloor => _onFloor; public bool isMoving => _moving; [ExportGroup( "Rotation" )] [Export] public bool adjustRotation = true; [Export] public Curve forwardToRotationSmoothingFrames = MathX.Curve( 0, 1 ); [Export(PropertyHint.Range, "0,600")] public int airRotationSmoothingFrames = 120; FrameSmoothing rotationSmoothing = new FrameSmoothing(); protected CharacterMovementData characterMovementData = new CharacterMovementData(); [Export] public bool lookAlwaysForward = false; [Export] public Smoothing lookAlwaysForwardSmoothing; Vector3 _smoothedMovement; public Vector3 smoothedMovement => _smoothedMovement; int _frameID = 0; protected override void _OnTrigger() { var onFloor = isGrounded; _onFloor = onFloor; characterMovementData.Reset( this ); var sm = Unique.Get(); if ( sm == null ) { return; } var activeDevice = sm.lastActiveDevice; currentMovementType = deviceMovementTypes.FindNonNull( d => d.deviceSelector.Selects( activeDevice ) ); if ( movementTypeSwitch != null && movementTypeSwitch.isActive && switchedMovementType != null ) { currentMovementType = switchedMovementType; } if ( currentMovementType == null ) { // this.LogInfo( "No device found:", activeDevice == null ? "null" : activeDevice.GetType().Name ); return; } currentMovementType.ProcessMovement( characterMovementData ); if ( cameraTargetOffset != null ) { cameraTargetOffset.SetMovingForward( characterMovementData.isMovingForward ? 1f : 0f ); } var movementMultiply = onFloor ? onFloorMultiply : inAirMultiply; characterMovementData.movement *= movementMultiply; if ( adjustRotation ) { var nextRotation = Math3D.LookRotation( characterMovementData.forwardDirection ); var speed = MathX.Clamp01( characterMovementData.movement.Length() / moveSpeed ); if ( speed > 0 ) { Quaternion rotation; if ( onFloor ) { rotationSmoothing.frames = Mathf.Round( forwardToRotationSmoothingFrames.Sample( speed ) ); } else { rotationSmoothing.frames = airRotationSmoothingFrames; } rotation = rotationSmoothing.Smooth( nextRotation, controller.delta ); body.SetGlobalQuaternion( rotation ); } } if ( lookAlwaysForward ) { var nextRotation = directionSource.GlobalYawQuaterntion(); body.SetGlobalQuaternion( Smoothing.Apply( lookAlwaysForwardSmoothing, nextRotation, controller.delta ) ); } // var smoothedMovement = Vector3.Zero; var movement = characterMovementData.movement; var delta = controller.delta; if ( onFloor || inAirMovementSmoothing == null ) { _smoothedMovement = ComputeMovement( movement, onFloorMovementSmoothing, onFloorDecelerationSmoothing, delta ); } else { _smoothedMovement = ComputeMovement(movement, inAirMovementSmoothing, inAirDecelerationSmoothing, delta ); } var wasMoving = _moving; currentSpeed = _smoothedMovement.Length(); // this.LogInfo( _frameID, currentMovementType.ResourceName, " >> speed:", currentSpeed._FF(), "movement:", movement, "delta:", delta._FFF() ); _frameID ++; _moving = currentSpeed > movingSpeedTreshold; if ( wasMoving != _moving ) { if ( _moving ) { TriggerSafe( onStartedMoving ); } else { TriggerSafe( onStoppedMoving ); } } Velocity( _smoothedMovement, onFloor ); } bool IsDecelerating( float newSpeed ) { if ( newSpeed > currentSpeed ) { return false; } var treshold = absoluteDecelerationSpeedMinimum + currentSpeed * relativeDecelerationSpeedOffset; return newSpeed < treshold; } Vector3 ComputeMovement( Vector3 nextMovement, Smoothing normal, Smoothing decelerating, float delta ) { if ( decelerating == null ) { return normal.Smooth( nextMovement, delta ); } var nextSpeed = nextMovement.Length(); if ( ! IsDecelerating( nextSpeed )) { return normal.Smooth( nextMovement, delta ); } decelerating.SetCurrent( normal.currentVector3 ); var value = decelerating.Smooth( nextMovement, delta ); normal.SetCurrent( decelerating.currentVector3 ); return value; } } }