Component on player cars that lets the local player return to the track or self-destruct and respawn. It detects when the car is off the racing line, handles input for a ReturnToTrack action, shows a hold-progress for self-destruct, spawns an explosion FX and triggers the respawn.
using Machines.GameModes;
using Machines.Race;
namespace Machines.Player;
/// <summary>
/// Lets the player teleport back to the track
/// </summary>
public sealed class CarReturnToTrack : Component
{
[RequireComponent]
public Car Car { get; private set; }
/// <summary>
/// Lateral distance from the racing line at which the car is considered off-track.
/// </summary>
[Property]
public float OffTrackDistance { get; set; } = 350f;
/// <summary>
/// How long the return button must be held to self-destruct and respawn.
/// </summary>
[Property]
public float ExplodeHoldTime { get; set; } = 2f;
/// <summary>
/// Explosion FX spawned when the car self-destructs on a held return.
/// </summary>
[Property]
public GameObject ExplosionPrefab { get; set; }
/// <summary>
/// Whether the car is currently off the track
/// </summary>
public bool IsOffTrack
{
get
{
var line = RacingPath.Current?.Optimal;
if ( line is null || !line.IsValid )
return false;
var d = line.GetDistanceAtPosition( WorldPosition );
var nearest = line.GetPointAtDistance( d );
var lateral = (WorldPosition - nearest).WithZ( 0f ).Length;
return lateral > OffTrackDistance;
}
}
/// <summary>
/// Whether the local player can press the return button right now. Allowed at any time while off-track.
/// </summary>
public bool CanReturn =>
Car.IsValid() && Car.IsLocalPlayer
&& Car.Respawn.IsValid() && !Car.Respawn.IsRespawning
&& IsOffTrack;
/// <summary>
/// Whether the local player can self-destruct right now: grounded and mid-race.
/// </summary>
public bool CanExplode =>
Car.IsValid() && Car.IsLocalPlayer
&& Car.Respawn.IsValid() && !Car.Respawn.IsRespawning
&& (Car.Movement?.IsGrounded ?? false)
&& BaseGameMode.Current?.State == GameModeState.Playing;
/// <summary>
/// Self-destruct hold progress [0..1]; 0 when not actively charging a self-destruct.
/// </summary>
public float SelfDestructProgress { get; private set; }
// When the return button was pressed, and whether the hold already fired.
private float _holdStart = -1f;
private bool _holdTriggered;
protected override void OnUpdate()
{
if ( !Car.IsValid() || !Car.IsLocalPlayer || !Car.Respawn.IsValid() )
return;
if ( Input.Pressed( "ReturnToTrack" ) )
{
_holdStart = Time.Now;
_holdTriggered = false;
}
// Hold to self-destruct: explode, then respawn. Only while grounded and mid-race.
var charging = !_holdTriggered && _holdStart >= 0f && Input.Down( "ReturnToTrack" ) && CanExplode;
SelfDestructProgress = charging ? MathX.Clamp( (Time.Now - _holdStart) / ExplodeHoldTime, 0f, 1f ) : 0f;
if ( charging && Time.Now - _holdStart >= ExplodeHoldTime )
{
_holdTriggered = true;
SelfDestructProgress = 0f;
ExplodeAndRespawn();
}
if ( Input.Released( "ReturnToTrack" ) )
{
_holdStart = -1f;
_holdTriggered = false;
}
}
private void ExplodeAndRespawn()
{
SpawnExplosionFx( WorldPosition );
Car.Respawn.Respawn();
}
[Rpc.Broadcast]
private void SpawnExplosionFx( Vector3 point )
{
if ( ExplosionPrefab.IsValid() )
{
ExplosionPrefab.Clone( new CloneConfig
{
Transform = new Transform( point ),
StartEnabled = true
} );
}
}
}