かなり久しぶりのブログになります。たしか前回から2年以上経っていると思います。こうした記事を書くのは好きなのですが、その間は特に書く価値のあることがあまりありませんでした。
その一方で、私は「chicane engine」の改良を積極的に進めており、正確には約400件ものコミットを行いました! それを踏まえて、ここでは変更点をできるだけ皆さんに共有し、今後についても少し触れていこうと思います。
レイヤリング
私は(今でもそうですが)ゲーム開発初心者だったため、さまざまな媒体でコツやテクニックを探していました。そこで出会ったのが The Cherno のチャンネルです。ゲーム開発業界で経験豊富な開発者であり、この分野に関する多くの動画を公開しています。
その中で「Layer システム」に関する動画が特に印象に残り、試してみる価値があると思いました。このシステムは名前の通り、複数のレンダリング技法を積み重ねることを可能にし、エンジンの柔軟性を高めるものです。そこで、いくつかのコアレイヤーを作成しました。
- Sky Layer — 空を描画する
- Shadow Layer — シャドウマップを書き込む
- Level Layer — メッシュとその影を描画する
- UI Layer — UIを描画する
これらはコアレイヤーですが、このシステムでは任意の位置にモジュールを追加できるため、さらに柔軟性が高まっています。ただし、描画順序については開発者自身が意識して管理する必要があります。エンジン側が面倒を見てくれるわけではありません。
デカップリング
私は Vulkan ベースのゲームエンジンを作るところから開発を始めました。しかしシステムが進化するにつれて、コードベース全体に Vulkan 固有のコードが散らばっていることに疲れてしまいました。
Vulkan の理解が深まるにつれ、グラフィックスAPI関連のコードを分離し、エンジンを API 非依存にしようと決めました。
現時点では別APIの実装予定はありませんが、新しいグラフィックスインターフェースに対応できる余地は残しています。もちろん、APIごとに独自の特性があるため完全に抽象化することはできません。そのため、レイヤーシステムはグラフィックスAPIと密接に関係しています。
基礎の改善
さまざまなテスト環境を作っていく中で、Transform システムに問題があることが明らかになりました。一貫性がなく、大量のコードのコピペが必要だったのです。これは、最初にこのシステムを設計した頃の自分の未熟さを浮き彫りにしました。
Transform の基礎を正しく理解し、自分の実装の問題点を把握するまでに3日近くかかりました。行列計算や回転処理を修正すると、今度はシェーダーコードに別の問題が発生し、「以前は動いていたのになぜ壊れたのか」を理解しようとする長い試行錯誤に入りました。
Transform の修正後は、当時の Actor と Component のアタッチメントシステムを改善しました。さまざまな設計を研究した結果、多くの機能を Transformable ベースクラスへ移行することに決めました。
さらに、addTranslation、addRotation、addScale といった便利なメソッドや複数のオーバーロードも実装し、アプリケーション全体での重複コードを削減しました。
Grid システム
短いながらも Unreal Engine を使った経験から、私はそのUIシステムを非常に気に入りました。そこで Qt の HTML5 / CSS3 / Javascript ライクなUI実装を参考にしつつ、自分自身のシステム Grid を作成しました。名前はレースのスタートグリッドに由来しています。
このシステムは ImGUI ライブラリをベースにしてコンポーネントを描画します。
XML ファイルを利用し、HTML5 タグのような属性と値、さらに AngularJS を思わせるバックエンド値参照、そして CSS3 ライクなスタイルファイルを採用しています。
基本コンポーネントはいくつかあります。
- Button
- Container
- List
- Popup
- Progress Bar
- Text
- Text Input
これらは HTML5 における div や span のように、他のコンポーネントを構築するための基盤として設計されています。以下は実際の Grid ファイルの例です。
テンプレート例 .grid
<View id="home" style="Content/Sample/Views/Home.decal">
<Container id="victory" isVisible="{{ didPlayerWin }}">
<Text id="victoryText">YOU WIN!</Text>
</Container>
<Container id="frameTelemetry" direction="row">
<Text id="framePerSecond">{{ getFPS() }} FPS</Text>
<Text id="frameTimeText">{{ getFrametime() }} ms</Text>
</Container>
<Container id="crosshair">
<Container id="crosshairTop"></Container>
<Container id="crosshairBottom"></Container>
<Container id="crosshairLeft"></Container>
<Container id="crosshairRight"></Container>
</Container>
</View>
スタイル例 .decal
#victory {
position: absolute;
height: 30vh;
width: 100%;
margin-top: 10vh;
background-color: #333333;
}
#victoryText {
alignment: center;
}
#frameTelemetry {
position: absolute;
width: 175px;
height: 25px;
margin-left: auto;
}
#framePerSecond {
margin-left: 1vw;
alignment: start center;
color: #24f51d;
}
#frameTimeText {
alignment: end center;
color: #24f51d;
}
#crosshairTop {
position: absolute;
height: 10px;
width: 2px;
background-color: #FFFFFF;
margin: 50% 2px 28px 50%;
}
#crosshairBottom {
position: absolute;
height: 10px;
width: 2px;
background-color: #FFFFFF;
margin: 50% 2px 8px 50%;
}
#crosshairLeft {
position: absolute;
height: 2px;
width: 10px;
background-color: #FFFFFF;
margin: 50% 15px 15px 50%;
}
#crosshairRight {
position: absolute;
height: 2px;
width: 10px;
background-color: #FFFFFF;
margin: 50% -5px 15px 50%;
}
Box システム
私は以前から、「アセット管理がしっかりしたエンジンこそ良いエンジンだ」と考えていました。そこで独自のシステムを作ることにしました。仕組み自体はシンプルで、3Dモデル、音声、画像などのアセットファイルをテンプレート内に組み込むというものです。
レースをテーマにしていたこともあり、このシステムはレース中に車両が整備を受けるピットボックスにちなんで Box と名付けました。
エンジン更新の合間に簡易的なアセットビューアを開発していたのですが、その過程で Box システムには改善が必要だと気付きました。当時のアセットファイルテンプレートは次のようなものでした。
テンプレート
BOX;{FILE_VERSION};{ASSET_TYPE};{ASSET_ID};{ASSET_ENTRY_COUNT};[ENTRY{ENTRY_COUNTER};{ENTRY_IDENTIFIER}DATA]
ヘッダーセクション
BOX;{FILE_VERSION};{ASSET_TYPE};{ASSET_ID};{ASSET_ENTRY_COUNT};
データセクション
[ENTRY{ENTRY_COUNTER};{ENTRY_IDENTIFIER}DATA]
当時はこの実装をかなり賢いと思っていました。仕事で日常的に扱っているメインフレーム系データにも似ていて、固定テンプレートを持つ単純な文字列なので、簡単かつ高速にパースできたからです。
しかし問題は、この形式が変更に対してあまり拡張性を持たないことでした。新しいデータを追加するたびにヘッダー部分を変更する必要があり、さらに異なるアセットバージョンとの後方互換性を維持し続けなければなりませんでした。
そこで私はアセットシステム全体を書き直すことに決め、Grid モジュールと同様に XML を採用しました。XML はより柔軟で扱いやすいファイル形式だからです。
新しい実装では、生のファイル内容を直接保持する方式をやめ、Base64 を利用してデータを保存する方式へ変更しました。また、XML コンポーネント属性を活用して、異なるアセットごとのカスタムデータを設定しています。
以下は、メッシュとテクスチャを組み合わせた実際の Box メッシュファイルの例です。
例
<Asset version="1" id="Cube">
<Group id="Body">
<Model>Content/Sample/Models/Cube.bmdl</Model>
<Texture>Content/Sample/Textures/Gray.btex</Texture>
</Group>
</Asset>
この新しいテンプレートは、将来的な拡張性だけでなく、可読性の面でも大きな力を持っています。
今後について
私は最初からこのエンジンを個人プロジェクトとして扱ってきました。しかし時間が経つにつれて、このプロジェクトの維持に多くの労力を割くようになりました。
新しいことを学べるのは嬉しいのですが、現時点で自分のキャリアとして本当にやりたいことではありません。まだ学業面で追求したいこともありますし、ソフトウェア開発業界での経験も浅いため、大手ゲーム開発会社に参加できる段階でもありません。
そうしたことを踏まえ、このプロジェクトはいったん終了することに決めました。その代わり、人生の別の側面に時間を使いたいと思っています。
これはブログ記事自体との別れではありません。しかし、このプロジェクトとの別れです。時間が経つにつれ、ゲームエンジン開発には膨大な時間と知識が必要だということを痛感しました。そして今の自分には、それがまだ足りていません。
これからのブログ記事では、ソフトウェア開発だけでなく、さまざまなテーマを扱っていくつもりです。例えば、新しい言語を学ぶ過程や、将来の海外旅行についてなどです。
それでは、また。