ue4下PS5自定义存储

前言

最近要将xbox游戏移植到ps5,在数据存储这块踩了不少坑,最初打算用ps平台自带的saveGame,但是里面的功能满足不了现有项目。所以只能边研究官方文档边看ps自带的sample。(只贴代码,不作过多讲解了,方便自己闲暇时回顾~~)

自己新建了一个存储插件

在这里插入图片描述

PlatformsGp5Save.h

/************************************************************************************
* PS5 SaveData Class
*/

#pragma once

#include "CoreMinimal.h"
#include <save_data.h>
#include "UObject/NoExportTypes.h"
#include "WorldSubsystem.h"
#include "PlatformsGp5Save.generated.h"

#define SAVE_DATA_PARAM_TITLE		"Sample param title"
#define SAVE_DATA_PARAM_SUB_TITLE	"Sample param sub title"
#define SAVE_DATA_PARAM_DETAIL		"Sample param detail"
#define SAVE_DATA_PARAM_USERPARAM	(0)
#define SAVE_DATA_ID_INVALID		(-1)
#define SAVE_DATA_WAIT_BACKUP_INTERVAL_USEC	(50 * 1000)

#define MOUNT_ROOT_PATH	            "gamedata"
#define LIST_FILE_PATH	            "pathes.sav"

UCLASS()
class PLATFORMS_API UPlatformsGp5Save : public UWorldSubsystem {
	GENERATED_BODY()

public:
	static UPlatformsGp5Save* GetInstance(const UObject* WorldContextObject);

	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

	void waitBackup(const char* dirName);
	void SavePathList(FString content);
	void SplitFileName(const FString& InPath, TArray<FString>& DirNames, FString& FileName);
	void FindSavedGames(TArray<FString>& DirNames);

	bool SaveStringToFile(FString JsonData, const TCHAR* Filename);
	bool CreateDirectoryTree(TArray<FString> DirectoryParts, SceSaveDataMountPoint& mountPoint);
	bool Mount(const FString dirName, SceSaveDataMountPoint& MountPoint);

	int32_t writeFile(const char* mountPoint, const char* fileName, FString JsonData);
	int32_t LoadFileToString(FString& fileName, FString& fileStr);
	int32_t readFile(const char* mountPoint, const char* fileName, FString& fileStr);
	int32_t getParam(const SceSaveDataMountPoint* mountPoint);

	// Get User Id
	bool GetUserId();
	bool InitSaveData();
	bool CreateTransactionResource();


	// setup mount object
	void setupSceSaveDataMount3(const SceUserServiceUserId userId, const SceSaveDataMountMode mode,
		const SceSaveDataDirName* dirName, const SceSaveDataTransactionResourceId transactionResourceId,
		SceSaveDataMount3* mount);

	// prepare
	int32_t prepare(const SceSaveDataMountPoint* mountPoint,
		const SceSaveDataTransactionResourceId transactionResourceId,
		const SceSaveDataPrepareMode prepareMode);

	//E Set a parameter
	int32_t setParam(const SceSaveDataMountPoint* mountPoint,
		const char* title = SAVE_DATA_PARAM_TITLE,
		const char* subTitle = SAVE_DATA_PARAM_SUB_TITLE,
		const char* detail = SAVE_DATA_PARAM_DETAIL,
		const uint32_t userParam = SAVE_DATA_PARAM_USERPARAM);

	//E Commit update of save data
	int32_t commit(const SceSaveDataTransactionResourceId transactionResourceId);

private:
	// sec save data
	int32_t m_saveDataInitId;
	SceUserServiceUserId m_userId;
	SceSaveDataDirName m_dirName;
	SceSaveDataTransactionResourceId m_transactionResourceId;

	SceSaveDataDirName SavedGameDirs[SCE_SAVE_DATA_DIRNAME_MAX_COUNT];
};

PlatformsGp5Save.cpp


/************************************************************************************
* PS5 SaveData Class
*/

#include "PlatformsGp5Save.h"
#include <user_service.h>
#include <scebase_common.h>
#include <string.h>
#include <_fs.h>
#include "UnrealMemory.h"


void UPlatformsGp5Save::Initialize(FSubsystemCollectionBase& Collection)
{
	// initialize
	InitSaveData();
	// get user id
	GetUserId();
	// create transaction id
	CreateTransactionResource();
}

void UPlatformsGp5Save::Deinitialize()
{
	int32_t ret = sceSaveDataTerminate();
	if (ret < SCE_OK)
	{
		printf("sceSaveDataTerminate : 0x%08x\n", ret);
		// through
	}
}

UPlatformsGp5Save* UPlatformsGp5Save::GetInstance(const UObject* WorldContextObject)
{
	if (WorldContextObject != nullptr)
	{
		return UWorld::GetSubsystem<UPlatformsGp5Save>(WorldContextObject->GetWorld());
	}
	return nullptr;
}

void UPlatformsGp5Save::SplitFileName(const FString& Path, TArray<FString>& DirNames, FString& FileName)
{
	FString ResultPath(Path);
	ResultPath.ParseIntoArray(DirNames, TEXT("/"), true);

	FileName = DirNames[DirNames.Num() - 1];
	DirNames.RemoveAt(DirNames.Num() - 1);
}

void UPlatformsGp5Save::SavePathList(FString content)
{
	// read native files
	FString Path = LIST_FILE_PATH;
	FString fileStr;
	int32_t ret = LoadFileToString(Path, fileStr);
	if (ret < SCE_OK)
	{
		printf("readFile : 0x%08x\n", ret);
	}

	// init data
	TArray<FString> DirNames;
	fileStr.ParseIntoArray(DirNames, TEXT("/n"), true);

	TMap<FString, bool> SaveListMap;
	for (int32 i = 0; i < DirNames.Num(); i++)
	{
		SaveListMap.Add(DirNames[i], true);
	}

	// add 
	if (!SaveListMap.Contains(content)) {
		SaveListMap.Add(content, true);

		 mount 
		SceSaveDataMountPoint mountPoint;
		if (!Mount(MOUNT_ROOT_PATH, mountPoint)) {
			return;
		}


		// write
		FString pathes = "";
		for (TPair<FString, bool> Elem : SaveListMap) {
			pathes = pathes + Elem.Key + TEXT("/n");
			//pathes.Append(FString::Printf(TEXT("&%s%s/n"), *pathes, *Elem.Key));
		}

		ret = writeFile(mountPoint.data, TCHAR_TO_ANSI(*Path), pathes);
		if (ret < SCE_OK)
		{
			printf("writeFile : 0x%08x\n", ret);
		}

		//  commit 
		ret = commit(m_transactionResourceId);
		if (ret < SCE_OK) {

			printf("sceSaveDataUmount2 : 0x%08x\n", ret);
		}

		//E Unmount
		//J アンマウント
		SceSaveDataMountPoint* mPoint = &mountPoint;
		int32_t ret2 = sceSaveDataUmount2(SCE_SAVE_DATA_UMOUNT_MODE_DEFAULT, mPoint);
		if (ret2 < SCE_OK)
		{
			printf("sceSaveDataUmount2 : 0x%08x\n", ret2);
		}


	}
}

bool UPlatformsGp5Save::SaveStringToFile(FString JsonData, const TCHAR* Filename)
{
	// split dirs 
	FString Path(Filename);
	TArray<FString> DirectoryParts;
	FString FileName;
	SplitFileName(Path, DirectoryParts, FileName);

	// mount 
	SceSaveDataMountPoint mountPoint;
	if (!Mount(MOUNT_ROOT_PATH, mountPoint))
		return false;

	// create dirtree
	if (!CreateDirectoryTree(DirectoryParts, mountPoint))
		return false;

	//  write content
	int32 ret = writeFile(mountPoint.data, TCHAR_TO_ANSI(*Path), JsonData);
	if (ret < SCE_OK)
	{
		printf("writeFile : 0x%08x\n", ret);
		commit(m_transactionResourceId);
		return false;
	}

	//  commit 
	ret = commit(m_transactionResourceId);
	if (ret < SCE_OK) {
		return false;
	}

	//E Unmount
	//J アンマウント
	int32_t ret2 = sceSaveDataUmount2(SCE_SAVE_DATA_UMOUNT_MODE_DEFAULT, &mountPoint);
	if (ret2 < SCE_OK)
	{
		printf("sceSaveDataUmount2 : 0x%08x\n", ret2);
	}

	// save path
	SavePathList(Path);


	return true;
}

//E Read the saved data
//J セーブデータ読み込み
int32_t UPlatformsGp5Save::LoadFileToString(FString& fileName, FString& fileStr)
{
	//TRACE;
	int32_t ret = SCE_OK;

	//E Wait for finish to create backup data
	//J バックアップデータ作成の完了を待つ
	waitBackup(m_dirName.data);

	//E Mount
	//J マウント
	FString MountDirName(MOUNT_ROOT_PATH);
	FMemory::Memzero(&m_dirName, sizeof(m_dirName));
	strlcpy(m_dirName.data, TCHAR_TO_ANSI(*MountDirName), sizeof(m_dirName.data));

	SceSaveDataMount3 mount3;
	setupSceSaveDataMount3(m_userId,
		SCE_SAVE_DATA_MOUNT_MODE_RDONLY,
		&m_dirName,
		SCE_SAVE_DATA_TRANSACTION_RESOURCE_ID_INVALID,
		&mount3);
	SceSaveDataMountResult mountResult;
	FMemory::Memzero(&mountResult, sizeof(mountResult));
	ret = sceSaveDataMount3(&mount3, &mountResult);
	if (ret < SCE_OK && ret != SCE_SAVE_DATA_ERROR_BUSY)
	{
		//EPRINT("sceSaveDataMount3 : 0x%08x(%s)\n", ret, m_dirName.data);
		return ret;
	}

	SceSaveDataMountPoint* mountPoint = &mountResult.mountPoint;

	ret = readFile(mountPoint->data, TCHAR_TO_ANSI(*fileName), fileStr);
	if (ret < SCE_OK)
	{
		printf("readFile : 0x%08x\n", ret);
		goto End;
	}

	ret = SCE_OK;

End:
	//E Unmount
	//J アンマウント
	int32_t ret2 = sceSaveDataUmount2(SCE_SAVE_DATA_UMOUNT_MODE_DEFAULT, mountPoint);
	if (ret2 < SCE_OK)
	{
		printf("sceSaveDataUmount2 : 0x%08x\n", ret2);
	}

	return ret;
}

bool UPlatformsGp5Save::CreateDirectoryTree(TArray<FString> DirectoryParts, SceSaveDataMountPoint& mountPoint)
{
	FString mPath = mountPoint.data;
	for (int32 i = 0; i < DirectoryParts.Num(); ++i)
	{
		mPath /= DirectoryParts[i];
		int32 Ret = sceKernelMkdir(TCHAR_TO_ANSI(*mPath), SCE_KERNEL_S_IRWU);
		if (Ret != SCE_OK && Ret != SCE_KERNEL_ERROR_EEXIST)
		{
			return false;
		}
	}

	return true;
}

void UPlatformsGp5Save::setupSceSaveDataMount3(const SceUserServiceUserId userId, const SceSaveDataMountMode mode,
	const SceSaveDataDirName* dirName, const SceSaveDataTransactionResourceId transactionResourceId,
	SceSaveDataMount3* mount)
{
	FMemory::Memzero(mount, sizeof(SceSaveDataMount3));
	mount->userId = userId;
	mount->dirName = dirName;
	mount->blocks = SCE_SAVE_DATA_BLOCKS_MAX2;
#ifdef SAVE_DATA_CONFIG_ENABLE_ROLLBACK
	mount->systemBlocks = SCE_SAVE_DATA_SYSTEM_BLOCKS_EQUAL_TO_BLOCKS;
#else
	mount->systemBlocks = 0;
#endif
	mount->mountMode = mode;
	mount->resource = transactionResourceId;

	return;
}

bool UPlatformsGp5Save::InitSaveData()
{
	int32_t ret = sceSaveDataInitialize3(NULL);
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("sceUserServiceInitialize() failed in Mount(). Error code: 0x%08x"), ret);
		return false;
	}
	m_saveDataInitId = ret;
	return true;
}

bool UPlatformsGp5Save::GetUserId()
{
	m_userId = SAVE_DATA_ID_INVALID;
	int32_t ret = sceUserServiceGetInitialUser(&m_userId);
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("sceUserServiceGetInitialUser() failed in Mount(). Error code: 0x%08x"), ret);
		return false;
	}
	return true;
}

bool UPlatformsGp5Save::CreateTransactionResource()
{
	int32_t ret = sceSaveDataCreateTransactionResource(0);
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("sceSaveDataCreateTransactionResource() failed in Mount(). Error code: 0x%08x"), ret);
		return false;
	}
	m_transactionResourceId = ret;
	return true;
}

bool UPlatformsGp5Save::Mount(const FString dirName, SceSaveDataMountPoint& MountPoint)
{
	FString MountDirName(dirName);
	// mount dirName
	//memset(mount, 0x00, sizeof(SceSaveDataMount3));
	FMemory::Memzero(&m_dirName, sizeof(m_dirName));

	strlcpy(m_dirName.data, TCHAR_TO_ANSI(*MountDirName), sizeof(m_dirName.data));

	// mount3 init
	SceSaveDataMount3 mount3;
	setupSceSaveDataMount3(m_userId,
		SCE_SAVE_DATA_MOUNT_MODE_CREATE2,
		&m_dirName,
		m_transactionResourceId,
		&mount3);
	SceSaveDataMountResult mountResult;
	FMemory::Memzero(&mountResult, sizeof(mountResult));

	int32_t ret = sceSaveDataMount3(&mount3, &mountResult);
	if (ret < SCE_OK && ret != SCE_SAVE_DATA_ERROR_BUSY)
	{
		UE_LOG(LogTemp, Warning, TEXT("sceSaveDataMount3() failed in Mount(). Error code: 0x%08x"), ret);
		return false;
	}

	SceSaveDataMountPoint* TmountPoint = &mountResult.mountPoint;

	MountPoint = mountResult.mountPoint;

	//E Prepare update of save data
	SceSaveDataPrepareMode prepareMode = SCE_SAVE_DATA_PREPARE_MODE_DEFAULT;
#ifdef SAVE_DATA_CONFIG_ENABLE_ROLLBACK
	prepareMode |= SCE_SAVE_DATA_PREPARE_MODE_ENABLE_CANCEL;
#endif
	ret = prepare(TmountPoint, m_transactionResourceId, prepareMode);
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("prepare() failed in Mount(). Error code: 0x%08x"), ret);
		goto End;
	}

	//E Set parameters
	ret = setParam(TmountPoint);
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("setParam() failed in Mount(). Error code: 0x%08x"), ret);
		commit(m_transactionResourceId);
		goto End;
	}

End:

	return true;
}


//E Prepare update of save data
int32_t UPlatformsGp5Save::prepare(const SceSaveDataMountPoint* mountPoint,
	const SceSaveDataTransactionResourceId transactionResourceId,
	const SceSaveDataPrepareMode prepareMode)
{
	int32_t ret = SCE_OK;

	SceSaveDataPrepareParam prepareParam;
	FMemory::Memzero(&prepareParam, sizeof(prepareParam));
	prepareParam.resource = transactionResourceId;
	prepareParam.prepareMode = prepareMode;

	ret = sceSaveDataPrepare(mountPoint, &prepareParam);
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("sceSaveDataPrepare() failed in Mount(). Error code: 0x%08x"), ret);
		return ret;
	}

	return SCE_OK;
}

//E Set a parameter
int32_t UPlatformsGp5Save::setParam(const SceSaveDataMountPoint* mountPoint,
	const char* title/*=SAVE_DATA_PARAM_TITLE*/,
	const char* subTitle/*=SAVE_DATA_PARAM_SUB_TITLE*/,
	const char* detail/*=SAVE_DATA_PARAM_DETAIL*/,
	const uint32_t userParam/*=SAVE_DATA_PARAM_USERPARAM*/)
{
	int32_t ret = SCE_OK;

	SceSaveDataParam param;
	FMemory::Memzero(&param, sizeof(param));

	strlcpy(param.title, title, sizeof(param.title));
	strlcpy(param.subTitle, subTitle, sizeof(param.subTitle));
	strlcpy(param.detail, detail, sizeof(param.detail));

	param.userParam = SAVE_DATA_PARAM_USERPARAM;

	ret = sceSaveDataSetParam(mountPoint,
		SCE_SAVE_DATA_PARAM_TYPE_ALL,
		&param, sizeof(param));
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("sceSaveDataSetParam() failed in Mount(). Error code: 0x%08x"), ret);
		return ret;
	}

	return SCE_OK;
}

//E Commit update of save data
int32_t UPlatformsGp5Save::commit(const SceSaveDataTransactionResourceId transactionResourceId)
{
	int32_t ret = SCE_OK;

	SceSaveDataCommitParam commitParam;
	FMemory::Memzero(&commitParam, sizeof(commitParam));
	commitParam.resource = transactionResourceId;
	ret = sceSaveDataCommit(&commitParam);
	if (ret < SCE_OK)
	{
		UE_LOG(LogTemp, Warning, TEXT("sceSaveDataCommit() failed in Mount(). Error code: 0x%08x"), ret);
		return ret;
	}

	return SCE_OK;
}

//E Write the save data
//E Create the file
//J ファイル作成
int32_t UPlatformsGp5Save::writeFile(const char* mountPoint, const char* fileName, FString JsonData)
{
	int32_t ret = SCE_OK;

	//E Write a file
	char path[64];
	snprintf(path, sizeof(path), "%s/%s", mountPoint, fileName);
	int32_t fd = sceKernelOpen(path, SCE_KERNEL_O_RDWR | SCE_KERNEL_O_TRUNC | SCE_KERNEL_O_CREAT, SCE_KERNEL_S_IRWU);
	if (fd < SCE_OK)
	{
		printf("sceKernelOpen : 0x%08x(%s)\n", fd, path);
		return fd;
	}

	//FString data_buff = TEXT("{\n\t\"starttime\": \"apr 6, 2023, 10:07:02 am\",\n\t\"select_count\": \"maingame1\"\n}");
	const size_t dummyBufSize = JsonData.Len();

	// data init 
	unsigned char* dummyBuf = new unsigned char[dummyBufSize];
	if (!dummyBuf)
	{
		return -1;
	}

	char* temp = TCHAR_TO_ANSI(*JsonData);
	for (int i = 0; i < dummyBufSize; i++) {
		dummyBuf[i] = temp[i];
	}


	//memset(dummyBuf, 0x00, sizeof(*dummyBuf));
	//dummyBuf = TCHAR_TO_ANSI(*JsonData);
	ret = static_cast<int32_t>(sceKernelWrite(fd, dummyBuf, dummyBufSize));
	if (ret < SCE_OK)
	{
		printf("sceKernelWrite : 0x%08x\n", ret);
		sceKernelClose(fd);
		return ret;
	}

	sceKernelClose(fd);
	return SCE_OK;
}

//E Read a file (+ dump)
//E In the case where the file system of the save data is corrupted, the error processing during reading is required 
//E because the corruption might be detected during file reading.
//J ファイル読み込み(+ダンプ)
//J セーブデータのファイルシステムが破損しているケースは、ファイル読み込み時に破損検出される場合もあるので
//J 読み込み時にエラー処理は必須です。
int32_t UPlatformsGp5Save::readFile(const char* mountPoint, const char* fileName, FString& fileStr)
{
	int32_t ret = SCE_OK;

	char path[64];
	snprintf(path, sizeof(path), "%s/%s", mountPoint, fileName);
	SceKernelStat st;
	ret = sceKernelStat(path, &st);
	if (ret < SCE_OK)
	{
		printf("sceKernelStat : 0x%08x\n", ret);
		return ret;
	}
	char* data = new char[st.st_size];
	if (!data)
	{
		return -1;
	}

	//E Read a file
	//E If the file system is corrupted, an error might occur during file opening/reading.
	//J ファイル読み込み
	//J ファイルシステムが壊れている場合は、ファイルオープン/リード時にエラーになる可能性がある
	int fd = sceKernelOpen(path, SCE_KERNEL_O_RDONLY, SCE_KERNEL_S_INONE);
	if (fd < SCE_OK)
	{
		printf("sceKernelOpen : 0x%08x\n", fd);
		delete[] data;
		return fd;
	}

	ret = static_cast<int32_t>(sceKernelRead(fd, data, static_cast<size_t>(st.st_size)));
	if (ret < SCE_OK)
	{
		//EPRINT("sceKernelRead : 0x%08x(%s)\n", ret, path);
		//goto End;
	}

	fileStr = ANSI_TO_TCHAR(data);
	fileStr = fileStr.Left(st.st_size);

	ret = SCE_OK;

	//End:
	sceKernelClose(fd);
	delete[] data;

	return ret;
}

//E Search
void UPlatformsGp5Save::FindSavedGames(TArray<FString>& DirNames)
{
	// read native files
	FString Path = LIST_FILE_PATH;
	FString fileStr;
	int32_t ret = LoadFileToString(Path, fileStr);
	if (ret < SCE_OK)
	{
		printf("readFile : 0x%08x\n", ret);
	}

	// init data
	fileStr.ParseIntoArray(DirNames, TEXT("/n"), true);
}

//E Get paramters
//J パラメータ取得
int32_t UPlatformsGp5Save::getParam(const SceSaveDataMountPoint* mountPoint)
{
	int32_t ret = SCE_OK;

	SceSaveDataParam param;
	FMemory::Memzero(&param, sizeof(param));

	size_t gotSize = 0;
	ret = sceSaveDataGetParam(mountPoint,
		SCE_SAVE_DATA_PARAM_TYPE_ALL,
		&param, sizeof(param), &gotSize);
	if (ret < SCE_OK)
	{
		return ret;
	}

	/*PRINT("get param\n");
	PRINT("%20s : %s\n", "title", param.title);
	PRINT("%20s : %s\n", "subTitle", param.subTitle);
	PRINT("%20s : %s\n", "detail", param.detail);
	PRINT("%20s : %u\n", "userParam", param.userParam);*/

	char str[64];
	struct tm tmData;
	gmtime_s(&param.mtime, &tmData);
	strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", &tmData);

	return SCE_OK;
}


void UPlatformsGp5Save::waitBackup(const char* dirName)
{
#if defined(SAVE_DATA_CONFIG_CREATE_BACKUP_DATA) || !defined(SAVE_DATA_CONFIG_ENABLE_ROLLBACK)

	SceSaveDataEvent event;
	while (1)
	{
		FMemory::Memzero(&event, sizeof(event));
		int32_t ret = sceSaveDataGetEventResult(NULL, &event);
		if (ret == SCE_SAVE_DATA_ERROR_EVENT_BUSY)
		{
			sceKernelUsleep(SAVE_DATA_WAIT_BACKUP_INTERVAL_USEC);
			continue;
		}
		else if (ret == SCE_SAVE_DATA_ERROR_NOT_FOUND)
		{
			break;
		}
		else if (ret < SCE_OK)
		{
			break;
		}

		if (strncmp(event.dirName.data, dirName, sizeof(event.dirName.data)) == 0)
		{
			break;
		}
	}

#endif

	return;
}

PlatformsManager.h

管理工具

#include "CoreMinimal.h"
#include "EngineMinimal.h"

namespace PlatformsManager
{
	void Write(const UObject* object, FString JsonData, const FString Filename);
	void Read(const UObject* object, const FString Path, FString& fileStr);

	void GetSaveGames(const UObject* object, FString SaveRootDir, TArray<FString>& SavFiles);

	FString StripResultPath(const FString& Path);
	bool IsGp5Dir(const FString& Path);	
}

PlatformsManager.cpp

#include "PlatformsManager.h"
#include "PlatformsGp5Save.h"

void PlatformsManager::Write(const UObject* object, FString JsonData, const FString Path)
{
	FString FinalSavedFilePath(Path);
	FString platform_name = UGameplayStatics::GetPlatformName();
	if (platform_name == "PS5" && IsGp5Dir(FinalSavedFilePath)) {
		FinalSavedFilePath = PlatformsManager::StripResultPath(FinalSavedFilePath);
		UPlatformsGp5Save::GetInstance(object)->SaveStringToFile(JsonData, *FinalSavedFilePath);
	}
	else
	{
		FFileHelper::SaveStringToFile(JsonData, *FinalSavedFilePath);
	}
}

void PlatformsManager::Read(const UObject* object, const FString Path, FString& fileStr)
{
	FString SingleFilePath(Path);
	FString platform_name = UGameplayStatics::GetPlatformName();
	if (platform_name == "PS5" && IsGp5Dir(SingleFilePath)) {
		SingleFilePath = StripResultPath(SingleFilePath);
		int32_t ret = UPlatformsGp5Save::GetInstance(object)->LoadFileToString(SingleFilePath, fileStr);
		if (ret < SCE_OK)
		{
			//printf("readFile : 0x%08x\n", ret);
			//continue;
		}
	}
	else
	{
		FFileHelper::LoadFileToString(fileStr, *SingleFilePath);
	}
}

void PlatformsManager::GetSaveGames(const UObject* object, FString SaveRootDir,TArray<FString>& SavFiles)
{
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	if (!PlatformFile.DirectoryExists(*SaveRootDir))
	{
		PlatformFile.CreateDirectory(*SaveRootDir);
	}

	FString platform_name = UGameplayStatics::GetPlatformName();
	if (platform_name == "PS5") {
		// read native files
		UPlatformsGp5Save::GetInstance(object)->FindSavedGames(SavFiles);
	}
	else
	{
		FString Suffix = FString(".sav");
		PlatformFile.FindFiles(SavFiles, *SaveRootDir, *Suffix);
	}
}

FString PlatformsManager::StripResultPath(const FString& Path)
{
	FString ResultPath(Path);
	int32 DotDotSlashIdx = ResultPath.Find(TEXT("../"), ESearchCase::CaseSensitive);
	while (DotDotSlashIdx != INDEX_NONE)
	{
		ResultPath = ResultPath.Right(ResultPath.Len() - (DotDotSlashIdx + 3));
		DotDotSlashIdx = ResultPath.Find(TEXT("../"), ESearchCase::CaseSensitive);
	}
	return ResultPath;
}


bool PlatformsManager::IsGp5Dir(const FString& Path)
{
	FString ResultPath = StripResultPath(Path);
	if (ResultPath.StartsWith(TEXT("/devlog/app/"), ESearchCase::CaseSensitive) || ResultPath.StartsWith(TEXT("devlog/app/"), ESearchCase::CaseSensitive))
	{
		return false;
	}
	return true;
}
### UE5 中后期处理参考图材质的使用方法 在 Unreal Engine 5 (UE5) 的后期处理中,参考图材质扮演着重要角色。这些材质允许开发者利用多种纹理输入来实现复杂的视觉效果。 #### 创建基础后期处理材质 为了创建一个有效的后期处理材质,在 Shader 类型下拉菜单中选择“Post Process”。这种类型的材质可以访问特定于后期处理的效果所需的特殊参数和节点[^2]。 ```cpp // 设置材质属性为 PostProcessMaterial UMaterialInterface* MaterialInstance = UMaterialFactoryNew::CreatePostProcessMaterial(); ``` #### 利用 SceneTexture 节点获取场景数据 SceneTexture 是一种特殊的纹理资源,能够提供关于当前渲染帧的信息。对于边缘检测等操作来说非常重要的是 `SceneColor` 和 `CustomDepth` 这两个选项。前者代表了最终合成的颜色图像;后者则存储自定义深度信息,可用于区分前景对象与背景之间的边界[^3]。 #### 实现简单的边缘描边效果 基于上述提到的技术细节,可以通过以下方式构建一个简易版本: 1. **计算相邻像素差异** - 使用 Sample Texture 结合 UV 坐标偏移采样周围四个方向上的 CustomDepth 或者 SceneDepth 数据。 2. **比较并标记边缘区域** - 对比中心位置与其他四个样本点之间是否存在显著变化(即绝对差值超过阈值)。如果满足条件,则认为该处属于物体轮廓部分[^1]。 3. **混合输出结果** - 应用 Lerp 函数根据是否被识别为边缘决定返回原始色彩还是指定好的高亮色作为最终显示内容。 ```hlsl float EdgeThreshold = 0.1; float CenterDepth = TexelFetch(SceneTextureId, int2(UV * ScreenSize)).r; bool IsEdgePixel(float DepthSample) { return abs(DepthSample - CenterDepth) >= EdgeThreshold; } fixed4 PS_Main(VS_OUTPUT input): SV_Target { float LeftDepth = TexelFetchOffset(SceneTextureId, int2((input.UV - float2(1., 0.)) * ScreenSize), int2(-1, 0)).r; float RightDepth = TexelFetchOffset(SceneTextureId, int2((input.UV + float2(1., 0.)) * ScreenSize), int2(+1, 0)).r; float TopDepth = TexelFetchOffset(SceneTextureId, int2((input.UV - float2(0., 1.)) * ScreenSize), int2(0, -1)).r; float BottomDepth = TexelFetchOffset(SceneTextureId, int2((input.UV + float2(0., 1.)) * ScreenSize), int2(0, +1)).r; bool bIsOutline = any(bool4( IsEdgePixel(LeftDepth), IsEdgePixel(RightDepth), IsEdgePixel(TopDepth), IsEdgePixel(BottomDepth))); fixed4 OutlineColor = fixed4(0, 0, 0, 1); return lerp(tex2D(_MainTex, input.Texcoord), OutlineColor, step(bIsOutline, 0)); } ``` 此代码片段展示了如何在一个后期处理着色器内执行基本的边缘检测逻辑,并据此调整输出颜色以突出显示目标模型周围的线条特征。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值