Player/Ghost/GhostRecorder.cs

Component that records a GameObject's local position and rotation into a MovieRecorder for ghost playback. It exposes SampleRate, StartRecording/StopRecording, and produces a GhostRecording from the finished clip.

using Sandbox.MovieMaker;

namespace Machines.Ghost;

/// <summary>
/// Records car position/rotation via MovieRecorder for ghost playback.
/// </summary>
public sealed class GhostRecorder : Component
{
	/// <summary>
	/// Sample rate in frames per second.
	/// </summary>
	[Property]
	public float SampleRate { get; set; } = 10;

	/// <summary>
	/// Whether the recorder is currently capturing.
	/// </summary>
	public bool IsRecording { get; private set; }

	private MovieRecorder _recorder;

	/// <summary>
	/// Begins recording, clearing any previous data.
	/// </summary>
	public void StartRecording()
	{
		StopRecording();

		var options = new MovieRecorderOptions( SampleRate: (int)SampleRate )
			.WithCaptureAction( x =>
			{
				var track = x.GetTrackRecorder( GameObject );
				track.Property( nameof( GameObject.LocalPosition ) ).Capture();
				track.Property( nameof( GameObject.LocalRotation ) ).Capture();
			} );

		_recorder = new MovieRecorder( Scene, options );
		_recorder.Start();
		IsRecording = true;
	}

	/// <summary>
	/// Stop recording and finalize the data.
	/// </summary>
	public void StopRecording()
	{
		if ( _recorder is not null )
		{
			_recorder.Stop();
			IsRecording = false;
		}
	}

	/// <summary>
	/// Stops recording on destroy to avoid a dangling recorder on scene change.
	/// </summary>
	protected override void OnDestroy()
	{
		StopRecording();
	}

	/// <summary>
	/// Returns the completed recording. Call after <see cref="StopRecording"/>.
	/// </summary>
	public GhostRecording GetRecording( string carResourcePath, string playerName = null )
	{
		if ( _recorder == null )
			return null;

		var clip = _recorder.ToClip();
		if ( clip == null )
			return null;

		_recorder = null;

		return GhostRecording.Create( carResourcePath, clip, playerName );
	}
}