准备:
PlayerInfo.json:
[ { "MaxHp": 10 }, { "Hp": 3 }, { "Mode": "Crazy" }, { "Inventory": [ { "0": "Sword" }, { "1": "Shield" }, { "2": "Foot" } ] } ]
过程:
创建一个C++空项目。
把PlayerInfo.json放到项目的Content\Data目录下。
打开JH.Build.cs文件,为项目添加Json模块:
using UnrealBuildTool; public class JH : ModuleRules { public JH(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); PrivateDependencyModuleNames.AddRange(new string[] { }); PublicDependencyModuleNames.AddRange(new string[] { "Json", "JsonUtilities" }); } }
创建一个继承于蓝图函数库类。
DataHandle.h:
#pragma once #include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" //使用Json相关的操作需要Json.h #include "Json.h" #include "DataHandle.generated.h" /** * */ //模式枚举 UENUM(BlueprintType) enum class EMode : uint8 { Easy = 0, Normal = 1, Crazy = 2, }; UCLASS() class JH_API UDataHandle : public UBlueprintFunctionLibrary { GENERATED_BODY() public: //根据文件名与文件路径读取Json数据 UFUNCTION(BlueprintCallable, Category = Json) static void LoadDataFromJson(const FString& FileName, const FString& FilePath, float& MaxHp, float& Hp, EMode& Mode, TArray<FString>& Inventory); //把MaxHp、Hp、Mode、Inventory存入指定目录下的文件里 UFUNCTION(BlueprintCallable, Category = Json) static void RecordDataToJson(const FString& FileName, const FString& FilePath, float MaxHp, float Hp, EMode Mode, TArray<FString> Inventory); private: //打印错误信息 static void Debug(const FString& Message); //在指定枚举类型中寻找该值是否存在,存在则返回其枚举值 template<typename TEnum> static TEnum GetEnumFromString(const FString& Name, const FString& Value); //把指定枚举类型的值,作为字符串返回 template<typename TEnum> static FString GetEnumAsString(const FString& Namw, const TEnum& Value); }; template<typename TEnum> inline static TEnum UDataHandle::GetEnumFromString(const FString& Name, const FString& Value) { const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *Name, true); if (!EnumPtr) { Debug(FString("Enum[") + Name + FString("] can't be found!")); return TEnum(0); } return (TEnum)EnumPtr->GetIndexByName(FName(*FString(Value))); } template<typename TEnum> FString UDataHandle::GetEnumAsString(const FString& Name, const TEnum& Value) { const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *Name, true); if (!EnumPtr) { Debug(FString("Enum[") + Name + FString("] can't be found!")); return FString(" "); } return EnumPtr->GetNameStringByIndex((int32)Value); }
DataHandle.cpp:
#include "DataHandle.h" void UDataHandle::LoadDataFromJson(const FString& FileName, const FString& FilePath, float& MaxHp, float& Hp, EMode& Mode, TArray<FString>& Inventory) { /*根据文件路径把文件数据保存到FString中*/ FString LoadResult; FString Mode_Str; if (!FileName.IsEmpty()) { FString Path = FPaths::ProjectContentDir() + FilePath + FileName; if (FPaths::FileExists(Path)) { if (FFileHelper::LoadFileToString(LoadResult, *Path)) { /*进行解析原始数据*/ TArray<TSharedPtr<FJsonValue>> ParsedData; //TJsonReaderFactory<TCHAR>的Create()方法会创建并返回一个TJsonReader<TCHAR>类型的值 TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(LoadResult); //对原始数据进行反序列化操作,把结果放入到解析数据中 /* * 对象序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。 * 从字节流创建对象的相反的过程称为反序列化。 * 以某种存储形式使自定义对象持久化。 **/ if (FJsonSerializer::Deserialize(JsonReader, ParsedData)) { /*从反序列化完成的数据中提取各个属性值*/ MaxHp = ParsedData[0]->AsObject()->GetNumberField(FString("MaxHp")); Hp = ParsedData[1]->AsObject()->GetNumberField(FString("Hp")); Mode_Str = ParsedData[2]->AsObject()->GetStringField(FString("Mode")); TArray<TSharedPtr<FJsonValue>> ItemArray = ParsedData[3]->AsObject()->GetArrayField(FString("Inventory")); for (int i = 0; i < ItemArray.Num(); i++) { FString Item = ItemArray[i]->AsObject()->GetStringField(FString::FromInt(i)); Inventory.Add(Item); } //把模式字符串转换为模式枚举类型 Mode = GetEnumFromString<EMode>(FString("EMode"), Mode_Str); } else { Debug(FString("Failed to Deseriazlize!")); } } else { Debug(FString("Failed to load file! --- ") + Path); } } else { Debug(FString("File does not exist!-- - ") + Path); } } else { Debug(FString("FileName can't be empty!")); } } void UDataHandle::RecordDataToJson(const FString& FileName, const FString& FilePath, float MaxHp, float Hp, EMode Mode, TArray<FString> Inventory) { //把模式从枚举转换成字符串 const FString Mode_Str = GetEnumAsString(FString("EMode"), Mode); //外层的{} TSharedPtr<FJsonObject> BaseObject = MakeShareable(new FJsonObject); //外层的[] TArray<TSharedPtr<FJsonValue>> BaseValue; //{"MaxHp":MaxHp} TSharedPtr<FJsonObject> MaxHpObject = MakeShareable(new FJsonObject); MaxHpObject->SetNumberField("MaxHp", MaxHp); TSharedPtr<FJsonValueObject> MaxHpValue = MakeShareable(new FJsonValueObject(MaxHpObject)); //{"Hp":Hp} TSharedPtr<FJsonObject> HpObject = MakeShareable(new FJsonObject); HpObject->SetNumberField("Hp", Hp); TSharedPtr<FJsonValueObject> HpValue = MakeShareable(new FJsonValueObject(HpObject)); //{"Mode":Mod} TSharedPtr<FJsonObject> ModeObject = MakeShareable(new FJsonObject); ModeObject->SetStringField("Mode", Mode_Str); TSharedPtr<FJsonValueObject> ModeValue = MakeShareable(new FJsonValueObject(ModeObject)); //背包字符串数组指针 TArray<FString>* InventoryPtr = &Inventory; //里层的[] TArray<TSharedPtr<FJsonValue>> ItemArray; for (int i = 0; i < InventoryPtr->Num(); ++i) { //{"0":Sword} //{"1":Food} // ... TSharedPtr<FJsonObject> ItemObject = MakeShareable(new FJsonObject); ItemObject->SetStringField(FString::FromInt(i), (*InventoryPtr)[i]); TSharedPtr<FJsonValueObject> ItemValue = MakeShareable(new FJsonValueObject(ItemObject)); ItemArray.Add(ItemValue); } //{"Inventory":[...]} TSharedPtr<FJsonObject> InventoryObject = MakeShareable(new FJsonObject); InventoryObject->SetArrayField("Inventory", ItemArray); TSharedPtr<FJsonValueObject> InventoryValue = MakeShareable(new FJsonValueObject(InventoryObject)); //外层的[ {"MaxHp":10}, {"Hp":Hp}, ... ] BaseValue.Add(MaxHpValue); BaseValue.Add(HpValue); BaseValue.Add(ModeValue); BaseValue.Add(InventoryValue); //外层的{"T",外层的[ ... ]} BaseObject->SetArrayField("T", BaseValue); FString Data_Str; if (BaseObject.IsValid() && BaseObject->Values.Num() > 0) { //把整个原始数据序列化,并写入字符串 TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&Data_Str); FJsonSerializer::Serialize(BaseObject.ToSharedRef(), JsonWriter); //移除开头字符串:{"T", Data_Str.RemoveAt(0, 8); //移除末尾字符串:} Data_Str.RemoveFromEnd(FString("}")); if (!Data_Str.IsEmpty()) { if (!FileName.IsEmpty()) { FString AbsPath = FPaths::ProjectContentDir() + FilePath + FileName; //把字符串保存为指定目录下的文件 if (FFileHelper::SaveStringToFile(Data_Str, *AbsPath)) { Debug(FString("Save data successfully!")); } else { Debug(FString("Save " + AbsPath + FString(" failed!"))); } } else { Debug("FileName cannot be empty!"); } } else { Debug("Data that needs to be saved is empty!"); } } else { Debug(FString("Fail to be JsonObject!")); } } void UDataHandle::Debug(const FString& Message) { if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, Message); } }
编译后生成的蓝图节点:
首先测试加载数据节点:
测试结果:
接着测试保存数据节点:
Macro_LoadData使用的就是第一次测试的方法。
测试结果:
补充:
class JSON_API FJsonObject
{
public:
TMap<FString, TSharedPtr<FJsonValue>> Values;
{
public:
TMap<FString, TSharedPtr<FJsonValue>> Values;