Instant Replay は Unity で直近のゲームプレイ動画をいつでも保存できるようにするライブラリです。
必要なタイミングで直近のゲーム映像を遡って保存できるため、記録したい出来事を逃さずに録画できます。録画時間は事前に指定した長さを上限とし、上限を超えたフレームは破棄されます。
ユーザーが直近のゲームプレイ映像を SNS に共有する機能を実装することができます。
不具合が発生した際、直前に行った操作を動画として書き出すことで、不具合の再現等に役立てることができます。
[!NOTE] 以下の情報は使用している API やプラットフォームツール等から推定したもので、実際には動作が検証されていない場合があります。
| Platform | OS version | aarch64 | x86_64 | Other requirements |
|---|---|---|---|---|
| iOS | 10.0+ | ✅ | N/A | |
| Android | 8.0+ | ✅ | ✅ | |
| macOS | 11.0+ | ✅ | ✅ | |
| Windows | Windows 10+, Windows Server 2016+ | - | ✅ | |
| Linux | kernel 3.2+, glibc 2.17+ | - | ✅ | ffmpeg in PATH |
| Web | (any) | (any) | (any) | WebCodecs をサポートするブラウザ |
ffmpeg が PATH に存在すれば動作する可能性があります。[!WARNING] WebGL での既知の問題: WebGL では、録画中に画面にフリッカーが発生する可能性があります。これはデフォルトの
IFrameProvider実装であるScreenshotFrameProviderに起因するもので、この問題が発生した場合は、BuiltinCameraFrameProvider(Built-in RP 用)、RendererFeatureFrameProvider(Universal RP 用)、またはその他のカスタムIFrameProvider実装を使用して、入力RenderTextureを直接提供することで回避できます。
| Platform | APIs |
|---|---|
| iOS / macOS | Video Toolbox (H.264), Audio Toolbox (AAC) |
| Android | MediaCodec (H.264 / AAC) |
| Windows | Media Foundation (H.264 / AAC) |
| Linux | システムにインストールされたFFmpeg (H.264 / AAC) |
| Web | WebCodecs (avc1.640028 for video, mp4a.40.2 for audio) |
UnityNuGet の scoped registry を追加して、以下の git URL を Package Manager に追加してください。
https://github.com/CyberAgentGameEntertainment/InstantReplay.git?path=/Packages/jp.co.cyberagent.instant-replay.dependencies#release
NuGetForUnity や UnityNuGet を使用して以下のパッケージをインストールします。
以下の git URL を Package Manager に追加してください。
https://github.com/CyberAgentGameEntertainment/InstantReplay.git?path=Packages/jp.co.cyberagent.instant-replay#release
Package Manager から "User Interfaces" サンプルをインポートしてください。
シーンに InstantReplay Recorder.prefab を配置します。この Prefab には RecorderInterface と PersistentRecorder コンポーネントが付いており、有効な間は自動的にゲームプレイを録画します。
その後、RecorderInterface.StopAndExport() を呼び出すことで録画を停止してビデオを保存できます。例えば、シーン内のボタンをクリックすることでこのメソッドを呼び出すことができます。
録画したビデオが画面に表示されます。
録画を行うには RealtimeInstantReplaySession を使用します。
using InstantReplay;
var ct = destroyCancellationToken;
// 録画開始
using var session = RealtimeInstantReplaySession.CreateDefault();
// 〜 ゲームプレイ 〜
await Task.Delay(10000, ct);
// 録画停止と書き出し
var outputPath = await session.StopAndExportAsync();
File.Move(outputPath, Path.Combine(Application.persistentDataPath, Path.GetFileName(outputPath)));
録画できる時間はメモリ使用量によって決定されます。デフォルト設定では 20MiB に設定されており、圧縮されたフレームや音声サンプルの合計サイズがこの上限に達すると古いデータから順に破棄されます。より長時間の録画を可能にするには、メモリ使用量 MaxMemoryUsageBytesForCompressedFrames を上げたり、フレームレートや解像度、ビットレートを下げてください。
実行時に使用されるメモリとしては、上記のエンコード済みのデータを保持するバッファに加え、エンコード前の生のフレームや音声サンプルがいくつか保持されます。これはエンコーダーが非同期的に動作する関係で、あるフレームをエンコードしている間に次のフレームを受け取るためです。VideoInputQueueSize と AudioInputQueueSizeSeconds でそれぞれのキューのサイズを指定できるほか、MaxNumberOfRawFrameBuffers (オプション) で圧縮前のフレームを保持するバッファの最大数を指定できます。この値を小さくすることでメモリ使用量を削減できる場合がありますが、フレームドロップの可能性が高まります。
// デフォルト設定
var options = new RealtimeEncodingOptions
{
VideoOptions = new VideoEncoderOptions
{
Width = 1280,
Height = 720,
FpsHint = 30,
Bitrate = 2500000 // 2.5 Mbps
},
AudioOptions = new AudioEncoderOptions
{
SampleRate = 44100,
Channels = 2,
Bitrate = 128000 // 128 kbps
},
MaxNumberOfRawFrameBuffers = 2, // (オプション) エンコード前のフレームに使用されるバッファの最大数。各バッファは VideoOptions.Width * VideoOptions.Height * 4 バイトのサイズを持ちます。
MaxMemoryUsageBytesForCompressedFrames = 20 * 1024 * 1024, // 20 MiB
FixedFrameRate = 30.0, // 固定フレームレートを使用しない場合はnull
VideoInputQueueSize = 5, // エンコード前の生のフレームを保持する数の上限
AudioInputQueueSizeSeconds = 1.0 // エンコード前にバッファリングされる最大音声入力時間(秒)
};
using var session = new RealtimeInstantReplaySession(options);
RealtimeInstantReplaySession.Pause() と RealtimeInstantReplaySession.Resume() を使用して録画を一時停止および再開できます。
IFrameProvider を使用して映像ソースのカスタマイズが可能です。
RealtimeInstantReplaySession のコンストラクタに frameProvider として渡してください。また disposeFrameProvider によって RealtimeInstantReplaySession 側で frameProvider を自動的に破棄するかどうかを指定できます。
new RealtimeInstantReplaySession(options, frameProvider: new ScreenshotFrameProvider(), disposeFrameProvider: true);
IFrameProviderScreenshotFrameProvider: デフォルトで使用される実装です。ScreenCapture.CaptureScreenshotIntoRenderTexture() を使用するため、Overlay Canvas などカメラに含まれない描画結果もキャプチャできます。キャプチャ用に追加の RenderTexture を使用するため、GPU メモリ使用量が増加します。BuiltinCameraFrameProvider: Built-in Render Pipeline で OnRenderImage() を使用して特定のカメラの映像をキャプチャします。RendererFeatureFrameProvider: Universal Render Pipeline で Renderer Feature を使用して特定のカメラの映像をキャプチャします。カメラに対応する Renderer に対して InstantReplayFrameRendererFeature を追加する必要があります。IFrameProvider の実装InstantReplay.IFrameProvider を継承したクラスを作成します。
public interface IFrameProvider : IDisposable
{
public delegate void ProvideFrame(Frame frame);
event ProvideFrame OnFrameProvided;
}
new RealtimeInstantReplaySession(options, frameProvider: new CustomFrameProvider(), disposeFrameProvider: true);
デフォルトでは Unity デフォルトの出力音声を OnAudioFilterRead を使用してキャプチャします。これはシーン上の特定の AudioListener を自動的に検索して使用します。
[!WARNING] Bypass Listener Effects が有効化された AudioSource の音声はキャプチャされません。
シーン上に複数の AudioListener が存在する場合は、InstantReplay.UnityAudioSampleProvider のコンストラクタに AudioListener を渡して初期化し、RealtimeInstantReplaySession のコンストラクタに audioSampleProvider として渡してください。
new RealtimeInstantReplaySession(options, audioSampleProvider: new UnityAudioSampleProvider(audioListener), disposeAudioSampleProvider: true);
音声ソースを無効化したい場合は、NullAudioSampleProvider.Instance を使用してください。
new RealtimeInstantReplaySession(options, audioSampleProvider: NullAudioSampleProvider.Instance);
[!NOTE]
NullAudioSampleProviderではIDisposableに関する考慮は不要です。
また、IAudioSampleProvider を実装することで独自の音声ソースを使用することも可能です。
public interface IAudioSampleProvider : IDisposable
{
public delegate void ProvideAudioSamples(ReadOnlySpan<float> samples, int channels, int sampleRate,
double timestamp);
event ProvideAudioSamples OnProvideAudioSamples;
}
new RealtimeInstantReplaySession(options, audioSampleProvider: new CustomAudioSampleProvider(), disposeFrameProvider: true);
InstantReplay は CRIWARE からの音声をキャプチャするための IAudioSampleProvider 実装を提供しています。
INSTANTREPLAY_CRI を追加します。InstantReplay.Cri アセンブリ参照を追加します。RealtimeInstantReplaySession コンストラクタの audioSampleProvider に InstantReplay.Cri.CriAudioSampleProvider を指定します。Wwise もサポートされています。
INSTANTREPLAY_WWISE を追加します。InstantReplay.Wwise アセンブリ参照を追加します。RealtimeInstantReplaySession コンストラクタの audioSampleProvider に InstantReplay.Wwise.WwiseAudioSampleProvider を指定します。InstantReplaySession.State プロパティで録画の状態を取得できます。
UnboundedRecordingSession を使用すると、エンコードしたデータをメモリに保持せず直接ディスク上の MP4 ファイルに書き出します。書き出せる動画ファイルの時間には制限が設定されず、ディスク容量の許す限り録画が行えます。コンストラクタで出力ファイルパスの指定が必要な以外は RealtimeInstantReplaySession と同様に使用できます。
[!WARNING] 録画中にアプリがバックグラウンドに移行すると録画が停止し、録画ファイルが破損する可能性があります。バックグラウンド移行時には録画を一旦完了させることを推奨します。
using InstantReplay;
var ct = destroyCancellationToken;
// 録画開始
using var session = new UnboundedRecordingSession("out.mp4", RealtimeEncodingOptions.Default);
// 〜 ゲームプレイ 〜
await Task.Delay(10000, ct);
// 録画停止と書き出し
await session.CompleteAsync();
デフォルトでは、RealtimeInstantReplaySession はビデオ・オーディオデータをリアルタイムでエンコードしますが、InstantReplaySession を使用するとJPEGで圧縮されたフレームとPCM音声サンプルを一時的にディスクに保存し、StopAndTranscodeAsync() 時にまとめてエンコードするレガシーモードで録画できます。ディスク負荷が大きい代わりに、録画中の計算負荷が小さくなります。
using InstantReplay;
var ct = destroyCancellationToken;
// 録画開始
using var session = new InstantReplaySession(numFrames: 900, fixedFrameRate: 30);
// 〜 ゲームプレイ 〜
await Task.Delay(10000, ct);
// 録画停止と書き出し
var outputPath = await session.StopAndTranscodeAsync(ct: ct);
File.Move(outputPath, Path.Combine(Application.persistentDataPath, Path.GetFileName(outputPath)));
InstantReplaySession のコンストラクタでは numFrames と fixedFrameRate を指定できます。
csharp
new InstantReplaySession(numFrames: 900, fixedFrameRate: 30);
fixedFrameRate を null に設定した場合、実際のFPSが使用されます。
numFrames を超えたフレームは古いものから破棄されます。numFrames に比例して録画中のディスク使用量が大きくなるので、適度なサイズに設定してください。
デフォルトでは実際の画面サイズで録画しますが、InstantReplaySession のコンストラクタで maxWidth や maxHeight を指定することもできます。maxWidth や maxHeight を指定している場合は自動的にリサイズします。サイズを縮小することで録画中のディスク使用量や書き出しにかかる時間を短縮できます。また、録画中のメモリ使用量も減少します。
InstantReplaySession も RealtimeInstantReplaySession と同様に、IFrameProvider や IAudioSampleProvider を使用して映像・音声ソースをカスタマイズできます。
バグ収集の一部として InstantReplay を使用している場合は、リリース ビルドでスクリプト ファイルとプラグイン ファイルを除外する必要があります。
Scripting Define Symbols に EXCLUDE_INSTANTREPLAY を 加えると InstantReplay に関連する全てのコードがコンパイル対象から除外されます。
したがって、InstantReplay にアクセスするコードをすべて#if !EXCLUDE_INSTANTREPLAY と #endifで囲っておけば、リリース時に関連するスクリプトを全て除外できます。
使用されている依存関係のライセンスについては THIRD-PARTY-NOTICES.md を参照してください。
$ claude mcp add InstantReplay \
-- python -m otcore.mcp_server <graph>