Joël van der Werf
記事一覧

ゲームは自分のリポジトリへ:Hydroと最初のゲームを分離する

Hydro、エディタ、最初のゲームはすべて1つのリポジトリにありました。ゲームごとにリポジトリを分ける計画、その前にある16の結合、そして最初の5ステップ。

これまで、Hydro、Oxygenエディタ、そしてその上で作っているタクティカルRPG「Eonlore」は、すべて1つのリポジトリにありました。ゲームのプロジェクトはエディタのフォルダの中にあり、6,795ファイル、234MBです。しばらくは便利でしたが、ゲームなしではエンジンをクローンできず、2本目のゲームの置き場所がなく、どこまでがエンジンでどこからがゲームなのかが誰にもわかりません。

現在のプルリクエストの目標は、ゲームは自分のgitリポジトリに置くこと。エンジン、エディタ、プレイヤーは特定のゲームについて何も知らず、Oxygenはプロジェクトハブから任意のフォルダのプロジェクトを開いたり作ったりできます。

まず「完了」を定義する

コードを書く前に、計画には5つのステップからなる成功テストがあります。

  1. エンジンだけをクローンしてビルドする。Oxygenはexeから、どの作業ディレクトリからでも起動し、プロジェクトハブを表示する。
  2. 作業用フォルダでNew Projectを選ぶと、開けて、保存できて、再生できるプロジェクトができる。
  3. ゲームのリポジトリをエンジンの隣にクローンし、そのワークスペースをビルドする。そのOxygenでゲームが開き、「神社の森」が再生できる。
  4. File › Buildがゲーム自身のフォルダにパッケージを書き出し、エンジンのチェックアウトの名前を変えてもパッケージが動く。
  5. 2〜4の間、エンジンのリポジトリに一切変更が出ない。

16の結合

次に、エンジンがゲームを前提にしている箇所を、コードと照らし合わせてすべて洗い出しました。例えば:

  • エンジンのシェーダーは作業ディレクトリからの相対パスで読み込まれ、エディタのフォルダに置かれていました。Oxygenはそのフォルダから起動したときしか動きませんでした。
  • エディタの設定とドッキングのレイアウトは作業ディレクトリから探され、gitで管理されていました。
  • ゲームのコードは、すべてのOxygenとRuntimeのビルドにリンクされる固定のライブラリでした。
  • ビルドスクリプトの36か所のパスがすべてソリューション基準で、別のリポジトリからエンジンを取り込めませんでした。
  • そして1つの驚き:生成されたリフレクションのコードがオブジェクトファイルのセクション数の上限を超え、mainのDebug CIが赤になっていました。これがステップ0になりました。

重要な判断

ワークスペースとプロジェクトは別物

ワークスペースは1つのゲームのビルド環境:ソリューション、C++のコード、ビルドスクリプト。プロジェクトはエディタが開くコンテンツ:シーン、プレハブ、データ。各ゲームのリポジトリは、1つのプロジェクトを含むワークスペースです。ワークスペースは、premake、Python、バッチスクリプト、CIのすべてが読める1つのJSONファイルで記述します。

ゲームが必要とするエンジンのバージョン

エンジンはゲームの隣にチェックアウトし、engine.lockファイルにゲームが期待するHydroのコミットを書きます。エンジンとゲームは一緒に変更することが多いので、ローカルではずれていても警告だけ。CIはまさにそのコミットをチェックアウトするので、ずれていれば失敗します。

別のゲームのプロジェクトを開いても安全に

シリアライザーが知らない型をどう扱うかを確認しました。未知のスクリプトの値と未知のデータフィールドは保持して書き戻されますが、登録されていないコンポーネントは保存時に消えてしまいます。つまり、別のゲームのエディタでプロジェクトを編集すると、黙ってコンテンツが壊れます。

それで決まりました。すべてのプロジェクトは固定のゲームIDを持ち、別のゲームのプロジェクトを開くと読み取り専用で開く、正しいエディタを探す…、キャンセルの選択肢が出ます。未知のコンポーネントが往復で失われないようになるまで、「そのまま続ける」はありません。

リソースはexeと一緒に

すべてのビルドがエンジンのシェーダーをexeの隣にコピーするので、パッケージがソースのチェックアウトに依存することはありません。ソースのフォルダから読み込めるのはOxygenのDebugとReleaseビルドだけで、シェーダーのホットリロードは引き続き本物のファイルを編集します。

リハーサルしてから分割し、履歴を残す

ゲームはまずエンジンのリポジトリ内のProjects/Eonlore/に移り、そこで成功テストを実行します。その後で初めて、ゲームの素材がこれまでに置かれたすべてのパスの対応表に沿って、git filter-repoで切り出します。シーン、スクリプト、ツール、ドキュメントの4つのファイルで、git log --followとgit blameが分割前の履歴を示すまで、エンジンからは何も削除しません。

Git LFSは使わない、データで判断

リポジトリ全体のパックは245MBで、最大のファイルは5.8MB。LFSを使ってもクローンのサイズはほとんど変わらず、ツールが増えるだけなので、今はまだ価値がありません。代わりにBlenderのバックアップファイルを無視対象にし、切り出す履歴からも除きます。

現状

ステップ状況
S0 Debug CIの修正(/bigobj)完了
S1 エンジンのパス、シェーダーを各exeの隣へ完了
S2 エディタ設定をユーザーごとに、gitの外へ完了
S3 プロジェクトハブと最近のプロジェクト完了
S4 ゲームをワークスペースのパラメータに、他のゲームは読み取り専用完了
エンジン所有のテストベッド(Sample)次
S5 1つのリポジトリ内でのリハーサル予定
S6 履歴を残した分割予定

ステップごとに1コミットで、次に進む前にCIが緑である必要があります。CIで確認できないのはエディタのUIそのものなので、各ステップにはWindowsで実行する短い手動チェックリストもあります。

エンジンとは別のEonloreフォルダから「神社の森」を読み込んだOxygen
分割後:Oxygenは「神社の森」をエンジンの中ではなく、ゲーム自身のフォルダから読み込みます(コンソールを参照)。

ここから得たこと

  • 計画の前に成功テストを書く。「エンジンのチェックアウトの名前を変えても動く」は、隠れたパスの依存を一度にすべて見つけます。
  • 記憶ではなくコードを確認する。シリアライザーの発見が、判断を「警告」から「読み取り専用」に変えました。
  • 計測で決める。LFSは、数字が違うと言うまでは正しく聞こえました。
前の記事:AIエージェントが読めるエンジンにする

お仕事のご相談はお気軽にどうぞ

東京在住です。Unity・C++・ゲームエンジン関連のポジション、C#・.NETのソフトウェア開発のポジション、そしてAIエージェントを活用した開発体制づくりに関心があります。英語・オランダ語・日本語(勉強中)で対応できます。