namespace Sandbox.Movement; partial class MoveMode { /// /// Update the animator which is available at Controller.Renderer. /// public virtual void UpdateAnimator( SkinnedModelRenderer renderer ) { // Animation parameters OnUpdateAnimatorVelocity( renderer ); OnUpdateAnimatorState( renderer ); OnUpdateAnimatorLookDirection( renderer ); UpdateRotationSpeed( renderer ); // Renderer transform OnRotateRenderBody( renderer ); } Vector3.SmoothDamped smoothedMove = new Vector3.SmoothDamped( 0, 0, 0.5f ); Vector3.SmoothDamped smoothedWish = new Vector3.SmoothDamped( 0, 0, 0.5f ); Vector3.SmoothDamped smoothedSkid = new Vector3.SmoothDamped( 0, 0, 0.5f ); /// /// /// Sets animation parameters on based on the current /// and . /// /// protected virtual void OnUpdateAnimatorVelocity( SkinnedModelRenderer renderer ) { var rot = renderer.WorldRotation; var vel = Controller.Velocity; var wishVel = Controller.WishVelocity; // skid { var skidAmount = 0.5f; // multiplier for moving skid var pushSkidAmount = 1.0f; // multiplier for sliding down a slope when standing still var skidDelay = 0.2f; // in seconds, longer means bigger gap between the velocities, more skidding // smooth version of our velocity var smoothed = vel; { smoothedMove.Target = smoothed; smoothedMove.SmoothTime = skidDelay; smoothedMove.Update( Time.Delta ); smoothed = smoothedMove.Current; } // skid is the difference between our old velocity and our current velocity var skid = (smoothed - vel) * skidAmount; // if we're standing still, use our actual velity as the skid skid = Vector3.Lerp( skid, vel * pushSkidAmount, wishVel.Length.Remap( 100, 0, 0, 1 ) ); // smooth our skidders smoothedSkid.Target = skid; smoothedSkid.SmoothTime = 0.5f; smoothedSkid.Update( Time.Delta ); skid = smoothedSkid.Current; // convert to model space skid = GetLocalVelocity( rot, skid ); renderer.Set( "skid_x", (skid.x / 400.0f) ); renderer.Set( "skid_y", (skid.y / 400.0f) ); } // move the legs { var smoothed = wishVel; { smoothedWish.Target = smoothed; smoothedWish.SmoothTime = 0.6f; smoothedWish.Update( Time.Delta ); smoothed = smoothedWish.Current; // Stop walking if we're too slow smoothed = ApplyDeadZone( smoothed, 10 ); } smoothed = GetLocalVelocity( rot, smoothed ); renderer.Set( "move_direction", GetAngle( smoothed ) ); renderer.Set( "move_speed", smoothed.Length ); renderer.Set( "move_groundspeed", smoothed.WithZ( 0f ).Length ); renderer.Set( "move_x", smoothed.x ); renderer.Set( "move_y", smoothed.y ); renderer.Set( "move_z", smoothed.z ); } // the player's wish { var local = GetLocalVelocity( rot, wishVel ); renderer.Set( "wish_direction", GetAngle( local ) ); renderer.Set( "wish_speed", wishVel.Length ); renderer.Set( "wish_groundspeed", wishVel.WithZ( 0f ).Length ); renderer.Set( "wish_x", local.x ); renderer.Set( "wish_y", local.y ); renderer.Set( "wish_z", local.z ); } } #region OnUpdateAnimatorVelocity Helpers private static Vector3 ApplyDeadZone( Vector3 velocity, float minimum ) { return velocity.IsNearlyZero( minimum ) ? 0f : velocity; } private static Vector3 GetLocalVelocity( Rotation rotation, Vector3 worldVelocity ) { // TODO: this could be rotation.Inverse * worldVelocity var forward = rotation.Forward.Dot( worldVelocity ); var sideward = rotation.Right.Dot( worldVelocity ); return new Vector3( forward, sideward, worldVelocity.z ); } private static float GetAngle( Vector3 localVelocity ) { return MathF.Atan2( localVelocity.y, localVelocity.x ).RadianToDegree().NormalizeDegrees(); } #endregion /// /// Sets animation parameters on describing the movement style, like /// swimming, falling, or ducking. /// protected virtual void OnUpdateAnimatorState( SkinnedModelRenderer renderer ) { renderer.Set( "sit", 0 ); renderer.Set( "b_swim", Controller.IsSwimming ); renderer.Set( "b_climbing", Controller.IsClimbing ); renderer.Set( "b_grounded", Controller.IsOnGround || Controller.IsClimbing ); var duck = Controller.Headroom.Remap( 25, 0, 0, 0.5f, true ); if ( Controller.IsDucking ) { duck *= 3.0f; duck += 1.0f; } renderer.Set( "duck", duck ); } /// /// Set animation parameters on to look towards . /// protected virtual void OnUpdateAnimatorLookDirection( SkinnedModelRenderer renderer ) { var eyesForward = Controller.EyeTransform.Forward; renderer.SetLookDirection( "aim_eyes", eyesForward, Controller.AimStrengthEyes ); renderer.SetLookDirection( "aim_head", eyesForward, Controller.AimStrengthHead ); renderer.SetLookDirection( "aim_body", eyesForward, Controller.AimStrengthBody ); } /// /// Updates the of . /// protected virtual void OnRotateRenderBody( SkinnedModelRenderer renderer ) { var eyeAngles = Controller.EyeTransform.Rotation.Angles(); var targetAngle = Rotation.FromYaw( eyeAngles.yaw ); var velocity = Controller.WishVelocity.WithZ( 0 ); var rotateDifference = renderer.WorldRotation.Distance( targetAngle ); var oldRotation = renderer.WorldRotation; // We're over the limit - snap it if ( rotateDifference > Controller.RotationAngleLimit ) { var delta = 0.999f - Controller.RotationAngleLimit / rotateDifference; var newRotation = Rotation.Lerp( renderer.WorldRotation, targetAngle, delta ); renderer.WorldRotation = newRotation; } // Otherwise only rotate while moving if ( velocity.Length > 10 ) { // TODO: frame rate dependent renderer.WorldRotation = Rotation.Slerp( renderer.WorldRotation, targetAngle, Time.Delta * 2.0f * Controller.RotationSpeed * velocity.Length.Remap( 0, 100 ) ); } AddRotationSpeed( oldRotation, renderer.WorldRotation ); } private const float RotationSpeedUpdatePeriod = 0.1f; private float _animRotationSpeed; private TimeSince _timeSinceRotationSpeedUpdate; private void AddRotationSpeed( Rotation oldRotation, Rotation newRotation ) { // TODO: this assumes we're always working with exactly one Renderer var oldYaw = oldRotation.Angles().yaw; var newYaw = newRotation.Angles().yaw; // TODO: why is this backwards? var deltaYaw = MathX.DeltaDegrees( newYaw, oldYaw ); _animRotationSpeed = (_animRotationSpeed + deltaYaw).Clamp( -90f, 90f ); } private void UpdateRotationSpeed( SkinnedModelRenderer renderer ) { if ( _timeSinceRotationSpeedUpdate < RotationSpeedUpdatePeriod ) return; // TODO: why 5? renderer.Set( "move_rotationspeed", _animRotationSpeed * 5f ); _timeSinceRotationSpeedUpdate = 0f; _animRotationSpeed = 0f; } }