ゲームは自分のリポジトリへ:Hydroと最初のゲームを分離する
Hydro、エディタ、最初のゲームはすべて1つのリポジトリにありました。ゲームごとにリポジトリを分ける計画、その前にある16の結合、そして最初の5ステップ。
これまで、Hydro、Oxygenエディタ、そしてその上で作っているタクティカルRPG「Eonlore」は、すべて1つのリポジトリにありました。ゲームのプロジェクトはエディタのフォルダの中にあり、6,795ファイル、234MBです。しばらくは便利でしたが、ゲームなしではエンジンをクローンできず、2本目のゲームの置き場所がなく、どこまでがエンジンでどこからがゲームなのかが誰にもわかりません。
現在のプルリクエストの目標は、ゲームは自分のgitリポジトリに置くこと。エンジン、エディタ、プレイヤーは特定のゲームについて何も知らず、Oxygenはプロジェクトハブから任意のフォルダのプロジェクトを開いたり作ったりできます。
まず「完了」を定義する
コードを書く前に、計画には5つのステップからなる成功テストがあります。
- エンジンだけをクローンしてビルドする。Oxygenはexeから、どの作業ディレクトリからでも起動し、プロジェクトハブを表示する。
- 作業用フォルダでNew Projectを選ぶと、開けて、保存できて、再生できるプロジェクトができる。
- ゲームのリポジトリをエンジンの隣にクローンし、そのワークスペースをビルドする。そのOxygenでゲームが開き、「神社の森」が再生できる。
- File › Buildがゲーム自身のフォルダにパッケージを書き出し、エンジンのチェックアウトの名前を変えてもパッケージが動く。
- 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で実行する短い手動チェックリストもあります。

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