Component that plays back a ghost recording by binding a MovieClip's tracks to this GameObject and updating playback over time. It exposes properties for the recording, player name, lap time, looping, and reports minimap blip info.
using Machines.UI;
using Sandbox.MovieMaker;
using Sandbox.MovieMaker.Compiled;
namespace Machines.Ghost;
/// <summary>
/// Plays back a ghost recording by rebinding clip tracks to this GameObject.
/// </summary>
public sealed class GhostPlayer : Component, IMinimapBlip
{
Color IMinimapBlip.BlipColor => new( 0.6f, 0.6f, 0.6f );
string IMinimapBlip.BlipClass => "ghost";
int IMinimapBlip.BlipPriority => 0;
bool IMinimapBlip.ShowOnMinimap => IsPlaying;
/// <summary>
/// The currently active ghost (last-wins, singleplayer only).
/// </summary>
public static GhostPlayer Current { get; private set; }
/// <summary>
/// The recording to play back.
/// </summary>
public GhostRecording Recording { get; set; }
/// <summary>
/// Player name shown on the ghost nameplate.
/// </summary>
public string PlayerName { get; set; }
/// <summary>
/// Lap time this ghost represents (seconds).
/// </summary>
public float LapTime { get; set; }
/// <summary>
/// Whether to loop playback when it reaches the end.
/// </summary>
[Property]
public bool Loop { get; set; } = true;
/// <summary>
/// Whether playback is currently active.
/// </summary>
public bool IsPlaying { get; private set; }
public double CurrentTime { get; private set; }
private MovieClip _clip;
private TrackBinder _binder;
private double _duration;
/// <summary>
/// Start playback from the beginning.
/// </summary>
public void StartPlayback()
{
if ( Recording == null || !Recording.IsValid )
return;
_clip = Recording.Clip;
_duration = _clip.Duration.TotalSeconds;
_binder = new TrackBinder( Scene );
foreach ( var track in _clip.Tracks )
{
if ( track is ICompiledReferenceTrack refTrack )
{
_binder.Get( refTrack ).Bind( GameObject );
break;
}
}
CurrentTime = 0f;
IsPlaying = true;
}
protected override void OnEnabled()
{
Current = this;
}
protected override void OnDisabled()
{
if ( Current == this )
Current = null;
}
protected override void OnFixedUpdate()
{
if ( !IsPlaying || _clip is null || _duration <= 0f )
return;
CurrentTime += Time.Delta;
if ( CurrentTime >= _duration )
{
CurrentTime = Loop ? CurrentTime % _duration : _duration;
}
var time = MovieTime.FromSeconds( CurrentTime );
_clip.Update( time, _binder );
}
}