土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

#UEFN #Vesre #Verselang v29.00に追加された「永続化データ(Persistable Data)」機能について解説

 v29.00から永続化データ(Persistable Data)についての機能が追加されました。

forums.unrealengine.com

 永続化データは、セッションを超えてデータを保持(≒永続化)できる変数をverse上で管理できるようにする物です。

 永続化データを利用するにはそれなりに前提知識が必要なのですが、公式ドキュメントがまだ日本語化されてないので、要点を記事に起こしておきます。

公式ドキュメント

 以下が永続化データに関連する主なページです。詳細についてはこれらを参照してください。

  • VERSE の持続性:UEFN 体験に進捗の保存機能を実装しましょう!(日本語)
  • Using Persistable Data in Verse(英語)
  • Persistent Player Statistics(英語)
    • Persistent Player Statistics
    • 仕様を踏まえての実装。運用サイクルを踏まえた永続化クラスのコンストラクタ関数実装が参考になると思います。サンプルコードに若干バグがあるらしい*1ので注意。
  • Persistence Backward Compatibility Check(英語)
    • Publishing from the Creator Portal
    • 「クリエイターポータルからの公開」内の項目。後方互換性チェックについての解説が追記されているのだけど、日本語ページでは表示されていないので注意。

 上記以外にも組み込み型の解説ページに永続化データについての説明が追記されていますが、これらについても日本語ページでは表示されないので注意してください。

注意事項

 永続化データを使用する際の注意事項をまとめておきます。

  • まだ搭載されたばかりの機能で動作実績が極めて少ないです。この記事もドキュメントの記述を元にしており、実機では検証していません。意図しない挙動が起きた時、UEFN側のバグなのかどうか慎重に判断する必要があります。
  • v29.00以降の環境で新規作成したプロジェクトでしか機能しないようです*2。これがバグなのかどうかは現状不明です。
  • 永続化デバイス*3を多数使用している島では永続化データが正しく機能しないようです*4。これはv29.10で対応予定です。
  • 島を一度公開すると、以後後方互換性チェック(後述)が行われ、チェックが失敗すると島を更新できません

前提:weak_mapについて

 永続化データはweak_map型を通じて管理します。weak_mapはモジュール空間(≒グローバル空間)にvar変数として記述できる特殊なmap型です。詳しくは下記の記事をどうぞ。

[UEFN][verse]グローバル変数を使いたい[3]weak_mapとsession

[UEFN][verse]グローバル変数を使いたい[4]weak_mapはラウンドを超えて値を持てない事が確定

 以下、weak_mapについては常にモジュール空間で記述される物とします*5

余談:「weak_map」という名称について

 weak_mapは、mapと比べて機能が限定されているので「weakな(弱い)map」という名前を持ちます。しかしこのweak_mapは永続化以外に使い道が無く、明らかにネーミングに難があります(persistence_mapとでもすべき)。とはいえ、言語仕様上weak_mapmapのスーパータイプとして定義されているためそうもいかないのでしょう。

永続化データ機能(1):"weak_map(player, T)"

 以下、永続化データに関わる事項を3つに分けて解説します。

 これまでweak_mapのkeyに指定できるのはsession型のみでした。v29.00からここにplayer型が追加され、weak_map(player, T)が記述出来るようになりました。weak_map(player, T)は永続化され、同じプレイヤーが再び島に入った時、以前のデータが再現されます。

 Tには永続化可能な型*6のいずれかを指定します。ただし、後方互換性(後述)に対応するため、通常は永続化クラス(これも後述)を指定することになる筈です。

 weak_map(player, T)の使用には以下の3つの制限があります。

制限1:データはプレイヤー単位で永続化される

 weak_map(player, T)はプレイヤー単位でのデータ永続化を想定しています。そのため、島全体で共有する永続化データを持つことはできません。

 これはつまり、継続的なハイスコアを表示するリーダボードなどは実現出来ないということです(マジか!)。将来的にはweak_map(island, T)みたいな物が追加されるのかもしれませんが、今の所はありません。

制限2:記述出来るのはプロジェクト中に2個まで

 3個以上記述するとコンパイルエラーになります。たった2変数では足りない感じがしますが、後述する永続化クラスを使用して保存するデータの要素数を増やせます。

制限3:最低1個の値は永続化クラスでなければならない。

 後方互換性(後述)の対応のために、weak_map(island, T)のうち最低1つは値に永続化クラス(後述)の指定が強制されます(じゃないとコンパイルエラーになる筈)。

永続化データ機能(2):永続化クラス

 永続化クラス*7は、weak_map(player, T)に格納して永続化できる特殊なクラスです。

 永続化クラスは、以下の条件を満たした物になります。

  • persistable指定子を付与する
  • final指定子を付与する(永続化クラスはサブクラスを持てない)
  • uniqueを付与しない(一意クラスは永続化クラスになれない)
  • スーパークラスを持たない
  • パラメータを持たない
  • メンバは永続化可能な型のみ
  • メンバは定数のみ(var変数は宣言出来ない)

 要するに、<final><persistable>を付与した定数メンバのみを持つクラスが永続化クラスという事になります。上記の中で特に重要なのは「メンバは定数のみ」という点です。

 永続化データの運用は、まずゲーム開始時にweak_map(player, T)から永続化クラスのインスタンスを取得しその値を参照し、次にその値を更新してweak_mapに書き戻すというサイクルを繰り返します。

 この時、永続化クラスは定数メンバしか持てないので、値を更新して書き戻す為には、毎回クラスを生成し直す必要があるのです。

 サイクルを機能させるには多少トリッキーなコードを書く必要があり、通常はコピーコンストラクタ的に動作するコンストラクタ関数を複数使い分けることになります(公式ドキュメントも参照してください)。

 以下は永続化クラスの例です(公式ドキュメントのコードを一部修正)。

player_profile_data := class<final><persistable>:
    Version:int = 1
    XP:int = 0
    Rank:int = 0
    CompletedQuestCount:int = 0
    QuestHistory:[]string = array{}

 ちなみに、weak_map(player, T)一つあたり128KBまでデータを保持できるそうです。初代ドラゴンクエスト2個分なので十分な容量と言えるでしょう(そうか?)

永続化データ機能(3):後方互換性チェック

 これが一番厄介です。 一度公開された島は、以後weak_map(player, T)の値の型を変更できません(ただし、永続化クラスのメンバー追加を除く)

 島をパブリッシュする際にコードがチェックされ、不一致が検出されると更新が却下されます。これを「後方互換性チェック」と呼びます。

 一度公開された島でゲームがプレイされ、永続化データが保存され、しかし島の更新により永続化データのスキーマが壊れるという状況を避けるための措置と思われます。

 後から変更できないのは厳しいですが、クラスのメンバフィールド追加は出来るので、これで対応することになります(フィールドを減らす事は出来ないので注意)。

 ちなみに、永続化クラスのVersion:intは、その永続化クラスのスキーマバージョンを表す特殊な扱いとなり、永続化データが再構築される時、Versionのデフォルト値がカウントアップされてる時、保存されていたデータは無視して新規に構築されるようです。

おわりに

 ざっとこんな感じです。永続化データ周りのドキュメントが日本語化する前に記事をアップしたかったので、所々構成を詰めて切れていないかもなので随時修正します。また、情報が更新されたら追記します。

 データの持ち越しが出来ると、島での遊びの可能性が大きく広がると思います。是非、色々試してみてください。そしてバグ報告や機能要望を公式フォーラムに投げ、日本国内UEFNコミュニティの存在感を本国に示していきましょう!><

宣伝

Verse言語の言語仕様解説同人誌をPDFで頒布しています。よろしければどうぞ。

s-games.booth.pm


*1:https://x.com/kamyker/status/1770149659425554913?s=20

*2:https://forums.unrealengine.com/t/verse-persistence-causes-connection-error-for-old-projects/1754301

*3:永続化を有効にしているボタンデバイス等

*4:https://forums.unrealengine.com/t/persistence-related-inventory-connectivity-issues/1754799

*5:weak_mapはクラススやメソッドコープにも記述可能で、その際はkeyに任意の型を指定できますが、敢えて使う意味はありません

*6:https://dev.epicgames.com/documentation/en-us/uefn/using-persistable-data-in-verse#persistabletypesinverse

*7:公式ドキュメントで定義されている名称ではありません