UE4 SaveGame保存游戏注意事项

保存数据的流程参考官方文档的例子足够(https://docs.unrealengine.com/latest/CHN/Gameplay/SaveGame/index.html)
这里说一些注意事项以及对SaveGame使用相关的分析理解。

1.首先,UE提供的游戏保存的基本原理是序列化对象,通过构建一个SaveGame对象,并把要保存的数据赋给该对象的成员属性,再把SaveGame对象序列化保存到磁盘上来实现该功能。查看SaveGame类可以看到,他直接继承于UObject并且没有任何成员变量及函数。所以,保存对象的细节不在于SaveGame对象,而在于UObject序列化里面的相关内容。

//MySaveGame.h
#pragma once

#include "GameFramework/SaveGame.h"
#include "MySaveGame.generated.h"

/**
 * 
 */
UCLASS()
class [PROJECTNAME]_API UMySaveGame : public USaveGame
{
    GENERATED_BODY()

    public:

    UPROPERTY(VisibleAnywhere, Category = Basic)
    FString PlayerName;

    UPROPERTY(VisibleAnywhere, Category = Basic)
    FString SaveSlotName;

    UPROPERTY(VisibleAnywhere, Category = Basic)
    uint32 UserIndex;

    UMySaveGame();
};

//MySaveGame.cpp
#include "[ProjectName].h"
#include "MySaveGame.h"

UMySaveGame::UMySaveGame()
{
    SaveSlotName = TEXT("TestSaveSlot");
    UserIndex = 0;
}

2.所有保存与加载游戏的逻辑操作都在GamePlayStatics.cpp的SaveGame相关函数里面,如果自己要实现自己的存档机制(完全可以不使用SaveGame对象),可以参考SaveGameToSlot等函数。下面给出了保存游戏涉及到的所有类:
这里写图片描述

//传入一个USaveGame的class类型,然后UGameplayStatics负责帮你创建一个USaveGame对象
USaveGame* UGameplayStatics::CreateSaveGameObject(TSubclassOf<USaveGame> SaveGameClass)
{
	// Don't save if no class or if class is the abstract base class.
	if (*SaveGameClass && (*SaveGameClass != USaveGame::StaticClass()))
	{
		return NewObject<USaveGame>(GetTransientPackage(), SaveGameClass);
	}
	return nullptr;
}
//调用SaveGameToSlot把刚才的新建的savegame传入,同时指定存档文件的名称,首先调用SaveGameToMemory把savegame对象的序列化到内存里面,然后再通过SaveDataToSlot序列化到本地磁盘
bool UGameplayStatics::SaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex)
{
	// This is a wrapper around the functions reading to/from a byte array
	TArray<uint8> ObjectBytes;
	if (SaveGameToMemory(SaveGameObject, ObjectBytes))
	{
		return SaveDataToSlot(ObjectBytes, SlotName, UserIndex);
	}
	return false;
}

//FMemoryWriter负责USaveGame的属性序列化到内存,FObjectAndNameAsStringProxyArchive负责将序列化的属性转换成可读的文字。SaveGameObject->Serialize(Ar)开始真正的执行序列化操作,
//具体序列化的逻辑是集成在UObject里面 在void UObject::Serialize(FStructuredArchive::FRecord Record) 里 ,Engine\Source\Runtime\CoreUObject\Private\UObject\Obj.cpp
//得到的结果存储在OutSaveData
bool UGameplayStatics::SaveGameToMemory(USaveGame* SaveGameObject, TArray<uint8>& OutSaveData )
{
	if (SaveGameObject)
	{
		FMemoryWriter MemoryWriter(OutSaveData, true);

		FSaveGameHeader SaveHeader(SaveGameObject->GetClass());
		SaveHeader.Write(MemoryWriter);

		// Then save the object state, replacing object refs and names with strings
		FObjectAndNameAsStringProxyArchive Ar(MemoryWriter, false);
		SaveGameObject->Serialize(Ar);

		return true; // Not sure if there's a failure case here.
	}

	return false;
}
//根据前面得到的序列化后的OutSaveData,进行本地磁盘的存储SaveGame
bool UGameplayStatics::SaveDataToSlot(const TArray<uint8>& InSaveData, const FString& SlotName, const int32 UserIndex)
{
	ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem();

	if (SaveSystem && InSaveData.Num() > 0 && SlotName.Len() > 0)
	{
		// Stuff that data into the save system with the desired file name
		return SaveSystem->SaveGame(false, *SlotName, UserIndex, InSaveData);
	}

	return false;
}

//具体存储到本地的逻辑
virtual bool SaveGame(bool bAttemptToUseUI, const TCHAR* Name, const int32 UserIndex, const TArray<uint8>& Data) override
{
	return FFileHelper::SaveArrayToFile(Data, *GetSaveGamePath(Name));
}

//load 同理
USaveGame* UGameplayStatics::LoadGameFromSlot(const FString& SlotName, const int32 UserIndex)
{
	// This is a wrapper around the functions reading to/from a byte array
	TArray<uint8> ObjectBytes;
	if (LoadDataFromSlot(ObjectBytes, SlotName, UserIndex))
	{
		return LoadGameFromMemory(ObjectBytes);
	}

	return nullptr;
}

static USaveGame* LoadGameFromMemory(const TArray<uint8>& InSaveData);
static bool LoadDataFromSlot(TArray<uint8>& OutSaveData, const FString& SlotName, const int32 UserIndex);

Archive是“档案”的意思,在UE里面表示数据存储的基类,序列化得到二进制的数据一般会临时存储在Archive里面。FMemoryArchive是内存数据存储,就是将当前的数据写到内存里面(这里是写到一个 TArray&Bytes;数组里面),进而存档。用它来辅助UObject的序列化以及反序列化操作。

3.SaveGameToSlot以及LoadGameFromSlot里面的最后参数UserIndex在非H5平台上并没有作用。你保存文件的名字就是SlotName.sav文件,与UserIndex无关。
这里写图片描述

4.我们知道每个属性前面可以添加SaveGame宏,但是不添加这个宏也可以在保存游戏时执行对属性的序列化,官方的例子就没有标记这个宏。那怎么理解这个宏?
官方的解释是: This specifier is a simple way to include fields explicitly for a checkpoint/save system at the property level. The flag should be set on all fields that are intended to be part of a saved game, and then a proxy archiver can be used to read/write it.
其实目前引擎里面虽然有对SaveGame宏的处理,但是默认情况下并不要求SaveGame宏。如果你在撰写自己的存档系统,你可以把所有的需要存储序列化的属性都标记为SaveGame宏,如果不不标记就不让他序列化存储,这样会让代码看起来更为清晰。具体方式是在存储的时候修改Archive的ArIsSaveGame为true
这里写图片描述

/**
* Indicates whether this archive is saving or loading game state
* @return true if the archive is dealing with save games, false otherwise.
*/
virtual bool IsSaveGame()
{
	return ArIsSaveGame;
}


原文链接(转载请标明):http://blog.csdn.net/u012999985/article/details/78493261

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值