【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 的相关文章

  • 第一篇:PyGame小游戏——2D迷宫游戏(16W字详解)

    目录 在开头的开场白 在CSDN看到一篇 利用深度优先算法自动生成随机迷宫 的blog 突发灵感 就想做这个迷宫游戏 本文大部分讲的意思 而非代码 文章最后会展示最终代码和图片的 读者不用过多地注意那些 本文是作者第一次写blog 难免有些
  • HTML5游戏实战(2):90行代码实现捕鱼达人

    捕鱼达人是一款非常流行的游戏 几年里赚取了数以千万的收入 这里借用它来介绍一下用Gamebuilder CanTK开发游戏的方法 其实赚钱的游戏未必技术就很难 今天我们就仅用90来行代码来实现这个游戏 CanTK Canvas ToolKi
  • 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
  • Unity中loading页加载的实现

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

    1 跳跃游戏 leetcode 55 跳跃游戏 1 问题描述 给定一个非负整数数组 n u m s nums nums 你最初位于数组的 第一个下标 数组中的每个元素代表你在该位置可以跳跃的最大长度 判断你是否能够到达最后一个下标 示例 1
  • Unity动画系统详解

    目录 动画编辑器 编辑器面板 动画复用 前言 人形重定向动画 Humanoid 通用动画 Generic 旧版本动画 Legacy 动画控制器 系统状态 切换条件 状态机脚本 IK动画 反向动力学 BlendTree 混合树 Animato
  • 什么是页面文件使用率

    你好 很高兴能看到你的问题 也很高兴我能够回答你的问题 你提问 什么是页面文件使用率 首先我们必须要了解什么叫 页面文件 页面文件是一个存放在硬盘上的文件 大多数情况下都放在系统磁盘 如C 盘 的根目录下 这个文件不允许用户访问 只能够被操
  • Unity之获取游戏物体对象或组件的几个方法

    文章目录 前言 通过物体名称获取对象 GameObject Find Transform Find 通过物体标签获取对象 GameObject FindWithTag GameObject FindGameObjectWithTag Gam
  • 捕鱼游戏源码(数值+完整项目资源)

    目前捕鱼游戏的玩法 逐渐有这些趋势 捕鱼玩法 消除类玩法 捕鱼玩法 模拟经营玩法 捕鱼玩法 建造养成玩法 这些趋势已经有龙头企业逐渐开始做出尝试 但是对大部分团队来讲 对垂直领域的理解不够深刻 对产品理解不够深刻 团队没有沉淀和积累 通常都
  • Unity保存图片到相册

    Unity保存图片到Android相册 Java 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
  • 探索元宇宙链游戏:一场数字世界的奇妙融合

    随着互联网的飞速发展 以及人们不断对互动娱乐体验的要求提高 元宇宙渐渐成为人们追求的目标 而区块链技术的出现给元宇宙链游开发带来了新的机遇和挑战 一 元宇宙链游定义 元宇宙链游全称为基于区块链技术的元宇宙游戏 是一种新型的网络互动娱乐形式
  • 华为OD机试真题-游戏分组-2023年OD统一考试(C卷)

    题目描述 部门准备举办一场王者荣耀表演赛 有10名游戏爱好者参与 分为两队 每队5人 每位参与者都有一个评分 代表着他的游戏水平 为了表演赛尽可能精彩 我们需要把10名参赛者分为实力尽量相近的两队 一队的实力可以表示为这一队5名队员的评分总
  • 坦克大战(二)

    欢迎来到程序小院 坦克大战 二 玩法 键盘 A W S D 键来控制方向 空格键发射子弹 N 下一关 P 上一关 Enter 开始 赶紧去闯关吧 开始游戏 https www ormcc com play gameStart 221 htm
  • 坦克大战(二)

    欢迎来到程序小院 坦克大战 二 玩法 键盘 A W S D 键来控制方向 空格键发射子弹 N 下一关 P 上一关 Enter 开始 赶紧去闯关吧 开始游戏 https www ormcc com play gameStart 221 htm
  • 全面解析找不到xinput1_3.dll无法继续执行代码的多种解决方案(实用教程)

    xinput1 3 dll文件是什么 xinput1 3 dll是一个动态链接库文件 它是DirectInput的组件之一 DirectInput是微软公司开发的一种输入设备驱动程序 用于处理游戏控制器 键盘 鼠标等输入设备的信号 xinp
  • 计算机提示vcruntime140.dll丢失的解决方法,多种修复教程分享

    vcruntime140 dll是一个非常重要的动态链接库文件 它包含了许多运行时的函数和类 然而 有时候我们可能会遇到vcruntime140 dll无法继续执行代码的问题 这会给我们带来很大的困扰 那么 这个问题是什么原因导致的呢 又应
  • 游戏开发常见操作梳理之NPC药品商店系统(NGUI版)

    后续会出UGUI Json的版本 敬请期待 游戏开发中经常会出现药品商店 实际操作与武器商店类似 甚至根据实际情况可以简化设置 废话不多说 直接上代码 药品商店的源码 using System Collections using Syste
  • 申泰勇教练的独家人物化身系列即将登陆 The Sandbox

    申泰勇 Shin Tae yong 教练是足球界的传奇人物 他来到 The Sandbox 推出了自己的专属人物化身系列 作为前 K 联赛中场球员和印尼队取得历史性成就的幕后教练 他的传奇经历现在已经影响到了虚拟世界 向过去 现在和未来致敬
  • 2024年华为OD机试真题-虚拟游戏理财-Python-OD统一考试(C卷)

    题目描述 在一款虚拟游戏中生活 你必须进行投资以增强在虚拟游戏中的资产以免被淘汰出局 现有一家Bank 它提供有若干理财产品m 风险及投资回报不同 你有N 元 进行投资 能接受的总风险值为X 你要在可接受范围内选择最优的投资方式获得最大回报

随机推荐