Files
sbox-public/engine/Sandbox.Engine/Scene/Components/Game/PlayerController/PlayerController.Ground.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

199 lines
4.5 KiB
C#

namespace Sandbox;
public sealed partial class PlayerController : Component
{
/// <summary>
/// The object we're standing on. Null if we're standing on nothing.
/// </summary>
public GameObject GroundObject { get; set; }
/// <summary>
/// The collider component we're standing on. Null if we're standing nothing
/// </summary>
public Component GroundComponent { get; set; }
/// <summary>
/// If we're stnding on a surface this is it
/// </summary>
public Surface GroundSurface { get; set; }
/// <summary>
/// The friction property of the ground we're standing on.
/// </summary>
public float GroundFriction { get; set; }
/// <summary>
/// Are we standing on a surface that is physically dynamic
/// </summary>
public bool GroundIsDynamic { get; set; }
TimeUntil _timeUntilAllowedGround = 0;
/// <summary>
/// Amount of time since this character was last on the ground
/// </summary>
public TimeSince TimeSinceGrounded { get; private set; } = 0;
/// <summary>
/// Amount of time since this character was last not on the ground
/// </summary>
public TimeSince TimeSinceUngrounded { get; private set; } = 0;
/// <summary>
/// Prevent being grounded for a number of seconds
/// </summary>
public void PreventGrounding( float seconds )
{
_timeUntilAllowedGround = MathF.Max( _timeUntilAllowedGround, seconds );
UpdateGroundFromTraceResult( default );
}
/// <summary>
/// Lift player up and place a skin level above the ground
/// </summary>
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();
}
}
}