概要
UnrealEngine5のリプレイシステムについてのメモです。
レプリケーション機能を使ったUE4時代から実装されているシステムのようです。
更新履歴
| 日付 | 内容 |
|---|---|
| 2026/09/11 | 初版 |
参考
以下を参考にさせて頂きました、ありがとうございます。
UE公式:リプレイシステム
UE公式:DemoNetDriverとストリーマー
DemoNetDriverリプレイシステム Ver5.2
wiki.unrealengine.com:Replay System Tutorial
環境
Windows11
Visual Studio 2022
UnrealEngine 5.7
関連ソース
"Engine\Source\Runtime\Engine\Classes\Engine\GameInstance.h"
"Engine\Source\Runtime\Engine\Public\ReplaySubsystem.h"
"Engine\Source\Runtime\Engine\Classes\Engine\DemoNetDriver.h"
リプレイシステムとは
UE公式にあるようにマルチプレイで使われるレプリケーション機能を使ったゲームプレイを記録/再生できるシステムです。
当然ながらレプリケーション対応が必要でこちらの対応コストが高いです。
リプレイ機能のハンズオン
GameInstanceクラスにあるリプレイ関連のメソッドについて
GameInstance.h にリプレイに関するメソッドがあります。
// 指定されたカスタム名とフレンドリー名で、リプレイの録画を開始
ENGINE_API virtual void StartRecordingReplay(const FString& InName, const FString& FriendlyName, const TArray<FString>& AdditionalOptions = TArray<FString>(), TSharedPtr<IAnalyticsProvider> AnalyticsProvider = nullptr);
// リプレイの録画の停止
ENGINE_API virtual void StopRecordingReplay();
// 録画したリプレイを再生する
ENGINE_API virtual bool PlayReplay(const FString& InName, UWorld* WorldOverride = nullptr, const TArray<FString>& AdditionalOptions = TArray<FString>());
// 指定されたリプレイの再生が保留されており、まだ再生が開始されていない場合、このメソッドは true を返します。
ENGINE_API virtual bool IsReplayDeferred(const FString& ReplayName) const;
// 以前に録画したリプレイのプレイリストの再生を開始します。
ENGINE_API bool PlayReplayPlaylist(const struct FReplayPlaylistParams& PlaylistParams);
GameInstanceクラスを継承してリプレイシステムを使う
GameInstanceクラスを継承して再生する場合のサンプルコードです。
録画形式はローカルファイル、オンメモリ、httpでのネットワークとあるようで録画時のオプションで指定できます。
またローカルファイルで保存の場合は、/Saved/Demos/[RecordingName].replay に保存されます。
UCLASS()
class SAMPLE_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
..省略..
public:
// オプション
TArray<FString> AddOptions;
// レコーディング名
FString RecordingName = TEXT("MyReplay");
// フレンドリー名
FString FriendlyName = TEXT("MyRep");
void StartRecording();
void StopRecording();
void StartReplay();
};
// 初期化
void UMyGameInstance::Init()
{
// ローカルファイルへの保存
// "/Saved/Demos/[RecordingName].replay" に保存
AddOptions.Add("ReplayStreamerOverride=LocalFileNetworkReplayStreaming");
// メモリへの保存の場合
// AddOptions.Add("ReplayStreamerOverride=InMemoryNetworkReplayStreaming");
}
// リプレイ録画の開始
void UMyGameInstance::StartRecording()
{
StartRecordingReplay(RecordingName, FriendlyName, AddOptions);
}
// リプレイ録画の停止
void UMyGameInstance::StopRecording()
{
StopRecordingReplay();
}
// リプレイ開始
void UMyGameInstance::StartReplay()
{
IConsoleVariable* DemoLoop = IConsoleManager::Get().FindConsoleVariable(TEXT("demo.Loop"));
if (DemoLoop){
DemoLoop->Set(1); // ループ設定
}
PlayReplay(RecordingName, nullptr, AddOptions);
}
上記コードでStartRecording() StopRecording() で録画開始/終了し、StartReplay() でリプレイを再生することができます。
リプレイ機能の関連クラス
ReplaySubsystemクラスについて
実際のリプレイ機能はこのサブシステムが持っていてリプレイ合計時間や再生中のリプレイ現在時間もこちらから取得できます。
UCLASS(DisplayName = "Replay Subsystem", MinimalAPI)
class UReplaySubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
..省略..
// リプレイを録画する
ENGINE_API void RecordReplay(const FString& Name, const FString& FriendlyName, const TArray<FString>& AdditionalOptions, TSharedPtr<IAnalyticsProvider> AnalyticsProvider);
// リプレイを再生する
ENGINE_API bool PlayReplay(const FString& Name, UWorld* WorldOverride, const TArray<FString>& AdditionalOptions);
// リプレイ録画or再生を停止する
ENGINE_API void StopReplay();
// 現在のリプレイの総時間を取得する
UFUNCTION(BlueprintCallable, Category = Replay)
ENGINE_API float GetReplayTotalTime() const;
// リプレイの現在再生時間を取得する
UFUNCTION(BlueprintCallable, Category=Replay)
ENGINE_API float GetReplayCurrentTime() const;
// リプレイ録画中か?
UFUNCTION(BlueprintCallable, Category=Replay)
ENGINE_API bool IsRecording() const;
// リプレイ再生中か?
UFUNCTION(BlueprintCallable, Category=Replay)
ENGINE_API bool IsPlaying() const;
};
コンソールコマンドについて
リプレイの録画/再生/停止 などはコンソールコマンドが用意されています。
また再生速度の変更などもできるようです。
| コンソールコマンド | 内容 |
|---|---|
| demorec(ReplayName) | リプレイの録画 |
| demostop | リプレイの録画停止 |
| demoplay(ReplayName) | リプレイの再生 |
| demopause | リプレイの一時停止 |
| demospeed(Speed) | スケーリング係数で再生速度を変えます。1が通常速度 |
デモ関連のコンソール変数も用意されています。
以下一例
| CVar | 内容 |
|---|---|
| demo.Loop | リプレイ再生時のループ設定 |
| demo.RecordHz | 録画時の1秒あたりのフレーム数を設定 |
| demo.CheckpointSaveMaxMSPerFrame | リプレイデータの記録時間、録画処理が指定時間ミリ秒で収まらない場合次フレームに回します |
| demo.CVarCheckpointUploadDelayInSeconds | チェックポイントを作成する秒指定での時間間隔 |
DemoNetDriverクラスについて
再生に関連する情報はこのクラスから取得できます。
コントローラーは SpectatorControllerというリプレイ再生時の観戦用コントローラーが作られるようです。
if (UDemoNetDriver* _DemoDriver = _World->GetDemoNetDriver()){
if (APlayerController* _ReplayPC = _DemoDriver->GetSpectatorController()){
// コントローラーでの処理
}
}
記録中の停止/再開などもこの DemoNetDriverで行えます。
if(UDemoNetDriver* _DemoDriver = GetWorld()->GetDemoNetDriver()){
_DemoDriver->PauseRecording(true);
}
Worldクラスについて
リプレイ記録中や再生中はWorldクラスで判別できます。
リプレイ再生中の特定処理などに利用できます。
if (GetWorld()->IsRecordingReplay()) {
// リプレイ記録中
}
if (GetWorld()->IsPlayingReplay()) {
// リプレイ再生中
}
SpectatorPawnクラスについて
GameMode の SpectatorClass で指定されている観戦者クラスがリプレイ再生中に生成されます。リプレイ再生中での専用処理はここを経由で処理したほうがいいかもしれません。
取得はプレイヤーコントローラーからの取得メソッドで行います。
if (UDemoNetDriver* _DemoDriver = GetWorld()->GetDemoNetDriver()){
if (APlayerController* _PC = _DemoDriver->GetSpectatorController()){
ASpectatorPawn* _SpectatorPawn = _PC->GetSpectatorPawn();
// 観戦者ポーンクラスを使った処理
}
}
リプレイ再生での注意点
試していくつか気が付いたことや注意点を列挙します。
再生中のプレイヤーはローカルプレイヤーではない
リプレイ再生中のプレイヤーアクターはローカルプレイヤーではありません、ネットワークロールは SimulatedProxy でマルチプレイでいうところのクライアント画面が再生された状態になります。
そのためローカルプレイヤー専用処理として IsLocallyControlled() で判定されている処理はリプレイでは再現されないことに注意が必要です。
GameplayAbilitySystem を使用している場合は処理の流れに注意
再生中のプレイヤーは SimulatedProxy のため、Abilityクラスに処理が来ませんので注意が必要です。また通常、AbilityTaskも処理は走りませんが、bSimulatedTaskをtrueにすることでシミュレートしたクライアントでもタスク処理を走らせることはできます。
リプレイ録画処理中に処理落ちをするとリプレイ再生がラグる
処理落ちしている環境でリプレイ録画をすると、それを再生した時マルチプレイ時のラグのようなズレが起こりました。
細かく検証はしていないためどのような影響かまではわかりませんが、少なくとも処理落ちした状況でのリプレイ録画はあまり推奨できないようです。
DemoNetDrivers の記録時の負荷を調整する機能の影響かもしれません。コンソールコマンド demo.CheckpointSaveMaxMSPerFrame での調整をすると改善されるかもしれませんが、いずれにせよ処理負荷が高い状態でのリプレイ記録は再生時の正確性が落ちる原因になります。
まとめ
「専用サーバーでのマルチプレイ時に HTTP Streamer を使って記録をするとチートの確認」や「SaveGame Replay Streamer を使ってのリプレイ保存」などいろいろ使えそうな感じはありますが、レプリケーション対応が前提なので導入コストがかなり高いと思います。