OrionEngine開発記録 Vol.1 (RustでBuildPipeline開発)
Rustでゲームエンジンのビルドパイプラインを自作してみた記録です。
OrionEngineとは何か
OrionEngineは、現在開発中のDirectX12ベースのゲームエンジンです。
本シリーズでは、このOrionEngineの開発過程を記録していきます。
将来的には教育用途での利用も想定しており、そのために自身の学校での試験的な授業導入も検討しています。
今回はその第1回として、RustでのBuildPipeline開発についてまとめます。
Rustでビルドパイプライン構築
なぜBuildPipelineを自作するのか
ゲームエンジンにおいて、ゲームを開発し、それをリリースするためのビルド機能は重要な要素です。
C++の std::filesystem でも実装は可能ですが、Rustをビルドシステムに用いる理由は主に以下です。
- メモリ安全性があり、大規模なビルド処理でも壊れにくい
- ディレクトリ処理が直感的に書ける
- C++との親和性が高い
実装(基本構造)
今回のBuildPipelineは、以下のようなステップで構成されています。
- 出力ディレクトリの作成
- キャッシュの確認
- 実行ファイルのコピー
- アセットのコピー
- 設定ファイルの生成
- エンジンアセットのコピー
これらを順番に実行するシンプルな構造になっています。
ビルドコンテキスト
まず、各処理で使用するパスや設定をまとめた BuildContext を定義しています。
struct BuildContext {
project_root: PathBuf,
editor_root: PathBuf,
project_name: String,
config: String,
force_rebuild: bool,
}
各ビルドステップはこのコンテキストを受け取ることで、
グローバル状態を持たずに処理できるようにしています。
ステップ実行の共通化
ビルド処理は複数のステップに分かれるため、
ログ出力とエラーハンドリングをまとめるためにマクロを用意しています。
macro_rules! step {
($pct:expr, $msg:expr, $fn:expr) => {{
log_progress($pct, $msg);
$fn.map_err(|e: String| format!("{}: {}", $msg, e))?;
}};
}
これにより、ビルド処理を以下のようにシンプルに記述できます。
step!(65, "Copying assets...", copy_assets(&ctx));
アセットコピー処理
実際の処理の一例として、アセットコピーを示します。
fn copy_assets(ctx: &BuildContext) -> Result<(), String> {
let src = ctx.project_root.join("Assets");
let dst = ctx.dist_output_dir().join("Assets");
log_line("DEBUG", &format!("Assets src: {}", src.display()));
log_line("DEBUG", &format!("Assets dst: {}", dst.display()));
if !src.exists() {
log_line(
"WARN",
&format!("Warning: Asset folder not found: {}", src.display()),
);
return Ok(()); // Assets がなくてもビルド続行
}
if dst.exists() {
fs::remove_dir_all(&dst).map_err(|e| format!("Failed to clean asset output dir: {}", e))?;
log_line("DEBUG", &format!("Removed stale assets: {}", dst.display()));
}
copy_directory(&src, &dst)
}
この関数では、プロジェクトのAssetsディレクトリを出力先へ再帰的にコピーしています。
Rustの std::fs を使うことで、比較的シンプルにディレクトリ操作を書くことができます。
今回のポイント
- 処理を小さなステップに分割する
- コンテキストに依存を集約する
- ログとエラーハンドリングを統一する
まとめ
今回はひとまずBuildPipelineの基盤が動くところまで実装できました。
リポジトリ: