前言:UE4.26的回放教程,最近有用到,So梳理了整个构建流程,希望能帮到你!(结尾有视频版教程,时长较长)
1.准备工作:
-
创建一个UE4C++项目,添加第一人称和第三人称功能包;
-
关闭引擎,找到项目目录:../ContentDir/Config/DefaultEngine.ini添加如下代码:
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")
-
重新打开项目。创建新的C++类MyGameInstance继承自:GameInstance
-
XXX.Build.cs中添加模块:Json
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","Json"});
-
MyGameInstance.h中:
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "NetworkReplayStreaming.h"
#include "Runtime/NetworkReplayStreaming/NullNetworkReplayStreaming/Public/NullNetworkReplayStreaming.h"
#include "Misc/NetworkVersion.h"
#include "MyGameInstance.generated.h"
USTRUCT(BlueprintType)
struct FS_ReplayInfo1
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly)
FString ReplayName;
UPROPERTY(BlueprintReadOnly)
FString FriendlyName;
UPROPERTY(BlueprintReadOnly)
FDateTime Timestamp;
UPROPERTY(BlueprintReadOnly)
int32 LengthInMS;
UPROPERTY(BlueprintReadOnly)
bool bIsValid;
FS_ReplayInfo1()
{
ReplayName = "Replay";
FriendlyName = "Replay";
Timestamp = FDateTime::MinValue();
LengthInMS = 0;
bIsValid = false;
}
FS_ReplayInfo1(FString NewName, FString NewFriendlyName, FDateTime NewTimestamp, int32 NewLengthInMS)
{
ReplayName = NewName;
FriendlyName = NewFriendlyName;
Timestamp = NewTimestamp;
LengthInMS = NewLengthInMS;
bIsValid = true;
}
};
/**
*
*/
UCLASS()
class REPLAYTEST_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Replays")
void StartRecordingReplayFromBP(FString ReplayName, FString FriendlyName);
UFUNCTION(BlueprintCallable, Category = "Replays")
void StopRecordingReplayFromBP();
UFUNCTION(BlueprintCallable, Category = "Replays")
void PlayReplayFromBP(FString ReplayName);
UFUNCTION(BlueprintCallable, Category = "Replays")
void FindReplays();
UFUNCTION(BlueprintCallable, Category = "Replays")
void RenameReplay(const FString& ReplayName, const FString& NewFriendlyReplayName);
UFUNCTION(BlueprintCallable, Category = "Replays")
void DeleteReplay(const FString& ReplayName);
virtual void Init() override;
TSharedPtr<INetworkReplayStreamer> EnumerateStreamsPtr;
FEnumerateStreamsCallback OnEnumerateStreamsCompleteDelegate1;
void OnEnumerateStreamsComplete1(const FEnumerateStreamsResult& Result);
FDeleteFinishedStreamCallback OnDeleteFinishedStreamCompleteDelegate1;
void OnDeleteFinishedStreamComplete1(const FDeleteFinishedStreamResult& Result);
UFUNCTION(BlueprintImplementableEvent, Category = "Replays")
void BP_OnFindReplaysComplete1(const TArray<FS_ReplayInfo1>& AllReplaysm);
};
-
MyGameInstance.cpp中:
#include "MyGameInstance.h"
#include "Modules/ModuleManager.h"
#include "Runtime/Core/Public/HAL/FileManager.h"
#include "Runtime/Core/Public/Misc/FileHelper.h"
void UMyGameInstance::Init()
{
Super::Init();
// create a ReplayStreamer for FindReplays() and DeleteReplay(..)
EnumerateStreamsPtr = FNetworkReplayStreaming::Get().GetFactory().CreateReplayStreamer();
// Link FindReplays() delegate to function
OnEnumerateStreamsCompleteDelegate1 = FEnumerateStreamsCallback::CreateUObject(this, &UMyGameInstance::OnEnumerateStreamsComplete1);
// Link DeleteReplay() delegate to function
OnDeleteFinishedStreamCompleteDelegate1 = FDeleteFinishedStreamCallback::CreateUObject(this, &UMyGameInstance::OnDeleteFinishedStreamComplete1);
}
void UMyGameInstance::StartRecordingReplayFromBP(FString ReplayName, FString FriendlyName)
{
StartRecordingReplay(ReplayName, FriendlyName);
}
void UMyGameInstance::StopRecordingReplayFromBP()
{
StopRecordingReplay();
}
void UMyGameInstance::PlayReplayFromBP(FString ReplayName)
{
PlayReplay(ReplayName);
}
void UMyGameInstance::FindReplays()
{
if (EnumerateStreamsPtr.Get())
{
EnumerateStreamsPtr.Get()->EnumerateStreams(FNetworkReplayVersion(), int32(), FString(), TArray<FString>(), OnEnumerateStreamsCompleteDelegate1);
}
}
void UMyGameInstance::OnEnumerateStreamsComplete1(const FEnumerateStreamsResult& Result)
{
TArray<FS_ReplayInfo1> AllReplays;
for (FNetworkReplayStreamInfo StreamInfo : Result.FoundStreams)
{
void BP_OnFindReplaysComplete1(const TArray<FS_ReplayInfo1> &AllReplaysm);
if (!StreamInfo.bIsLive)
{
AllReplays.Add(FS_ReplayInfo1(StreamInfo.Name, StreamInfo.FriendlyName, StreamInfo.Timestamp, StreamInfo.LengthInMS));
}
}
BP_OnFindReplaysComplete1(AllReplays);
}
void UMyGameInstance::RenameReplay(const FString& ReplayName, const FString& NewFriendlyReplayName)
{
// Get File Info
FNullReplayInfo Info;
const FString DemoPath = FPaths::Combine(*FPaths::ProjectSavedDir(), TEXT("Demos/"));
const FString StreamDirectory = FPaths::Combine(*DemoPath, *ReplayName);
const FString StreamFullBaseFilename = FPaths::Combine(*StreamDirectory, *ReplayName);
const FString InfoFilename = StreamFullBaseFilename + TEXT(".replayinfo");
TUniquePtr<FArchive> InfoFileArchive(IFileManager::Get().CreateFileReader(*InfoFilename));
if (InfoFileArchive.IsValid() && InfoFileArchive->TotalSize() != 0)
{
FString JsonString;
*InfoFileArchive << JsonString;
Info.FromJson(JsonString);
Info.bIsValid = true;
InfoFileArchive->Close();
}
// Set FriendlyName
Info.FriendlyName = NewFriendlyReplayName;
// Write File Info
TUniquePtr<FArchive> ReplayInfoFileAr(IFileManager::Get().CreateFileWriter(*InfoFilename));
if (ReplayInfoFileAr.IsValid())
{
FString JsonString = Info.ToJson();
*ReplayInfoFileAr << JsonString;
ReplayInfoFileAr->Close();
}
}
void UMyGameInstance::DeleteReplay(const FString& ReplayName)
{
if (EnumerateStreamsPtr.Get())
{
EnumerateStreamsPtr.Get()->DeleteFinishedStream(ReplayName, OnDeleteFinishedStreamCompleteDelegate1);
}
}
void UMyGameInstance::OnDeleteFinishedStreamComplete1(const FDeleteFinishedStreamResult& Result)
{
FindReplays();
}
//void UMyGameInstance::BP_OnFindReplaysComplete1(TArray<FS_ReplayInfo1>& AllReplays)
//{
//}
-
创建新的C++类PC_Spectator继承自:PlayerController
-
PC_Spectator.h中:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "PC_ReplaySpectator.generated.h"
/**
*
*/
UCLASS()
class REPLAYTEST_API APC_ReplaySpectator : public APlayerController
{
GENERATED_BODY()
public:
/** we must set some Pause-Behavior values in the ctor */
APC_ReplaySpectator(const FObjectInitializer& ObjectInitializer);
protected:
/** for saving Anti-Aliasing and Motion-Blur settings during Pause State */
int32 PreviousAASetting;
int32 PreviousMBSetting;
public:
/** Set the Paused State of the Running Replay to bDoPause. Return new Pause State */
UFUNCTION(BlueprintCallable, Category = "CurrentReplay")
bool SetCurrentReplayPausedState(bool bDoPause);
/** Gets the Max Number of Seconds that were recorded in the current Replay */
UFUNCTION(BlueprintCallable, Category = "CurrentReplay")
int32 GetCurrentReplayTotalTimeInSeconds() const;
/** Gets the Second we are currently watching in the Replay */
UFUNCTION(BlueprintCallable, Category = "CurrentReplay")
int32 GetCurrentReplayCurrentTimeInSeconds() const;
/** Jumps to the specified Second in the Replay we are watching */
UFUNCTION(BlueprintCallable, Category = "CurrentReplay")
void SetCurrentReplayTimeToSeconds(int32 Seconds);
/** Changes the PlayRate of the Replay we are watching, enabling FastForward or SlowMotion */
UFUNCTION(BlueprintCallable, Category = "CurrentReplay")
void SetCurrentReplayPlayRate(float PlayRate = 1.f);
};
-
PC_Spectator.cpp中:
// Fill out your copyright notice in the Description page of Project Settings.
#include "PC_ReplaySpectator.h"
#include "Engine/World.h"
#include "Engine/DemoNetDriver.h"
APC_ReplaySpectator::APC_ReplaySpectator(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
bShowMouseCursor = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
bShouldPerformFullTickWhenPaused = true;
}
bool APC_ReplaySpectator::SetCurrentReplayPausedState(bool bDoPause)
{
AWorldSettings* WorldSettings = GetWorldSettings();
// Set MotionBlur off and Anti Aliasing to FXAA in order to bypass the pause-bug of both
static const auto CVarAA = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DefaultFeature.AntiAliasing"));
static const auto CVarMB = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DefaultFeature.MotionBlur"));
if (bDoPause)
{
PreviousAASetting = CVarAA->GetInt();
PreviousMBSetting = CVarMB->GetInt();
// Set MotionBlur to OFF, Anti-Aliasing to FXAA
CVarAA->Set(1);
CVarMB->Set(0);
WorldSettings->Pauser = PlayerState;
return true;
}
// Rest MotionBlur and AA
CVarAA->Set(PreviousAASetting);
CVarMB->Set(PreviousMBSetting);
WorldSettings->Pauser = NULL;
return false;
}
int32 APC_ReplaySpectator::GetCurrentReplayTotalTimeInSeconds() const
{
if (GetWorld())
{
if (GetWorld()->DemoNetDriver)
{
return GetWorld()->DemoNetDriver->DemoTotalTime;
}
}
return 0.f;
}
int32 APC_ReplaySpectator::GetCurrentReplayCurrentTimeInSeconds() const
{
if (GetWorld())
{
if (GetWorld()->DemoNetDriver)
{
return GetWorld()->DemoNetDriver->DemoCurrentTime;
}
}
return 0.f;
}
void APC_ReplaySpectator::SetCurrentReplayTimeToSeconds(int32 Seconds)
{
if (GetWorld())
{
if (GetWorld()->DemoNetDriver)
{
GetWorld()->DemoNetDriver->GotoTimeInSeconds(Seconds);
}
}
}
void APC_ReplaySpectator::SetCurrentReplayPlayRate(float PlayRate)
{
if (GetWorld())
{
if (GetWorld()->DemoNetDriver)
{
GetWorld()->GetWorldSettings()->DemoPlayTimeDilation = PlayRate;
}
}
}
-
编译成功后启动引擎进入下一步;
2.编写蓝图及其他设置:
-
分别创建用户控件WBP_ReplayChild;WBP_ReplayMenu;WBP_ReplaySpectator:
-
创建新的蓝图类GI_Replay继承自MyGameInstance并重载函数BP on Find Replays Complete 1:
-
创建新的控制器类BP_PB_ReplaySpectator继承自PC_ReplaySpectator:
-
打开第一人称&第三人称游戏模式蓝图,设置重播旁观者为创建的PC_ReplaySpectator:
-
两人称角色蓝图中类默认值开启复制&复制运动,写入录制逻辑:
-
打开所需录制的地图(一三人称地图)分别对应世界场景设置游戏模式并开启所有可移动物细节栏中的复制:
-
项目设置中设置默认地图与游戏实例:
-
创建空地图MainMenuMap,游戏模式选择None,关卡蓝图中添加菜单到视口:
3.开始运行,进入录制或进行回放。
视频教程:BV17L4y1A7eF【虚幻4】Replay重播系统-UE4.26教程