(个人)太极拳学习系统创新实训第四周

一、上周工作问题的解决

a) 观看模式已经成功合并到主项目当中,统一了一些公用变量。

b) 给用户一个准备的时间,准备动作做好之后说出开始指令后再开始学习。

c) 创建一个UserCharacter作为观看视角,设置类默认值locked to HMD为false以取消VR头盔对视角的控制。

二、回放系统测试

FirstPerson模板上的回放系统测试

首先新建一个FirstPerson的blueprint项目,我这里命名为ReplayTest.创建完成后打开ReplayTest/Config/DefaultEngine.ini,在文件最后添加如下语句然后保存。这个语句的作用就是允许使用DemoNetDriver这个录像功能。

[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")

然后我们需要将所有Map里所有能移动的actor的Static Mesh Replicate Movement属性设置为true:



完成之后打开blueprint文件“FirstPersonProjectile”,将其类默认值值中的“Replicates”与“Replicate Movement”属性都设为true:


完成后打开blueprint文件“FirstPersonCharacter”,同样将“Replicates”与“Replicate Movement”属性设为true。然后找到“InputAction Fire”这个自定义事件,在该事件旁边创建一个自定义事件命名为“Server_Fire”,然后将该事件的属性设置为如图所示:


然后再在蓝图中做一些改动:


接下来我们需要把UE4中的录制与回放方法提供给蓝图以便我们在蓝图中调用。首先创建一个新的C++类,选择父类为GameInstance:


我这里命名为MyGameInstance,创建后会自动打开Visual Studio,先在项目中找到名为“ReplayTest.Build.cs”的C#文件,(注意第一个.前面的字符串为项目名称),打开该文件并添加语句:

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Json" });

添加后的文件内容如下:

然后我们再打开之前创建的MyGameInstance.h文件,首先添加include语句:

#include "NetworkReplayStreaming.h"

注意有些include语句必须放在所有include的最前面或者最后面,如果之后编译出错可以根据错误提示调整其位置。然后我们来添加所需的函数:

public:
	/** Start recording a replay from blueprint. ReplayName = Name of file on disk, FriendlyName = Name of replay in UI */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void StartRecordingReplayFromBP(FString ReplayName, FString FriendlyName);

	/** Start recording a running replay and save it, from blueprint. */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void StopRecordingReplayFromBP();

	/** Start playback for a previously recorded Replay, from blueprint */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void PlayReplayFromBP(FString ReplayName);

	/** Start looking for/finding replays on the hard drive */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void FindReplays();

	/** Apply a new custom name to the replay (for UI only) */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void RenameReplay(const FString &ReplayName, const FString &NewFriendlyReplayName);

	/** Delete a previously recorded replay */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void DeleteReplay(const FString &ReplayName);
virtual void Init() override;

private:

	// for FindReplays() 
	TSharedPtr<INetworkReplayStreamer> EnumerateStreamsPtr;
	FOnEnumerateStreamsComplete OnEnumerateStreamsCompleteDelegate;

	void OnEnumerateStreamsComplete(const TArray<FNetworkReplayStreamInfo>& StreamInfos);

	// for DeleteReplays(..)
	FOnDeleteFinishedStreamComplete OnDeleteFinishedStreamCompleteDelegate;

	void OnDeleteFinishedStreamComplete(const bool bDeleteSucceeded);
protected:
	UFUNCTION(BlueprintImplementableEvent, Category = "Replays")
		void BP_OnFindReplaysComplete(const TArray<FS_ReplayInfo> &AllReplays);

最后再在文件开始处添加语句:

USTRUCT(BlueprintType)
struct FS_ReplayInfo
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(BlueprintReadOnly)
		FString ReplayName;

	UPROPERTY(BlueprintReadOnly)
		FString FriendlyName;

	UPROPERTY(BlueprintReadOnly)
		FDateTime Timestamp;

	UPROPERTY(BlueprintReadOnly)
		int32 LengthInMS;

	UPROPERTY(BlueprintReadOnly)
		bool bIsValid;

	FS_ReplayInfo(FString NewName, FString NewFriendlyName, FDateTime NewTimestamp, int32 NewLengthInMS)
	{
		ReplayName = NewName;
		FriendlyName = NewFriendlyName;
		Timestamp = NewTimestamp;
		LengthInMS = NewLengthInMS;
		bIsValid = true;
	}

	FS_ReplayInfo()
	{
		ReplayName = "Replay";
		FriendlyName = "Replay";
		Timestamp = FDateTime::MinValue();
		LengthInMS = 0;
		bIsValid = false;
	}
};
那么MyGameInstance.h文件就已经全部写好了,再贴一下完整代码:
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "NetworkReplayStreaming.h"
#include "MyGameInstance.generated.h"
USTRUCT(BlueprintType)
struct FS_ReplayInfo
{
	GENERATED_USTRUCT_BODY()

		UPROPERTY(BlueprintReadOnly)
		FString ReplayName;

	UPROPERTY(BlueprintReadOnly)
		FString FriendlyName;

	UPROPERTY(BlueprintReadOnly)
		FDateTime Timestamp;

	UPROPERTY(BlueprintReadOnly)
		int32 LengthInMS;

	UPROPERTY(BlueprintReadOnly)
		bool bIsValid;

	FS_ReplayInfo(FString NewName, FString NewFriendlyName, FDateTime NewTimestamp, int32 NewLengthInMS)
	{
		ReplayName = NewName;
		FriendlyName = NewFriendlyName;
		Timestamp = NewTimestamp;
		LengthInMS = NewLengthInMS;
		bIsValid = true;
	}

	FS_ReplayInfo()
	{
		ReplayName = "Replay";
		FriendlyName = "Replay";
		Timestamp = FDateTime::MinValue();
		LengthInMS = 0;
		bIsValid = false;
	}
};
/**
 * 
 */
UCLASS()
class REPLAYTEST_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
public:
	/** Start recording a replay from blueprint. ReplayName = Name of file on disk, FriendlyName = Name of replay in UI */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void StartRecordingReplayFromBP(FString ReplayName, FString FriendlyName);

	/** Start recording a running replay and save it, from blueprint. */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void StopRecordingReplayFromBP();

	/** Start playback for a previously recorded Replay, from blueprint */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void PlayReplayFromBP(FString ReplayName);

	/** Start looking for/finding replays on the hard drive */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void FindReplays();

	/** Apply a new custom name to the replay (for UI only) */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void RenameReplay(const FString &ReplayName, const FString &NewFriendlyReplayName);

	/** Delete a previously recorded replay */
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void DeleteReplay(const FString &ReplayName);
	virtual void Init() override;

private:

	// for FindReplays() 
	TSharedPtr<INetworkReplayStreamer> EnumerateStreamsPtr;
	FOnEnumerateStreamsComplete OnEnumerateStreamsCompleteDelegate;

	void OnEnumerateStreamsComplete(const TArray<FNetworkReplayStreamInfo>& StreamInfos);

	// for DeleteReplays(..)
	FOnDeleteFinishedStreamComplete OnDeleteFinishedStreamCompleteDelegate;

	void OnDeleteFinishedStreamComplete(const bool bDeleteSucceeded);
protected:
	UFUNCTION(BlueprintImplementableEvent, Category = "Replays")
		void BP_OnFindReplaysComplete(const TArray<FS_ReplayInfo> &AllReplays);
	
	
	
};

然后我们再来编辑MyGameInstance.cpp文件,首先还是先添加include语句:

#include "Runtime/NetworkReplayStreaming/NullNetworkReplayStreaming/Public/NullNetworkReplayStreaming.h"
#include "NetworkVersion.h"
#include "MyGameInstance.h"

之后是所需函数:

void UMyGameInstance::Init()
{
	Super::Init();
	
	// create a ReplayStreamer for FindReplays() and DeleteReplay(..)
	EnumerateStreamsPtr = FNetworkReplayStreaming::Get().GetFactory().CreateReplayStreamer();
	// Link FindReplays() delegate to function
	OnEnumerateStreamsCompleteDelegate = FOnEnumerateStreamsComplete::CreateUObject(this, &UMyGameInstance::OnEnumerateStreamsComplete);
	// Link DeleteReplay() delegate to function
	OnDeleteFinishedStreamCompleteDelegate = FOnDeleteFinishedStreamComplete::CreateUObject(this, &UMyGameInstance::OnDeleteFinishedStreamComplete);
}
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(), FString(), FString(), OnEnumerateStreamsCompleteDelegate);
	}
}

void UMyGameInstance::OnEnumerateStreamsComplete(const TArray<FNetworkReplayStreamInfo>& StreamInfos)
{
	TArray<FS_ReplayInfo> AllReplays;

	for (FNetworkReplayStreamInfo StreamInfo : StreamInfos)
	{
		if (!StreamInfo.bIsLive)
		{
			AllReplays.Add(FS_ReplayInfo(StreamInfo.Name, StreamInfo.FriendlyName, StreamInfo.Timestamp, StreamInfo.LengthInMS));
		}
	}

	BP_OnFindReplaysComplete(AllReplays);
}
void UMyGameInstance::RenameReplay(const FString &ReplayName, const FString &NewFriendlyReplayName)
{	
	// Get File Info
	FNullReplayInfo Info;

	const FString DemoPath = FPaths::Combine(*FPaths::GameSavedDir(), 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, OnDeleteFinishedStreamCompleteDelegate);
	}
}

void UMyGameInstance::OnDeleteFinishedStreamComplete(const bool bDeleteSucceeded)
{
	FindReplays();
}
MyGameInstance.cpp完整代码:
// Fill out your copyright notice in the Description page of Project Settings.

#include "ReplayTest.h"
#include "Modules/ModuleManager.h"
#include "Runtime/NetworkReplayStreaming/NullNetworkReplayStreaming/Public/NullNetworkReplayStreaming.h"
#include "NetworkVersion.h"
#include "MyGameInstance.h"

IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ReplayTest, "ReplayTest" );
void UMyGameInstance::Init()
{
	Super::Init();

	// create a ReplayStreamer for FindReplays() and DeleteReplay(..)
	EnumerateStreamsPtr = FNetworkReplayStreaming::Get().GetFactory().CreateReplayStreamer();
	// Link FindReplays() delegate to function
	OnEnumerateStreamsCompleteDelegate = FOnEnumerateStreamsComplete::CreateUObject(this, &UMyGameInstance::OnEnumerateStreamsComplete);
	// Link DeleteReplay() delegate to function
	OnDeleteFinishedStreamCompleteDelegate = FOnDeleteFinishedStreamComplete::CreateUObject(this, &UMyGameInstance::OnDeleteFinishedStreamComplete);
}
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(), FString(), FString(), OnEnumerateStreamsCompleteDelegate);
	}
}

void UMyGameInstance::OnEnumerateStreamsComplete(const TArray<FNetworkReplayStreamInfo>& StreamInfos)
{
	TArray<FS_ReplayInfo> AllReplays;

	for (FNetworkReplayStreamInfo StreamInfo : StreamInfos)
	{
		if (!StreamInfo.bIsLive)
		{
			AllReplays.Add(FS_ReplayInfo(StreamInfo.Name, StreamInfo.FriendlyName, StreamInfo.Timestamp, StreamInfo.LengthInMS));
		}
	}

	BP_OnFindReplaysComplete(AllReplays);
}
void UMyGameInstance::RenameReplay(const FString &ReplayName, const FString &NewFriendlyReplayName)
{
	// Get File Info
	FNullReplayInfo Info;

	const FString DemoPath = FPaths::Combine(*FPaths::GameSavedDir(), 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, OnDeleteFinishedStreamCompleteDelegate);
	}
}

void UMyGameInstance::OnDeleteFinishedStreamComplete(const bool bDeleteSucceeded)
{
	FindReplays();
}


那么VS的编辑工作暂时告一段落,我们回到UE4中来使用刚刚写好的函数。首先编译,然后新建一个blueprint类,父类为之前创建的“MyGameInstance”,命名为“BP_MyGameInstance”,打开后在函数栏选择覆盖(override)“BP_OnFindReplaysComplete”:



之后我们会用这个函数来与UI连接显示回放列表。接下来新建一个关卡(map),这里命名“MainMenuMap”,打开项目设置,在“地图&模式”(map&mode)下进行如下设置:


然后打开blueprint文件"FirstPersonCharacter",添加如下节点:


这步完成后我们就实现了最基础的录制功能,可以在独立窗口下运行测试一下:


没有问题的话可以在项目目录下的saved/demos下找到录制文件。接下来我们需要做一个简单的UI界面来显示以及回放我们的录制文件。新建一个空间蓝图(widget)命名为”WID_ReplaySlot“,内容如下:


注意ReplayName与ReplayFriendlyName要进行设置:


接下来再新建一个widget,命名为WID_MainMenu,内容如下:



完成后打开MainMenuMap的关卡蓝图来实现widget:


然后我们再回到BP_MyGameInstance完成之前的BPonFindReplaysComplete函数:


这一步完成后我们就可以再次进行测试,检验回放删除等功能。没有问题的话继续下一步,再次新建一个C++类,父类为”Player Controller“,命名为”PC_ReplaySpectator”。在PC_ReplaySpectator.h的class中添加语句:

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_ReplaySpectator.cpp中添加语句:

#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;
		}
	}
}

完成后返回UE4编译。没有问题的话我们再次创建一个widget,命名为“WID_ReplaySpectator”:


对CurrentTime、Slider、MaxTime、PauseText分别添加绑定函数:




注意SliderPickedByUser、CurrentPauseState默认值为False.

事件蓝图:



其中PlayerRateComboBox需要设置:


那么这个控件终于弄好了,最后再创建一个蓝图类,父类为PC_ReplaySpectator,命名为BP_PC_ReplaySpectator:


打开FirstPersonGameMode:


所有工作到这就结束了。

猜你喜欢

转载自blog.csdn.net/qq_17615227/article/details/80120299