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

上周做了FirstPerson模板的回放系统,本周我主要在主项目中编写回放系统。遇到了很多问题。还没有完全解决。

1)打开Config/DefaultEngine.ini,添加如下语句保存以允许使用DemoNetDriver:

[/Script/Engine.GameEngine]  
+NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")  
创建一个父类为MyGameInstance的C++类,命名为 MyGameInstance。打开Visual Studio,先打开 项目名.Build.cs 这个C#文件,添加语句:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Json" });  
然后完成头文件与CPP文件:
//头文件
// 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 TAICHIPROJECT_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 EnumerateStreamsPtr;
	FOnEnumerateStreamsComplete OnEnumerateStreamsCompleteDelegate;

	void OnEnumerateStreamsComplete(const TArray& StreamInfos);

	// for DeleteReplays(..)
	FOnDeleteFinishedStreamComplete OnDeleteFinishedStreamCompleteDelegate;

	void OnDeleteFinishedStreamComplete(const bool bDeleteSucceeded);
protected:
	UFUNCTION(BlueprintImplementableEvent, Category = "Replays")
		void BP_OnFindReplaysComplete(const TArray &AllReplays);
	
};
//CPP
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyGameInstance.h"
#include "TaiChiProject.h"
#include "Runtime/NetworkReplayStreaming/NullNetworkReplayStreaming/Public/NullNetworkReplayStreaming.h"
#include "NetworkVersion.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& StreamInfos)
{
	TArray 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 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 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中生成解决方案。这里我犯了一个错,我选择了重新生成解决方案。这样将整个项目重新编译,因为我的UE是安装版而不是源码版,重新编译的话会导致dll文件丢失,用到的一些插件也会报错。还好有github能回退到之前的版本,但是发现以及问题的过程浪费了很多时间。

MyGameInstance写好后就可以进行测试,在UE中创建一个MyGameInstance的子蓝图类BP_MyGameInstance。将项目的GameInstance设置为BP_MyGameInstance。然后在UserCharacter中调用相关函数:

(个人)太极拳学习系统创新实训第五周_第1张图片

运行开始时调用StartRecording,通过键盘事件结束录制然后播放。注意要在独立窗口运行。这里又遇到一个问题,根据查看文件目录可以确定录制没有问题,但是播放时场景为空。经过添加一些辅助物体作为参照物,发现原因是播放回放时的旁观者对象spectator生成位置与userCharacter位置不一致,为了解决这个问题我将场景位置做了一个平移以适配spectator的位置。再次测试能够看到场景但是只是静态的,并没有动态的录像。经过论坛上的查询,可能是gamemode中的default pawn class与spectator class不一致的原因。于是我将UserCharacter的父类从pawn改为spectator pawn,将gamemode的default pawn class与spectator class都设为UserCharacter,结果还是不行。我怀疑是不能在园关卡直接回放,两者会造成冲突。于是我新建了一个关卡,专门用来播放回放。然后发现可行。那么按照这个方案的话我需要再额外做一些工作,将录制好的文件复制到名为NewReplay的文件夹中,然后所有文件重命名为NewReplay,这样直接在回放关卡中播放NewReplay就可以了。

先贴一下相关函数的代码:

//头文件的函数声明
	UFUNCTION(BlueprintCallable, Category = "Save")
		static bool CreateDirectory() ;
	UFUNCTION(BlueprintCallable, Category = "Save")
		static FString CopyDirectory(FString oldPath);
//CPP中的函数定义
 bool USaveToTxt::CreateDirectory() 
 {
	 FString Dir = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay"));
	 IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	 if (!PlatformFile.DirectoryExists(*Dir))
	 {
		 PlatformFile.CreateDirectory(*Dir);

		 if (!PlatformFile.DirectoryExists(*Dir))
		 {
			 return false;
			 //~~~~~~~~~~~~~~
		 }
	 }
	 return true;
 }
 FString USaveToTxt::CopyDirectory(FString oldPath)
 {
	 FString AbsoluteSourcePath = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/"));
	 AbsoluteSourcePath = FPaths::Combine(AbsoluteSourcePath, oldPath);
	 FString AbsoluteDestinationPath = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay"));
	 /*if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*AbsoluteSourcePath))
	 {
		 
		 return;
	 }

	 if (!FPlatformFileManager::Get().GetPlatformFile().MoveFile(*AbsoluteDestinationPath, *AbsoluteSourcePath))
	 {
		
	 }*/
	 IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	 PlatformFile.CopyDirectoryTree(*AbsoluteDestinationPath, *AbsoluteSourcePath, true);
	 FString oldName = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/"));
	 oldName = FPaths::Combine(oldName, oldPath);
	 FString temp = oldPath + ".demo";
	 FString oldName1 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/"), temp);
	 temp = oldPath + ".header";
	 FString oldName2 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/"), temp);
	 temp = oldPath + ".replayinfo";
	 FString oldName3 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/"), temp);
	 temp = oldPath + ".final";
	 FString oldName4 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/"), temp);

	 FString newName = FPaths::Combine(AbsoluteDestinationPath, TEXT("/newReplay"));
	 FString newName1 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/newReplay.demo"));
	 FString newName2 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/newReplay.header"));
	 FString newName3 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/newReplay.replayinfo"));
	 FString newName4 = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/newReplay/newReplay.final"));
	 
	 PlatformFile.MoveFile(*newName1, *oldName1);
	 PlatformFile.MoveFile(*newName2, *oldName2);
	 PlatformFile.MoveFile(*newName3, *oldName3);
	 PlatformFile.MoveFile(*newName4, *oldName4);
	 FString s=  FPaths::Combine(*FPaths::GameSavedDir(), TEXT("Demos/Replay_2018-5-6-20-5/Replay_2018-5-6-20-5.demo"));
	 FString x = FPaths::Combine(AbsoluteDestinationPath, TEXT("/Replay_2018-5-6-20.demo"));
	 PlatformFile.MoveFile(*AbsoluteDestinationPath, *AbsoluteSourcePath);
	 return oldName1;
 }

然后解释一下函数的意思。首先需要一个创建一个C++类,我之前写个一个SaveToTxt,这里就继续使用。头文件中要注意UFUNCTION的第一个参数,设为BlueprintPure,生成的蓝图节点不会带有执行引脚。设为BlueprintCallable则会有执行引脚。然后是CPP文件,首先关于UE4的文件操作可以参考:点击打开链接。CreateDirectory() 用来创建NewReplay 文件夹,FPaths::GameSavedDir()用来获取项目文件下的saved目录,注意combine()会默认添加"\"字符,比如combine("newReplay","xxx")的结果就是"newReplay\xxx",如果是combine("newReplay\","xxx")结果还是"newReplay\xxx",也就是说如果你没有自己加上"\",该函数会自己加上,这个问题又是花了很多时间才发现。还有就是moveFile既可以移动文件,也可以起到重命名的作用,具体请看参考链接。

文件操作写完了后,再次进行测试,结果发现还是没有解决问题,没办法只好到官方论坛提问,希望能有人帮忙解决。接下来先把这个问题放下,完成回放系统界面操作。

你可能感兴趣的:((个人)太极拳学习系统创新实训第五周)