Code/PlayerMovement.cs
using Sandbox;
namespace XMovement;
public partial class PlayerMovement : Component
{
/// <summary>
/// The current gravity.
/// </summary>
[Property, Group( "Config" )] public Vector3 Gravity { get; set; } = new Vector3( 0, 0, 800 );
/// <summary>
/// How much friction does the player have?
/// </summary>
[Property, Group( "Friction" )] public float BaseFriction { get; set; } = 4.0f;
/// <summary>
/// The speed at which we fully come to a stop.
/// </summary>
[Property, Group( "Friction" )] public float StopSpeed { get; set; } = 100.0f;
/// <summary>
/// Can we control our movement in the air?
/// </summary>
[Property, Group( "Config" )] public float AirControl { get; set; } = 30f;
/// <summary>
/// Maximum wish speed used for the dot-product / addspeed check in air.
/// Equivalent to Source's GetAirSpeedCap() (hardcoded 30 in HL2/CS).
/// Keeping this low is what makes strafing feel like Source — you can always
/// add velocity sideways because the capped component against your current
/// velocity direction stays small.
/// </summary>
[Property, Group( "Config" )] public float AirSpeedCap { get; set; } = 30f;
[Property, Group( "Acceleration" )] public float AirAcceleration { get; set; } = 10f;
[Property, Group( "Acceleration" )] public float BaseAcceleration { get; set; } = 10;
[Property] public MovementFrequencyMode MovementFrequency { get; set; } = MovementFrequencyMode.PerFixedUpdate;
public enum MovementFrequencyMode
{
PerFixedUpdate,
PerUpdate
}
[Sync] public Vector3 WishVelocity { get; set; }
protected override void OnStart()
{
base.OnStart();
Tags.Add( "player" );
CreateShadowObjects();
}
public void PrepareMovement()
{
UpdateFromSimulatedShadow();
}
/// <summary>
/// Move a character, with this velocity
/// </summary>
public void Move( bool withWishVelocity = true, bool withGravity = true, float frictionOverride = 0 )
{
RestoreGroundPos();
ApplyAcceleration();
// Start gravity
if ( !IsOnGround && withGravity )
Velocity -= Gravity * Time.Delta * 0.5f;
if ( withWishVelocity )
{
if ( IsOnGround )
{
Accelerate( WishVelocity, BaseAcceleration );
}
else
{
// Source-correct air acceleration: full wish velocity passed in,
// AirAccelerate internally caps wishspd to AirSpeedCap for the
// dot-product check while using full speed for the accel formula.
AirAccelerate( WishVelocity, AirAcceleration );
}
}
if ( frictionOverride > 0 )
{
Velocity = ApplyFriction( Velocity, frictionOverride, StopSpeed );
}
else if ( IsOnGround )
{
Velocity = ApplyFriction( Velocity, GetFriction(), StopSpeed );
}
if ( TryUnstuck() )
return;
ClipVelocityToOutOfBounds();
if ( IsOnGround )
{
Move( true );
}
else
{
Move( false );
}
if ( IsOnGround ) StayOnGround();
CategorizePosition();
HandleOutOfBounds();
// Finish gravity
if ( !IsOnGround && withGravity )
Velocity -= Gravity * Time.Delta * 0.5f;
ResetSimulatedShadow();
SaveGroundPos();
PreviousPosition = WorldPosition;
}
private void ApplyAcceleration()
{
if ( !IsOnGround ) Acceleration = AirAcceleration;
else Acceleration = BaseAcceleration;
}
/// <summary>
/// Get the current friction.
/// </summary>
/// <returns></returns>
private float GetFriction()
{
return BaseFriction;
}
}