A camera behaviour used at the end of a race when the local player's car is on autopilot. It picks baked camera spots along the racing path near the car, switches periodically or when occluded, and smoothly slerps rotation while snapping position to the chosen spot.
using Machines.GameModes;
using Machines.Player;
using Machines.Race;
namespace Machines.Components;
/// <summary>
/// Trackside camera when the car is in autopilot mode at the end of the game
/// </summary>
public sealed class TracksideCamera : CameraBehaviour
{
/// <summary>
/// How often (seconds) the camera switches to the nearest baked spot.
/// </summary>
[Property, Group( "Timing" )]
public float SwitchInterval { get; set; } = 4f;
/// <summary>
/// Max arc distance (units) a camera spot can be from the car to qualify.
/// </summary>
[Property, Group( "Timing" )]
public float MaxSwapDistance { get; set; } = 800f;
[Property, Group( "Smoothing" )]
public float RotationLerpSpeed { get; set; } = 8f;
[Property, Group( "Framing" )]
public float FieldOfView { get; set; } = 60f;
public override int Priority => 30;
private Car _target;
private Rotation _currentRotation;
private float _switchTimer;
private float _occludedTimer;
private int _currentSpotIndex = -1;
private bool _initialized;
public override bool WantsControl
{
get
{
var mode = BaseGameMode.Current;
if ( !mode.IsValid() || mode.State is not (GameModeState.Playing or GameModeState.GameOver) )
return false;
var car = FindLocalCar();
return car.IsValid() && car.Autopilot && RacingPath.Current?.CameraSpots.Count > 0;
}
}
public override void OnActivated( CameraPose from )
{
_initialized = false;
_switchTimer = SwitchInterval; // force an immediate pick
_occludedTimer = 0f;
_currentSpotIndex = -1;
_target = FindLocalCar();
}
public override CameraPose Evaluate( CameraPose current )
{
if ( !_target.IsValid() )
{
_target = FindLocalCar();
if ( !_target.IsValid() )
return current;
}
_switchTimer += Time.Delta;
if ( !CanSeeTarget() )
_occludedTimer += Time.Delta;
else
_occludedTimer = 0f;
if ( _switchTimer >= SwitchInterval || _currentSpotIndex < 0 || _occludedTimer >= 1f )
{
PickClosestSpot();
_switchTimer = 0f;
_occludedTimer = 0f;
}
return UpdateTransform( current );
}
private void PickClosestSpot()
{
var path = RacingPath.Current;
if ( path is null || path.CameraSpots.Count == 0 || !_target.IsValid() )
return;
var line = path.Optimal;
if ( line is null || !line.IsValid )
return;
var targetDist = line.GetDistanceAtPosition( _target.WorldPosition );
var totalLength = line.TotalLength;
// Nearest spot within the preferred swap radius, plus a global-nearest fallback so the
// camera always has somewhere to go even when no baked spot is close.
var bestInRange = -1;
var bestInRangeGap = float.MaxValue;
var bestAny = -1;
var bestAnyGap = float.MaxValue;
for ( int i = 0; i < path.CameraSpots.Count; i++ )
{
// Never re-pick the current spot (the camera must visibly move), and only choose
// spots that can actually see the car so we never cut to an occluded angle.
if ( i == _currentSpotIndex )
continue;
var spot = path.CameraSpots[i];
if ( !CanSee( spot.Position, _target.WorldPosition ) )
continue;
// Wrap-aware arc distance from the car to the spot.
var ahead = (spot.Distance - targetDist) % totalLength;
if ( ahead < 0f ) ahead += totalLength;
var gap = MathF.Min( ahead, totalLength - ahead );
if ( gap < bestAnyGap )
{
bestAnyGap = gap;
bestAny = i;
}
if ( gap <= MaxSwapDistance && gap < bestInRangeGap )
{
bestInRangeGap = gap;
bestInRange = i;
}
}
var chosen = bestInRange >= 0 ? bestInRange : bestAny;
if ( chosen >= 0 )
_currentSpotIndex = chosen;
}
private CameraPose UpdateTransform( CameraPose current )
{
var path = RacingPath.Current;
if ( path is null || _currentSpotIndex < 0 || _currentSpotIndex >= path.CameraSpots.Count )
return current;
var spot = path.CameraSpots[_currentSpotIndex];
var targetPos = _target.IsValid() ? _target.WorldPosition : spot.Position;
var lookDir = targetPos - spot.Position;
if ( lookDir.LengthSquared < 0.01f )
lookDir = Vector3.Forward;
var targetRot = Rotation.LookAt( lookDir.Normal, Vector3.Up );
if ( !_initialized )
{
_currentRotation = targetRot;
_initialized = true;
}
// Hard-cut position, smooth rotation only (tracks the moving car).
_currentRotation = Rotation.Slerp( _currentRotation, targetRot, Time.Delta * RotationLerpSpeed );
return new CameraPose( spot.Position, _currentRotation, FieldOfView );
}
private Car FindLocalCar()
{
foreach ( var car in Scene.GetAllComponents<Car>() )
{
if ( car.IsValid() && car.IsLocalPlayer )
return car;
}
return null;
}
/// <summary>
/// Trace from the current spot to the car to check line-of-sight.
/// </summary>
private bool CanSeeTarget()
{
if ( !_target.IsValid() || _currentSpotIndex < 0 )
return false;
var path = RacingPath.Current;
if ( path is null || _currentSpotIndex >= path.CameraSpots.Count )
return false;
return CanSee( path.CameraSpots[_currentSpotIndex].Position, _target.WorldPosition );
}
/// <summary>
/// True if there's clear line-of-sight from <paramref name="from"/> to <paramref name="to"/>.
/// </summary>
private bool CanSee( Vector3 from, Vector3 to )
{
var tr = Scene.Trace.Ray( from, to )
.WithoutTags( "player", "car" )
.Run();
return !tr.Hit;
}
}