【UE4】Replay游戏回放 for UE4.26

前言: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.编写蓝图及其他设置:

  1. 分别创建用户控件WBP_ReplayChildWBP_ReplayMenuWBP_ReplaySpectator

  2. 创建新的蓝图类GI_Replay继承自MyGameInstance并重载函数BP on Find Replays Complete 1

  3. 创建新的控制器类BP_PB_ReplaySpectator继承自PC_ReplaySpectator

  4. 打开第一人称&第三人称游戏模式蓝图,设置重播旁观者为创建的PC_ReplaySpectator

  5. 两人称角色蓝图中类默认值开启复制&复制运动,写入录制逻辑

  6. 打开所需录制的地图(一三人称地图)分别对应世界场景设置游戏模式开启所有可移动物细节栏中复制

  7. 项目设置中设置默认地图游戏实例

  8. 创建空地图MainMenuMap,游戏模式选择None关卡蓝图中添加菜单到视口:

    3.开始运行,进入录制或进行回放。


    视频教程:BV17L4y1A7eF​​​​​​​

    【虚幻4】Replay重播系统-UE4.26教程

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值