(Individual) The fifth week of Taijiquan learning system innovation training

Last week I did the playback system of the FirstPerson template, and this week I mainly wrote the playback system in the main project. Encountered a lot of problems. Not fully resolved yet.

1) Open Config/DefaultEngine.ini, add the following statement to save to allow the use of DemoNetDriver:

[/Script/Engine.GameEngine]  
+NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")  
Create a C++ class whose parent class is MyGameInstance and name it MyGameInstance. Open Visual Studio, first open the project name.Build.cs C# file, add the statement:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Json" });  
Then complete the header file and CPP file:
 
 
//head File
// 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<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);
	
};
// 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<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();
}

 
 

Then we generate the solution in VS. I made a mistake here, I chose to regenerate the solution. In this way, the entire project will be recompiled, because my UE is the installation version instead of the source version. Recompiling will cause the dll file to be lost, and some plug-ins used will also report errors. Fortunately, there is github that can roll back to the previous version, but the process of discovery and problems wastes a lot of time.

After MyGameInstance is written, it can be tested, and a sub-blueprint class BP_MyGameInstance of MyGameInstance is created in UE. Set the project's GameInstance to BP_MyGameInstance. Then call the relevant function in UserCharacter:


Call StartRecording at the beginning of the run, end recording and then play through keyboard events. Note to run in a separate window. Another problem is encountered here. According to the file directory, it can be determined that there is no problem with the recording, but the scene is empty during playback. After adding some auxiliary objects as reference objects, I found that the reason is that the generation position of the spectator object during playback is inconsistent with the position of the userCharacter. In order to solve this problem, I made a translation of the scene position to fit the position of the spectator. Tested again to be able to see the scene but it is only static, and there is no dynamic recording. After inquiries on the forum, it may be the reason why the default pawn class in gamemode is inconsistent with the spectator class. So I changed the parent class of UserCharacter from pawn to spectator pawn, and set both the default pawn class and spectator class of gamemode to UserCharacter, but it still didn't work. I suspect that it cannot be played directly in the garden level, and the two will cause conflict. So I created a new level just for playback. Then find it works. Then according to this plan, I need to do some extra work, copy the recorded files to a folder named NewReplay, and then rename all files to NewReplay, so that NewReplay can be played directly in the playback level.

First paste the code of the relevant function:

//头文件的函数声明
	UFUNCTION(BlueprintCallable, Category = "Save")
		static bool CreateDirectory() ;
	UFUNCTION(BlueprintCallable, Category = "Save")
		static FString CopyDirectory(FString oldPath);
//function definition in 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;
 }

Then explain what the function means. First, you need to create a C++ class. I wrote a SaveToTxt before, and I will continue to use it here. In the header file, pay attention to the first parameter of UFUNCTION, set to BlueprintPure, the generated blueprint node will not have execution pins. Set to BlueprintCallable to have execution pins. Then there is the CPP file. First, you can refer to the file operation of UE4: click to open the link . CreateDirectory() is used to create the NewReplay folder, FPaths::GameSavedDir() is used to get the saved directory under the project file, note that combine() will add the "\" character by default, such as combine("newReplay","xxx") The result is "newReplay\xxx", if it is combine("newReplay\","xxx") the result is still "newReplay\xxx", that is to say, if you don't add "\" yourself, the function will add it by itself, this The problem again took a lot of time to discover. In addition, moveFile can both move files and rename them. For details, please refer to the reference link.

After the file operation is finished, I test it again. It turns out that the problem is still not solved. I have no choice but to ask questions in the official forum. I hope someone can help solve it. Next, put this problem aside and complete the playback system interface operation.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325523837&siteId=291194637