読者です 読者をやめる 読者になる 読者になる

Scripts load multi-assets

 いま、ちょっとした実験コードを書きょーるんじゃけど……朝までに終わるんか分からん。眠い。

今回のネタは、ゲームプレイコードのオブジェクト……すなわちゲームオブジェクトが、

  • C/C++ で書かれたステートマシンで状態を渡り歩きながらロードと転送を処理していくんじゃのうて、
  • スクリプトのコルーチンで "式次第を消化するように" やれんかのーというもの。


 アピアランスとビヘイビアの両方に関わるゲームオブジェクトは、地勢、メッシュ、コリジョン、モーション、エフェクト、フィジックス等などの各専門モジュールのデータをハンドリングする。そのためにこれらの専門モジュールにアセットのロード(読み取りと構築)を依頼せんといけん。

そんだけじゃのうて、パラメータとかのゲームタイトル用の多種多様なデータを読み取って、データの生成やモジュールへの引き渡しを行わんにゃあいけん。

 それは開発当初では一種単品でも、終盤には複数複数になり、それらのデータの連携動作などを大量に書かんといけんことになっとるかもしれん。いずれにせよ、「これをこう表現したい」「こうしたい」というゲームデザイナーの要求は開発の進行とともに変化する。

 思うに、少々下手な設計でも、アセットのロードが終わってしまえば、それらを繋ぎあわせたり、仕様通りに振る舞うようにすることは難しゅうない。データをハンドリングするメンバ変数を拡張して、アップデートプロセスにコードを書き足しゃあええ。

 面倒なんはロード行程じゃと思う。じゃけん、ゲームプレイコード側に複雑なロードを書かんでもええように、ゲームタイトル(あるいはエンジンシステム)の中央でコントロールされるアセットロードフレームワークが必要になるじゃろう。こうすると、色んなゲームオブジェクトが、アセットロードフレームワークに依存性を持つことになる。けど、ゲームオブジェクトが、各専門的モジュールと直接喋る必要はのうなる。

 そのロードフレームワークが、

  • 複雑で多岐にわたるアセットのロードを確実に遂行する
  • ロード中に、あるいはロード後に判明した追加リクエストを受け入れる
  • あらゆるモジュールで管理されるキャッシュを検索する
  • サードパーティライブラリによって極端に違うロード行程を隠蔽する
  • すべてのアセットがロードを終えれば通知する
  • あるいは、VRAM等に転送が可能になったデータの単位から、(依頼主がそれを求めるなら)依頼主に通知を行う。*1


 などの能力を持てば、専門モジュールとのネゴシエーションはロードフレームワークが持ち、そのフレームワークにクエリを投げて、すべてが終わるまで待つ、という部分をゲームプレーコードの初期化行程に書きゃあええ。

 これをすべて C/C++ で実装した場合、必要なアセットのロードクエリはバイトデータとして表現されると思う。拡張が必要になれば、コントローラに新たな処理を追加して、クエリのフォーマットに新しい制御コードを追加することになるじゃろう。

そういった拡張は頻繁に発生する。加えて、ひとつのアセットをロードしたことで、そのロードしたアセットの中に含まれているパラメータから別のアセットをロードせんにゃあいけんようになることもある。たとえばメッシュデータにベイクされた情報から必要な別資産をロードする場合などは、汎用であるアセットロードフレームワークには判定できん。一度その専門モジュールにメッシュデータを送りつけることで、初めて追加クエリが判明する。

 こういった拡張は、モジュールの担当者とゲームデザイナーの間で必要に応じてスピーディに行われていくし、実験の結果、断念されることも結構あるけー、とにかく変化に強くなきゃあいけん。

 また、必要なアセットはデータに書き込まれてるだけじゃのうて、カスタムコードから要求される場合もあるじゃろう。たとえば、ある種のゲームオブジェクトはコリジョンデータを使う予定がなかったが、ある非常に特別な役割を担うオブジェクトを表現するためのサブクラスが、その仕様の実現上、特別にコリジョンデータを必要としたとする。

 その1例のために、全データ構造に手を入れるかどうか。

 そういう考え方もあると思うが、カスタムサブクラスから、彼のみが知るアセットの追加ロードをリクエストできて、それをそのアセットの特別なメンバ変数にぶらさげることができれば「一品モノ」を作るフットワークは軽くなる。

 データからロードの計画を立てるということは、計画外のものが出たときに、データの作りから拡張しなければならないということ。ユニークな「一品モノ」が増えれば、 C/C++ に特例のアセットロードというグルーコードが増えてしまう。

長々とわりいの

 やっと話は冒頭へ。そこで、アセットロードフレームワークのコントローラをスクリプトで記述し、必要があればリクエストの依頼主が制御スクリプトを送り込むことができるメカニクスをちょっと思いついたんよ。単純にリクエストをかける場合は、スクリプトで制御されている部分は依頼主からは隠蔽されとるけー、その制御がスクリプトドリブンであることを意識する必要はないじゃろ。で、そのスクリプトの品質を管理できれば、データにロード計画のヒントとなる制御コードをどんどん拡張していくより、シンプルな構造だと思う。グルーコードはスクリプトコードにしとけ、という。

 もう一点、スクリプトがロードの処理に向いているのではないかと考えたんは、上のような状態におけるロードの処理はパイプライン処理であり、ステートと相性がよくないと思ったけーなんよ。たとえば、絵を描くときに、まずパースをとり、アタリをとって下書きを作り、清書して色を塗るように、ロードは順番と終わりのあるパイプライン処理じゃけー。

 決められた種類の決められた量の指定データを順序ロードする。それには追加や順番も存在するが、基本的にはスタートから始まり、有限のループを経て、処理の終了にいきつく。式次第を順序よく消化していく要領で、バックステップするこたぁない。じゃけん、コルーチンとぶち相性ええし、コルーチンは非同期ロード待ちの一手段であるポーリングを簡単に実現することができる。

 超汎用的なフレームワークはデータドリブンでも作ることができるけど、各モジュールと "喋る" 部分をスクリプトエンジンとの会話部分に限定すれば、通信部分は限定的でシンプルになると思うたんよ。それに汎用スクリプトなら、データドリブンなロードも、カスタムロードも両方可能になると思うた。

 しかし正直 "思いつき" でもあるので、実験している次第。でも、スクリプトにデータ流し込みの役割をやらせるという話は、数年前からあったと思う。

*1:このあたりをスクリプトならむちゃくちゃ柔軟に書ける