Unreal中的SaveGame标记
虚幻中提供了SaveGame类型作为保存游戏的对象,用户可以将需保存的数据直接赋值至SaveGame子类的数据中再将该对象SaveToSlot就可以保存至本地。
https://docs.unrealengine.com/latest/CHN/Gameplay/SaveGame/index.html
但是细心的用户可以在属性的高级设置中看到SaveGame标记,那是否可以像Java之类高级语言一样直接序列化这个对象呢?
我们知道虚幻已经魔改了C++,使之具有了反射的能力,那么序列化的功能是必然具有的,所以下面我们就需要将对象序列化的功能自行改进,并且暴露给蓝图。
Archive类型
Base class for archives that can be used for loading, saving, and garbage collecting in a byte order neutral way.
从Archive类的描述中可以得知这个类型可用于保存和读取,我们就从改装这个类开始。
区分需要序列化的类型
有些类型,比如说UTexture2D、USoundBase之类的对象是已经确定存在与游戏包内,所以不必完全序列化该对象,只需要通过保存的路径读取即可。通过IsAsset()函数可区别。
区分是UObject还是Actor
Actor是需要生成在场景中的,而且组件没法添加SaveGame标记,所以要单独区分。
enum class ESaveLoadType : uint8
{
Static, //不需要序列化,直接读取路径即可
Normal, //需要序列化的UObject类型
Actor //Actor类型
};
FORCEINLINE FArchive& operator<<(FArchive& Ar, const ESaveLoadType& SaveLoadType)
{
uint8 SaveLoadEnumType = (uint8)SaveLoadType;
Ar << SaveLoadEnumType;
return Ar;
}
FORCEINLINE FArchive& operator<<(FArchive& Ar, ESaveLoadType& SaveLoadType)
{
uint8 SaveLoadEnumType = (uint8)SaveLoadType;
Ar << SaveLoadEnumType;
if (Ar.IsLoading())
{
SaveLoadType = (ESaveLoadType)SaveLoadEnumType;
}
return Ar;
}
//假如需要序列化Component继承该接口,当然也可使用别的放法,比如Tag来识别
UINTERFACE(BlueprintType)
class UNeedSaveComponent :public UInterface
{
GENERATED_UINTERFACE_BODY()
};
class INeedSaveComponent
{
GENERATED_IINTERFACE_BODY()
};
继承Archive的子类并且重写<<函数
//序列化使用的类
struct FGameWriteArchive : public FBufferArchive
{
FGameWriteArchive(bool bIsPersistent = false, const FName InArchiveName = NAME_None)
:FBufferArchive(bIsPersistent, InArchiveName)
{
ArIsSaveGame = true; //为真即序列化被标记为SaveGame的属性
}
virtual FArchive& operator<<(class UObject*& Obj) override;
};
//反序列化使用的类
class FGameReadArchive final : public FMemoryArchive
{
public:
UWorld* CurrentWorld;//SpawnActor需要提供World信息
virtual FString GetArchiveName() const { return TEXT("FSaveGameReadArchive"); }
int64 TotalSize()
{
return FMath::Min((int64) Bytes.Num(), LimitSize);
}
void Serialize(void* Data, int64 Num)
{
if (Num && !ArIsError)
{
// Only serialize if we have the requested amount of data
if (Offset + Num <= TotalSize())
{
FMemory::Memcpy(Data, &Bytes[Offset], Num);
Offset += Num;
}
else
{
ArIsError = true;
}
}
}
FGameReadArchive(const TArray<uint8>& InBytes, UWorld* CurrentWorld, bool bIsPersistent = false)
: FMemoryArchive()
, Bytes(InBytes)
, CurrentWorld(CurrentWorld)
, LimitSize(INT64_MAX)
{
ArIsLoading = true;//表示这个是反序列化的类
ArIsPersistent = bIsPersistent;
}
/** With this method it's possible to attach data behind some serialized data. */
void SetLimitSize(int64 NewLimitSize)
{
LimitSize = NewLimitSize;
}
virtual FArchive& operator<<(class UObject*& Obj) override;
protected:
const TArray<uint8>& Bytes;
int64 LimitSize;
};
实现
//不需要序列化的类型填这里
TArray<UClass*> StaticObjectClass