やあ
ゲームのセーブ、ロードの実装中にAttributeSetをJsonに保存したいなーって思って調べたよ。
前置き
Json
関連のものを使えるようにするためにモジュールを追加する。
PublicDependencyModuleNames.AddRange(new string[] { "Json", "JsonUtilities" });
Json
の読み書きに使うヘッダー。
/* 必須 */ #include "Json.h" /* ファイルの読み込みや書き込みに使う */ #include "Misc/FileHelper.h" /* UStructからFJsonObjectに変えたりするときに使う */ #include "JsonUtilities/Public/JsonObjectConverter.h"
FJsonObject
名前と値を保持してくれる。
TSharedPtr<FJsonObject> JsonObj = MakeShareable(new FJsonObject);
例えば、キャラクターの体力を保持したい場合。
JsonObj->SetNumberField(”Health”, 60);
この時、中身は
{ "Health" : 60 }
になる。
ネストしたい場合。
例、キャラクターのIDの中に、体力の情報を入れる。
TSharedPtr<FJsonObject> RootJsonObj = MakeShareable(new FJsonObject); TSharedPtr<FJsonObject> ElementJsonObj = MakeShareable(new FJsonObject); ElementJsonObj->SetNumberField("Health", 60); RootJsonObj->SetObjectField("CharacterID", ElementJsonObj);
この時、中身は
{ "CharacterID" : { "Health" : 60 } }
になる。
作ったFJsonObject
を保存する。
FString OutPutString; /* OutPutStringに書き込むように指定する */ const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutPutString); /* JsonObjに保持している名前と値をWriterに書き込む */ FJsonSerializer::Serialize(JsonObj.ToSharedRef(), Writer); /* Project/Saved/ に保存する */ FFileHelper::SaveStringToFile(OutPutString, *(FPaths::ProjectSavedDir() + "Filename.json"));
Json
ファイルを読み込む。
FString LoadJsonStringData; /* LoadJsonStringDataにJsonの中身を読み込む */ if (FFileHelper::LoadFileToString(LoadJsonStringData, "FileFullPath") { TSharedPtr<FJsonObject> LoadedJsonObj = MakeShareable(new FJsonObject); /* Jsonの読み込み先をLoadJsonStringDataにする */ const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(LoadJsonStringData); /* ReaderからLoadJsonObjに */ if (FJsonSerializer::Deserialize(Reader, LoadJsonObj)) { TSharedPtr<FJsonObject> CharacterData = LoadJsonObj->GetObjectField("CharacterID"); float Health = CharacterData->GetNumberField("Health"); } }
ここまで理解できればすぐにできるよ。
やる
こんな感じの構造を想定してるよ。
{ "CharacterA": [ { "Health": { "baseValue": 100, "currentValue": 100 } }, { "MaxHealth": { "baseValue": 100, "currentValue": 100 } } ], "CharacterB": [ { "Health": { // 以下省略 }
Jsonに書き込む
#include "Json.h" #include "Misc/FileHelper.h" #include "JsonUtilities/Public/JsonObjectConverter.h" #include "Kismet/GameplayStatics.h" void UHogeSaveGame::BeginSave(const UObject* _WorldContextObject) { TArray<AActor*> OutActors; UGameplayStatics::GetAllActorsOfClass(_WorldContextObject, AHogeCharacter::StaticClass(), OutActors); TSharedPtr<FJsonObject> SaveJsonObj = MakeShareable(new FJsonObject()); for (AActor* Actor : OutActors) { AHogeCharacter* Character = Cast<AHogeCharacter>(Actor); if (Character) { SaveAttributeSet(Character, SaveJsonObj); } } FString OutPutString; const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutPutString); FJsonSerializer::Serialize(SaveJsonObj.ToSharedRef(), Writer); FFileHelper::SaveStringToFile(OutPutString, *FileFullPath); } void UHogeSaveGame::SaveAttributeSet(AHogeCharacter* InTarget, TSharedPtr<FJsonObject>& SaveJsonObj) { if (!InTarget) return; /* 念のため、AbilitySystemComponentを持っているかを調べる */ if (!InTarget->GetAbilitySystemComponent()) return; if (!InTarget->HogeAttributeSet) return; UHogeAttributeSet* HogeAttributeSet = InTarget->HogeAttributeSet; if (!HogeAttributeSet) return; /* AttributeSetの中身をひとまとめにするための配列 */ TArray<TSharedPtr<FJsonValue>> Values; for (TFieldIterator<FProperty> It(HogeAttributeSet->GetClass(), EFieldIteratorFlags::IncludeSuper); It; ++It) { FProperty* Property = *It; FStructProperty* StructProperty = CastField<FStructProperty>(Property); if (!StructProperty) continue; /* StructPropertyをHogeAttributeSetからFGameplayAttributeDataにする */ FGameplayAttributeData* DataPtr = StructProperty->ContainerPtrToValuePtr<FGameplayAttributeData>(HogeAttributeSet); if (!DataPtr) continue; /* FGameplayAttributeDataからFJsonObject型に変換する */ TSharedPtr<FJsonObject> Obj = FJsonObjectConverter::UStructToJsonObject<FGameplayAttributeData>(*DataPtr); if (!Obj) continue; /* プロパティに値に名前を付けるためのJsonObj */ TSharedPtr<FJsonObject> JsonObj_ForPropertyName = MakeShareable(new FJsonObject()); /* JsonObj_ForPropertyNameの中身は { "Health" : { "baseValue" : 100, "currentValue" : 100 } } */ JsonObj_ForPropertyName->SetObjectField(Property->GetName(), Obj); /* JsonObjectをJsonValueに変換する */ TSharedRef<FJsonValueObject> JsonValue = MakeShareable(new FJsonValueObject(JsonObj_ForPropertyName)); /* プロパティを配列に追加 */ Values.Add(JsonValue); } /* CharacterIDにAttributeSetの中身を保持する */ SaveJsonObj->SetArrayField(”CharacterID”, Values); } }
これでAttributeSet
の中身をJsonに保存することができたよ。
Jsonから読み込み
void UCharacterSaveGame::LoadAttributeSet(AHogeCharacter* InTarget) { FString LoadJsonStringData; if (FFileHelper::LoadFileToString(LoadJsonStringData, *FileFullPath)) { TSharedPtr<FJsonObject> LoadJsonObj = MakeShareable(new FJsonObject()); const TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(LoadJsonStringData); if (FJsonSerializer::Deserialize(JsonReader, LoadJsonObj)) { UHogeAttributeSet* HogeAttributeSet = InTarget->HogeAttributeSet; TArray<TSharedPtr<FJsonValue>> Values = LoadJsonObj->GetArrayField(CharacterID); TMap<FString, TSharedPtr<FJsonValue>> ValueWithPropertyName; /* CharacterIDごとにAttributeSetのプロパティをLoadJsonObjから読み込んでおく */ for (TSharedPtr<FJsonValue>& Value : Values) { ValueWithPropertyName.Append(Value->AsObject()->Values); } for (TFieldIterator<FProperty> It(HogeAttributeSet->GetClass(), EFieldIteratorFlags::IncludeSuper); It; ++It) { FProperty* Property = *It; FStructProperty* StructProperty = CastField<FStructProperty>(Property); if (!StructProperty) continue; FGameplayAttributeData* DataPtr = StructProperty->ContainerPtrToValuePtr<FGameplayAttributeData>(MainStatusAttributeSet); if (!DataPtr) continue; TSharedPtr<FJsonValue>* PropertyValue = ValueWithPropertyName.Find(Property->GetName()); if (!PropertyValue) continue; FGameplayAttributeData OutData; /* こんなデータをFGameplayAttributeDataに読み込む { "Health" : { "baseValue" : 100, "currentValue" : 100 } } */ TSharedPtr<FJsonObject> AttributeDataObj = (*PropertyValue)->AsObject(); FJsonObjectConverter::JsonObjectToUStruct<FGameplayAttributeData>(AttributeDataObj.ToSharedRef(), &OutData, false, false); /* HogeAttributeSetに値を設定する。 */ DataPtr->SetBaseValue(OutData.GetBaseValue()); DataPtr->SetCurrentValue(OutData.GetCurrentValue()); /* 注意 : 更新イベントは呼ばれないので手動で呼び出す必要があるよ */ } } } }
これでJson
からAttributeSet
に値を読み込むことができるよ。