Components/Camera/PreRaceCamera.cs

A camera behaviour used before a race starts. It cycles through available CameraMarker entities while the game mode is WaitingForPlayers and returns interpolated CameraPose with configurable cycle duration and field of view.

using Machines.GameModes;

namespace Machines.Components;

/// <summary>
/// Pre-race camera that cycles through all CameraMarkers in the scene while waiting for players to join.
/// </summary>
public sealed class PreRaceCamera : CameraBehaviour
{
	/// <summary>
	/// Seconds to dwell on each marker before cycling to the next.
	/// </summary>
	[Property, Group( "Cycling" )]
	public float CycleDuration { get; set; } = 3f;

	[Property, Group( "Framing" )]
	public float FieldOfView { get; set; } = 60f;

	public override int Priority => 10;

	public override bool WantsControl =>
		BaseGameMode.Current.IsValid()
		&& BaseGameMode.Current.State == GameModeState.WaitingForPlayers
		&& CameraMarker.All.Count > 0;

	private int _currentIndex = -1;
	private TimeSince _timeSinceCycle;
	private CameraMarker _currentMarker;

	public override void OnActivated( CameraPose from )
	{
		_currentIndex = -1;
		_timeSinceCycle = CycleDuration; // force an immediate pick
		CycleToNext();
	}

	public override CameraPose Evaluate( CameraPose current )
	{
		if ( CameraMarker.All.Count == 0 )
			return current;

		if ( _timeSinceCycle >= CycleDuration )
			CycleToNext();

		if ( !_currentMarker.IsValid() )
			return current;

		var t = MathX.Clamp( _timeSinceCycle / CycleDuration, 0f, 1f );
		_currentMarker.Evaluate( t, out var position, out var rotation );
		return new CameraPose( position, rotation, FieldOfView );
	}

	private void CycleToNext()
	{
		if ( CameraMarker.All.Count == 0 )
			return;

		_currentIndex = (_currentIndex + 1) % CameraMarker.All.Count;
		_currentMarker = CameraMarker.All[_currentIndex];
		_timeSinceCycle = 0f;
	}
}