namespace Sandbox; public sealed partial class PlayerController : Component { /// /// The object we're standing on. Null if we're standing on nothing. /// public GameObject GroundObject { get; set; } /// /// The collider component we're standing on. Null if we're standing nothing /// public Component GroundComponent { get; set; } /// /// If we're stnding on a surface this is it /// public Surface GroundSurface { get; set; } /// /// The friction property of the ground we're standing on. /// public float GroundFriction { get; set; } /// /// Are we standing on a surface that is physically dynamic /// public bool GroundIsDynamic { get; set; } TimeUntil _timeUntilAllowedGround = 0; /// /// Amount of time since this character was last on the ground /// public TimeSince TimeSinceGrounded { get; private set; } = 0; /// /// Amount of time since this character was last not on the ground /// public TimeSince TimeSinceUngrounded { get; private set; } = 0; /// /// Prevent being grounded for a number of seconds /// public void PreventGrounding( float seconds ) { _timeUntilAllowedGround = MathF.Max( _timeUntilAllowedGround, seconds ); UpdateGroundFromTraceResult( default ); } /// /// Lift player up and place a skin level above the ground /// internal void Reground( float stepSize ) { if ( !IsOnGround ) return; // Don't keep regrounding if we're not moving if ( Body.Sleeping ) return; var currentPosition = WorldPosition; float radiusScale = 1.0f; var tr = TraceBody( currentPosition + Vector3.Up * 1, currentPosition + Vector3.Down * stepSize, radiusScale, 0.5f ); while ( tr.StartedSolid ) { radiusScale = radiusScale - 0.1f; if ( radiusScale < 0.7f ) return; tr = TraceBody( currentPosition + Vector3.Up * 1, currentPosition + Vector3.Down * stepSize, radiusScale, 0.5f ); } if ( tr.StartedSolid ) { return; } if ( tr.Hit ) { var targetPosition = tr.EndPosition + Vector3.Up * 0.01f; var delta = currentPosition - targetPosition; if ( delta == Vector3.Zero ) return; WorldPosition = targetPosition; // when stepping down, clear out the gravity velocity to avoid // it thinking we're falling and building up like crazy if ( delta.z > 0.01f ) { var velocity = Body.Velocity; velocity.z = 0; Body.Velocity = velocity; } } } void CategorizeGround() { var groundVel = GroundVelocity.z; bool wasOnGround = IsOnGround; if ( !Mode.AllowGrounding ) { PreventGrounding( 0.1f ); UpdateGroundFromTraceResult( default ); return; } // ground is pushing us crazy, stop being grounded if ( groundVel > 250 ) { PreventGrounding( 0.3f ); UpdateGroundFromTraceResult( default ); return; } var velocity = Velocity - GroundVelocity; if ( _timeUntilAllowedGround > 0 || groundVel > 300 ) { UpdateGroundFromTraceResult( default ); return; } var from = WorldPosition + Vector3.Up * 4; var to = WorldPosition + Vector3.Down * 2; float radiusScale = 1; var tr = TraceBody( from, to, radiusScale, 0.5f ); while ( tr.StartedSolid || (tr.Hit && !Mode.IsStandableSurface( tr )) ) { radiusScale = radiusScale - 0.1f; if ( radiusScale < 0.7f ) { UpdateGroundFromTraceResult( default ); return; } tr = TraceBody( from, to, radiusScale, 0.5f ); } if ( tr.StartedSolid ) { UpdateGroundFromTraceResult( default ); return; } if ( !tr.StartedSolid && tr.Hit && Mode.IsStandableSurface( tr ) ) { UpdateGroundFromTraceResult( tr ); } else { UpdateGroundFromTraceResult( default ); } } void UpdateGroundFromTraceResult( SceneTraceResult tr ) { var wasGrounded = IsOnGround; var body = tr.Body; GroundObject = body?.GameObject; GroundComponent = body?.Component; GroundSurface = tr.Surface; GroundIsDynamic = true; if ( GroundObject is not null ) { TimeSinceGrounded = 0; _groundTransform = GroundObject.WorldTransform; GroundFriction = tr.Surface.Friction; if ( tr.Component is Collider collider ) { if ( collider.Friction.HasValue ) GroundFriction = collider.Friction.Value; GroundIsDynamic = collider.IsDynamic; } } else { TimeSinceUngrounded = 0; _groundTransform = default; } if ( wasGrounded != IsOnGround ) { UpdateBody(); } } }