Partial FourWheelVehicle class for driver input. Converts throttle, brake, steering, boost and handbrake into VehicleControlParams, with speed-sensitive steering and throttle reduction while turning.
using static VehicleMath;
/// <summary>
/// The driver's keys, turned into throttle, brake, steering, boost and handbrake controls.
/// </summary>
public sealed partial class FourWheelVehicle
{
/// <summary>
/// Slower than this (in/s) the car counts as stopped, and the throttle drives rather than brakes.
/// </summary>
private const float SpeedThrottleAsBrake = 2.0f;
/// <summary>
/// How fast the brake comes on, per second. Twice as fast braking with forward while rolling back.
/// </summary>
private const float BrakeRate = 1.5f;
private const float BrakeBackForwardScalar = 2.0f;
private VehicleControlParams _controls = new() { Handbrake = true };
private bool _throttleDown;
/// <summary>
/// Throttle held with the handbrake on: revving the engine in neutral.
/// </summary>
private bool _freeRevving;
private struct DriverInput
{
/// <summary>
/// The pedals, 0 to 1. Keys are all or nothing, a gamepad's triggers anything between.
/// </summary>
public float Forward;
public float Back;
/// <summary>
/// Where to steer, -1 full left to 1 full right. Keys are full lock, a gamepad's stick anything between.
/// </summary>
public float Steering;
public bool Boost;
public bool Handbrake;
}
private void ResetControls()
{
_controls = new VehicleControlParams { Handbrake = true };
_throttleDown = false;
_freeRevving = false;
}
/// <summary>
/// The boost is burning.
/// </summary>
public bool IsBoosting() => (_state.BoostDelay - Engine.BoostDelay) > 0.0f;
/// <summary>
/// Recharging from a boost, so another can't start yet.
/// </summary>
private bool IsBoostRecovering => _state.BoostTimeLeft != 100 && _state.BoostTimeLeft != 0;
/// <summary>
/// Let go of the steering: it returns to centre, slower the faster we're going.
/// </summary>
private void SteeringRest( float carSpeed, float frameTime )
{
var steeringRate = RemapValClamped( carSpeed, Steering.SpeedSlow, Steering.SpeedFast,
Steering.SteeringRestRateSlow, Steering.SteeringRestRateFast );
_controls.Steering = Approach( 0, _controls.Steering, steeringRate * frameTime );
}
/// <summary>
/// Steering towards where the driver's steering (full lock on the keys), slower the faster we're going - but snapping
/// back through the centre when changing direction or easing off, quicker on the brakes and while boosting.
/// </summary>
private void SteeringTurn( float carSpeed, float targetSteering, bool brake, bool throttle, float frameTime )
{
// steering speeds are in mph, engine speeds in in/s
var steeringRestRate = RemapValClamped( carSpeed, Steering.SpeedSlow, Steering.SpeedFast,
Steering.SteeringRestRateSlow, Steering.SteeringRestRateFast );
var carSpeedIns = MphToIns( carSpeed );
if ( carSpeedIns > MaxSpeedIns )
{
steeringRestRate = RemapValClamped( carSpeedIns, MaxSpeedIns, BoostMaxSpeedIns,
Steering.SteeringRestRateFast, Steering.SteeringRestRateFast * 0.5f );
}
// still going faster than max after a boost, steer like we're boosting
var isBoosting = _state.IsTorqueBoosting || (IsBoostRecovering && carSpeedIns > MaxSpeedIns * Engine.AutobrakeSpeedGain);
if ( isBoosting )
{
steeringRestRate *= Steering.BoostSteeringRestRateFactor;
}
else if ( throttle )
{
steeringRestRate *= Steering.ThrottleSteeringRestRateFactor;
}
var steeringRate = RemapValClamped( carSpeed, Steering.SpeedSlow, Steering.SpeedFast,
Steering.SteeringRateSlow, Steering.SteeringRateFast );
var easingOff = Sign( targetSteering ) != Sign( _controls.Steering ) || MathF.Abs( targetSteering ) < MathF.Abs( _controls.Steering );
if ( MathF.Abs( steeringRate ) < steeringRestRate && easingOff )
{
steeringRate = steeringRestRate;
}
if ( isBoosting )
{
steeringRate *= Steering.BoostSteeringRateFactor;
}
else if ( brake )
{
steeringRate *= Steering.BrakeSteeringRateFactor;
}
_controls.Steering = Approach( targetSteering, _controls.Steering, steeringRate * frameTime );
}
private void UpdateDriverControls( DriverInput input, float frameTime )
{
// which way we're going, if we're really going anywhere
var carSign = _state.Speed >= SpeedThrottleAsBrake ? 1.0f : _state.Speed <= -SpeedThrottleAsBrake ? -1.0f : 0.0f;
var carSpeed = MathF.Abs( InsToMph( _state.Speed ) );
var forward = input.Forward > 0.0f;
var back = input.Back > 0.0f;
if ( input.Steering != 0.0f )
{
SteeringTurn( carSpeed, input.Steering, back, forward && !back, frameTime );
}
else
{
SteeringRest( carSpeed, frameTime );
}
_controls.Boost = 0;
_controls.Handbrake = false;
_controls.HandbrakeLeft = false;
_controls.HandbrakeRight = false;
bool throttle;
if ( forward )
{
throttle = true;
// less throttle while turning, ramped in from a stop so it can still pull away
_controls.Throttle = input.Forward;
if ( _controls.Steering != 0 )
{
var throttleReduce = carSpeed < Steering.SpeedSlow
? RemapValClamped( carSpeed, 0, Steering.SpeedSlow, 0, Steering.TurnThrottleReduceSlow )
: RemapValClamped( carSpeed, Steering.SpeedSlow, Steering.SpeedFast, Steering.TurnThrottleReduceSlow, Steering.TurnThrottleReduceFast );
_controls.Throttle = input.Forward * MathF.Max( 0, 1.0f - (throttleReduce * MathF.Abs( _controls.Steering )) );
}
// rolling backwards: forward is the brake
if ( carSign < 0.0f )
{
_controls.Brake = Approach( input.Forward, _controls.Brake, frameTime * BrakeRate * BrakeBackForwardScalar );
_controls.Throttle = 0.0f;
throttle = false;
}
else
{
_controls.Brake = 0.0f;
}
}
else if ( back )
{
throttle = true;
_controls.Throttle = -input.Back;
// rolling forwards: back is the brake
if ( carSign > 0.0f )
{
_controls.Brake = Approach( input.Back, _controls.Brake, frameTime * BrakeRate );
_controls.Throttle = 0.0f;
throttle = false;
}
else
{
_controls.Brake = 0.0f;
}
}
else
{
throttle = false;
_controls.Throttle = 0;
_controls.Brake = 0.0f;
}
// boost only going forward, and not while it's recharging
if ( input.Boost && throttle && !IsBoostRecovering && !back )
{
_controls.Boost = 1.0f;
}
if ( input.Handbrake )
{
_controls.Handbrake = true;
_controls.HandbrakeLeft = input.Steering < 0.0f;
_controls.HandbrakeRight = input.Steering > 0.0f;
// no engine rev up while braking
throttle = false;
}
_throttleDown = throttle;
_freeRevving = _controls.Handbrake && (forward || back);
}
}