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

2023-11-03

前言: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教程

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【UE4】Replay游戏回放 for UE4.26 的相关文章

  • RAIN{INDIE} 自动寻路

    Unity游戏中有较多的自动寻路插件 看过几个 大同小异 而RAIN中的Behavior Tree感觉很好 听名字就知道很条理 下面 就用它做个简单的寻路小例子 首先 导入RAIN的包 结构如下 在使用的过程当中还会产生几个文件夹用来存放E
  • 二进制在数学中的妙用

    二进制在数学中的妙用 goal00001111搜集整理 十 八世纪初 莱布尼茨发明了二进制数 当时的他肯定没有预料到二进制在信息时代会有着如此广泛的应用 二进制数以其工作可靠 运算简单 逻辑严密 容易实现 等特点 成为了计算机的专用语言 在
  • LUA中判断GameObject是否被Destory

    function IsNil uobj return uobj nil or uobj Equals nil end 你懂的 转载于 https www cnblogs com vsirWaiter p 7884249 html
  • HTML5游戏实战(2):90行代码实现捕鱼达人

    捕鱼达人是一款非常流行的游戏 几年里赚取了数以千万的收入 这里借用它来介绍一下用Gamebuilder CanTK开发游戏的方法 其实赚钱的游戏未必技术就很难 今天我们就仅用90来行代码来实现这个游戏 CanTK Canvas ToolKi
  • Python——报数出圈

    编写程序 模拟报数游戏 有n个人围成一圈 顺序编号 从第一个人开始从1到k 假设k 3 报数 报到k的人退出圈子 然后圈子缩小 从下一个人继续游戏 问最后留下的是原来的第几号 n int input 请输入总人数 n k int input
  • java实现简单的生成52张牌、三个人洗牌、码牌算法

    定义一个Pocker类 用于定义牌类 package demo public class Poker private String suit 花色 private int rank 数字 构造函数 public Poker String s
  • 【UGUI】2D头顶血条制作

    前言 近期因为需要制作玩家和敌人头顶的2D血条 查找了很多博客 发现很多都拘束于Canvas的渲染模式必须要设定为ScreenSpace Overlay 还有应该是版本原因 我的是unity2019 1 11f1 用RecttTransfo
  • UE4 制作导出Content目录下某个文件夹内所有模型的六视图并将模型资源文件复制到指定文件夹的插件

    一 新建空白插件 在Bulid cs内加入两个模块 EditorSubsystem UnrealEd PublicDependencyModuleNames AddRange new string Core EditorSubsystem
  • Unity中loading页加载的实现

    首先创建一个Global cs 使用单例用于存储场景的名字 便于后续脚本的调用 此脚本不必挂载在游戏物体上 using UnityEngine using System Collections public class Global Mon
  • Python实现迷宫游戏

    项目 迷宫游戏 摘要 1 引言 1 1研究的背景及意义 1 2研究的内容 2 系统结构 2 1系统的结构 2 2基本思路 3 实现代码 3 1Maze类 3 2Player类 3 3Controller类 3 4主函数 4 实验 5 总结和
  • Unity动画系统详解

    目录 动画编辑器 编辑器面板 动画复用 前言 人形重定向动画 Humanoid 通用动画 Generic 旧版本动画 Legacy 动画控制器 系统状态 切换条件 状态机脚本 IK动画 反向动力学 BlendTree 混合树 Animato
  • CocosCreator列表scrollview滑动速度的修改,鼠标滚动速度修改

    由于cocos creator 在pc端 使用scrollview 鼠标滚动速度太慢 原文地址 CocosCreator列表滑动速度的修改 简书CocosCreator列表滑动速度的修改 简书引擎版本 2 2 2 之后升级的2 4 0直接可
  • visual studio 一直显示正在准备解决方案

    首先重启电脑 无法解决的情况下执行以下步骤 Kill Visual Studio Open Visual Studio without loading a solution Disable AnkhSvn as Source Control
  • 坦克大战(二)

    欢迎来到程序小院 坦克大战 二 玩法 键盘 A W S D 键来控制方向 空格键发射子弹 N 下一关 P 上一关 Enter 开始 赶紧去闯关吧 开始游戏 https www ormcc com play gameStart 221 htm
  • 收单外包服务机构的评级工作已经开始了!

    评级收单外包服务机构的工作不仅是金融监管部门规范和引导收单外包服务市场的重要举措 也影响着外包机构的业务发展 并且是每年最重要的合规工作之一 中国支付清算协会于2023年12月18日发布了一份关于进行2023年度收单外包服务机构评级工作的通
  • 广告竞价策略:激发广告变现潜能的关键

    在数字化时代 广告已经成为企业推广品牌 产品和服务的关键手段之一 为了最大程度地发挥广告的效果 广告竞价策略成为广告主和数字营销专业人士关注的焦点 通过巧妙运用竞价策略 广告主可以在激烈的市场竞争中脱颖而出 实现广告变现的潜能 admaoy
  • 游戏开发常见操作系列之敌人系统的开发一(U3D)

    在开发游戏的过程中 我们常常会出现一些敌人攻击我们玩家 并且实现掉血以及死亡的现象 敌人还会源源不断地生成 这是怎么制作的呢 接下来为大家提供方法 其中使用了NGUI 后续会更新其它方法 敬请期待 使用HUDText实现扣血时显示文本 直接
  • 游戏开发中常见系统梳理之背包系统的实现一

    游戏中几乎都存在大大小小的背包系统 接下来我将讲述背包系统具体是如何实现的 完整源码 以下是使用unity NGUI实现 使用txt配置的方法 后续更新UGUI Json实现的背包系统敬请期待 背包中的物品我们常常将其制作成预设体 通过改变
  • 案例研究:YGG 如何通过 GAP 帮助 Pixels 扩大玩家群体

    在 Sky Mavis 联合创始人 Jeffrey Jihoz Zirlin 在 YGG Web3 游戏峰会 W3GS 上发表主题演讲时 他向在场的人们透露 MMO 农场游戏 Pixels 的日活跃用户数已经超过了 130 000 人 这使
  • 计算机Java项目|java游戏账号交易系统

    作者简介 Java领域优质创作者 CSDN博客专家 CSDN内容合伙人 掘金特邀作者 阿里云博客专家 51CTO特邀作者 多年架构师设计经验 腾讯课堂常驻讲师 主要内容 Java项目 Python项目 前端项目 人工智能与大数据 简历模板

随机推荐