てんちょーの技術日誌

自分がつまづいたこととかメモ

【UE4】SaveとLoad時のUserIndexについて

はじめに

時々SaveとLoadに苦戦するのでよくお世話になっているほげたつさんのサイトです。

hogetatu.hatenablog.com

ここには

セーブデータにはスロット名とユーザーインデックスが指定できます。 スロット名はセーブデータの名前、ユーザーインデックスはその中でのIDみたいなものですね。 ドラクエ冒険の書1とか冒険の書2とかそんなイメージです。

とあります。

もちろんこちらにも。

docs.unrealengine.com

For some platforms, master user index to identify the user doing the saving.

ただ実際に実装してみると、なにやら動作がおかしいので調べてみました。

ソースコード

ということで、Engineのソースコードを追います。UE4.13です。 ctrl+FとかでUserIndexをハイライトしておくと、見やすいかもしれません。

Runtime/Engine/Private/GameplayStatics.cpp

USaveGame* UGameplayStatics::LoadGameFromSlot(const FString& SlotName, const int32 UserIndex)
{
    USaveGame* OutSaveGameObject = NULL;

    ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem();
    // If we have a save system and a valid name..
    if(SaveSystem && (SlotName.Len() > 0))
    {
        // Load raw data from slot
        TArray<uint8> ObjectBytes;
        bool bSuccess = SaveSystem->LoadGame(false, *SlotName, UserIndex, ObjectBytes);
        if(bSuccess)
        {
                   (省略)
bool UGameplayStatics::SaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex)
{
    ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem();
    // If we have a system and an object to save and a save name...
    if(SaveSystem && SaveGameObject && (SlotName.Len() > 0))
    {
        (省略)
        // Stuff that data into the save system with the desired file name
        return SaveSystem->SaveGame(false, *SlotName, UserIndex, ObjectBytes);
    }
    return false;
}

とSaveもLoadもUserIndexに関わる処理はSaveGame、LoadGameを呼び出しているのでそちらへ。

Runtime/Engine/Public/SaveGameSystem.h

virtual bool SaveGame(bool bAttemptToUseUI, const TCHAR* Name, const int32 UserIndex, const TArray<uint8>& Data) override
    {
#if PLATFORM_HTML5_BROWSER
        return UE_SaveGame(TCHAR_TO_ANSI(Name),UserIndex,(char*)Data.GetData(),Data.Num());
#elif PLATFORM_HTML5_WIN32
        FILE *fp;
        fp=fopen("c:\\test.sav", "wb");
        fwrite((char*)Data.GetData(), sizeof(char), Data.Num(), fp);
        fclose(fp);
        return true;
#else
        return FFileHelper::SaveArrayToFile(Data, *GetSaveGamePath(Name));
#endif
    }
 virtual bool LoadGame(bool bAttemptToUseUI, const TCHAR* Name, const int32 UserIndex, TArray<uint8>& Data) override
    {
#if PLATFORM_HTML5_BROWSER
        char*  OutData;
        int        Size;
        bool Result = UE_LoadGame(TCHAR_TO_ANSI(Name),UserIndex,&OutData,&Size);
        if (!Result)
            return false; 
        Data.Append((uint8*)OutData,Size);
        ::free (OutData);
        return true;
#elif PLATFORM_HTML5_WIN32
        FILE *fp;
        fp=fopen("c:\\test.sav","rb");
        if (!fp)
            return false;
            // obtain file size:
        fseek (fp, 0 , SEEK_END);
        int size = ftell (fp);
        fseek (fp, 0 , SEEK_SET);
        Data.AddUninitialized(size);
        int result = fread (Data.GetData(),1,size,fp);
        fclose(fp);
        return true;
#else
        return FFileHelper::LoadFileToArray(Data, *GetSaveGamePath(Name));
#endif
    }

えーっとPlatformがHTML5のブラウザだけUserIndexを使っているように思います。

そしてこのUE_SaveGameとUE_LoadGameはこちらに。

Runtime/HTML5/HTML5JS/Private/HTML5JavaScriptFx.js

UE_SaveGame: function (name, userIndex, indata, insize) {
    // user index is not used.
    var _name = Pointer_stringify(name);
    var gamedata = Module.HEAPU8.subarray(indata, indata + insize);
    // local storage only takes strings, we need to convert string to base64 before storing.
    var b64encoded = base64EncArr(gamedata);
    $.jStorage.set(_name, b64encoded);
    return true;
  },

  UE_LoadGame: function (name, userIndex, outdataptr, outsizeptr) {
    var _name = Pointer_stringify(name);
    // local storage only takes strings, we need to convert string to base64 before storing.
    var b64encoded = $.jStorage.get(_name);
    if (b64encoded === null)
      return false;
    var decodedArray = base64DecToArr(b64encoded);
    // copy back the decoded array.
    var outdata = Module._malloc(decodedArray.length);
    // view the allocated data as a HEAP8.
    var dest = Module.HEAPU8.subarray(outdata, outdata + decodedArray.length);
    // copy back.
    for (var i = 0; i < decodedArray.length; ++i) {
      dest[i] = decodedArray[i];
    }
    Module.HEAP32[outsizeptr >> 2] = decodedArray.length;
    Module.HEAP32[outdataptr >> 2] = outdata;
    return true;
  },

いやあのこれ、UserIndex出番なくないですか…(引数として呼ばれているだけで未使用)

まとめ

現状だとUserIndexは出番がないようです。ちゃんと(?)設定していないはずのUserIndexでLoadしてもエラーが出ませんでした。

Forum等も漁りましたが、良さげな解答が見つからなかったので追ってみました。

どこか間違ってたり、新たに何か分かった時にはぜひ@shop_0761までお知らせください(更新します)