You can record gameplay into a MovieClip, which can be played back or imported into Movie Maker for editing. This could be useful for killcams in shooters, leaderboard replays in racing games, or for capturing gameplay to edit into a trailer.
This feature is provided by the MovieRecorder class. You can use its default behaviour to capture all display-related components in the scene, or configure it to capture only certain objects, components and properties.
By default, a MovieRecorder will capture all Renderers, Cameras, SoundPoints and particle-related components. Just create a recorder, and call Start.
// Create a recorder using the default capturing options
var recorder = new MovieRecorder( Scene );
// Start capturing
recorder.Start();
This starts capturing the scene every fixed update. When you want to stop recording, call Stop.
// Stop capturing
recorder.Stop();
You can then compile the recording into a MovieClip for playback in a MoviePlayer, or to save into a MovieResource.
// Convert the recording to a MovieClip
var clip = recorder.ToClip();
// Play the clip!
GetComponent<MoviePlayer>().Play( clip );
// Save it to a .movie
FileSystem.Data.WriteJson( $"movies/example.movie", clip.ToResource() );
In some cases you'll need more control over when captures are taken, like when recording in-editor. Instead of Start and Stop, call Advance to move the recording along by an amount of time, and Capture to record a frame.
while ( IsRecording )
{
SimulateScene();
// Move the recording time along by 10ms
recorder.Advance( 0.01 );
// Capture a frame
recorder.Capture();
}
If you want to be more selective about what gets recorded, create a MovieRecorderOptions instance and pass it in to the MovieRecorder constructor. These options let you specify the sample rate, filter which GameObjects are allowed to be captured, and add actions to run during each capture.
A new options instance won't capture anything, letting you fully configure it.
var options = new MovieRecorderOptions( SampleRate: 60 )
.WithCaptureAll<Player>()
.WithComponentCapturer<PlayerCapturer>()
.WithCaptureAction( x => x
.GetTrackRecorder( scoreManager )
.Property( "Score" )
.Capture() );
var recorder = new MovieRecorder( Scene, options );
If you want to base your configuration off the default options (which captures all renderers, cameras, particles etc), you can access it through MovieRecorderOptions.Default.
var options = MovieRecorderOptions.Default with { SampleRate = 60 }
.WithFilter( x => !x.Tags.Has( "effect" ) );
You can customize the default options by creating one or more static methods with a [DefaultMovieRecorderOptions] attribute, like this:
[DefaultMovieRecorderOptions]
public static MovieRecorderOptions BuildDefaultOptions( MovieRecorderOptions options )
{
return options
.WithFilter( x => !x.Tags.Has( "viewmodel" ) )
.WithFilter( x => x.PrefabInstanceSource?.StartsWith( "prefabs/surface/" ) is not true );
}
The default options will be used when pressing the Record button in the Movie Maker editor, and the movie console command in-game.
By default, property tracks in the generated recording are resampled at 30 FPS to keep file sizes small. You can choose a different rate when creating a MovieRecorderOptions, or use the with syntax on an existing instance.
var options = new MovieRecorderOptions( SampleRate: 60 );
var options = MovieRecorderOptions.Default with { SampleRate = 60 };
Adding filters lets you control which GameObjects are allowed to be captured. Components won't be recorded if their containing GameObject, or any of its ancestors, are filtered out. The first time a capture is attempted on an object, it is checked against all provided filters. If any return false, that object and any of its descendants will be ignored during recording.
var options = MovieRecorderOptions.Default
.WithFilter( x => !x.Tags.Has( "effect" ) )
.WithFilter( x => !x.Name.StartsWith( "decal_" ) );
Every time Capture is called on a MovieRecorder, it goes through all the capture actions in its MovieRecorderOptions. These actions will then access track recorders for GameObjects, Components, and properties, then call Capture on them.
// Capture "ExampleProperty" from inside exampleComponent
var options = new MovieRecorderOptions()
.WithCaptureAction( x => x
.GetTrackRecorder( exampleComponent )
.Property( "ExampleProperty" )
.Capture() );
Most of the time you'll want to capture specific properties from all instances of a given component type. To simplify this, you can implement ComponentCapturer<T>. Whenever Capture is called on a component's track recorder, all component capturer instances that match that component's type will be invoked.
class ModelRendererCapturer : ComponentCapturer<ModelRenderer>
{
protected override void OnCapture( IMovieTrackRecorder recorder, ModelRenderer component )
{
recorder.Property( nameof( ModelRenderer.Model ) ).Capture();
recorder.Property( nameof( ModelRenderer.Tint ) ).Capture();
recorder.Property( nameof( ModelRenderer.MaterialOverride ) ).Capture();
recorder.Property( nameof( ModelRenderer.RenderType ) ).Capture();
if ( component.HasBodyGroups )
{
recorder.Property( nameof( ModelRenderer.BodyGroups ) ).Capture();
}
}
}
All capturers with public parameterless constructors will be included in MovieRecorderOptions.Default, or you can manually add capturers to an options instance. These capturers are invoked when Capture is called on a matching component's track recorder.
var options = new MovieRecorderOptions()
.WithComponentCapturer( new ModelRendererCapturer() )
.WithCaptureAction( recorder =>
{
foreach ( var renderer in recorder.Scene.GetAllComponents<ModelRenderer>() )
{
// This will look for matching ComponentCapturers
recorder.GetTrackRecorder( renderer )?.Capture();
}
} );
For convenience, WithCaptureAll does the same job as the above example capture action.
var options = new MovieRecorderOptions()
.WithComponentCapturer( new ModelRendererCapturer() )
.WithCaptureAll<ModelRenderer>();