「Rust」の「Bevy」でデスクトップゲーム「Color」を開発してみよう

第3回の今回は、プログラミング言語「Rust」の「Bevy」クレートを使ってデスクトップゲーム「Color」を作る解説をします。

大西 武 (オオニシ タケシ)

4月1日 6:30

はじめに

この連載では、さまざまなプログラミング言語やライブラリで同じゲームを開発することで、容易にそのプログラミング言語に入門します。

今回はプログラミング言語の「Rust」を使って、次のURLのようなColorゲームを作ります。こちらからアセットをダウンロードしておいてください。

Rustの文法については、「「TAURI」+「Rust」ではじめるデスクトップアプリ開発」の第3回と第4回を参考にしてください。

Colorゲームについて

Colorゲームの内容は第1回でも解説した通り、ゲームを開始したら、赤緑青の色ブロックをクリックします。すると背景色にその色が混ぜられるので、どんどんクリックして真っ白な背景にしていこうというゲームです。

プログラミング言語について

第3回の今回はプログラミング言語Rustで「Bevy」クレートを使ってデスクトップアプリを開発します。Rustは事前にメモリ管理を徹底していて堅牢にエラーの出にくいアプリを開発できるプログラミング言語です。しかもコンパイル言語なのでプログラミング言語「C/C++」のように処理が高速です。

BevyのECSについて

Bevyは2Dや3Dゲームなどのアプリを作るためのクレート(Rustの追加機能)で、次の表のように「ECS(Entity Component System)」という仕組みで成り立っています。具体的にはサンプルコードで理解してください。Bevyはプログラミング言語Rustを使いこなせなければ、ちょっと理解が難しいかもしれません。

【ECS(Entity Component Systemアーキテクチャー)

Entity一意のIDを持つ、Componentの実在物
ComponentEntityに付与される成分
System用意したデータの中から任意のデータを引数にして処理を行う関数

開発のために必要となる環境の準備

今回必要となるRustと「Visual Studio Code」を準備します。「Visual Studio Code」は第1回でも解説しました。

Rustの準備

Rustのセットアップについては、「「TAURI」+「Rust」ではじめるデスクトップアプリ開発」の第1回を参考にしてください。

Visual Studio Codeの準備

こちらのURLからVisual Studio Codeをダウンロードしてインストールしてください。セットアップの手順は省略します(第1回を参照)。

Rustプロジェクトを作成しよう

次のコマンドを実行して「colorBevy」プロジェクトを新規作成します。それから次の階層図のように画像の「images」フォルダは「assets」フォルダに入れます。今回もサウンドはなしです。

・Rustプロジェクト新規作成コマンド

$ cargo new colorBevy

・階層図

colorBevyフォルダ
┠assets(アセットはこのフォルダが参照される)
┃┗imagesフォルダ(画像フォルダ)
┃ ┠0.pngファイル(赤の色ブロック画像)
┃ ┠1.pngファイル(緑の色ブロック画像)
┃ ┠2.pngファイル(青の色ブロック画像)
┃ ┠GameOver.pngファイル(ゲームオーバー画像)
┃ ┠StageClear.pngファイル(ステージクリア画像)
┃ ┗Title.pngファイル(タイトル画像)
┠src(ソースフォルダ)
┃┗main.rsファイル(メインファイル)
┠target(ビルド生成フォルダ)
┠.gitignoreファイル
┠Cargo.lockファイル
┗Cargo.tomlファイル(設定ファイル)

次の設定ファイル「Cargo.toml」とサンプルコード「main.rs」ファイルをコーディングして、次のコマンドを実行してターミナルで起動すると図1のように真っ白なウィンドウが表示されます。

・実行コマンド

$ cargo run

・設定ファイル「Cargo.toml」

[package]
name = "colorBevy"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.18.0"
rand = "0.10.0"

・サンプルコード「main.rs」ファイル

// クレート
use bevy::{
 prelude::*,
 color::palettes::basic::*,
};
// メイン
fn main() {
 App::new()
   .add_plugins(DefaultPlugins.set(WindowPlugin {
     primary_window: Some(Window {
       title: "Color".into(),
       ..default()
     }),
     ..default()
   }))
   .insert_resource(ClearColor(Color::from(WHITE)))
   .add_systems(Startup, setup)
   .run();
}
// 初期化
fn setup(mut commands: Commands) {
 commands.spawn(Camera2d);
}

【サンプルコードの解説】
「use」でクレートを読み込みやすくします。
「main」関数では「App」構造体の「new」コンストラクタでプラグインを追加し、主ウィンドウ表示を作成します。あわせてクリアカラー(背景色)を白に設定するリソースを挿入し、Startup(起動時)に「setup」関数を読み込みます。
「setup」関数で2D用のカメラをスポーン(発生させる)します。

タイトル画面を表示する

スタートアップ時にタイトル画像を読み込んで、図2のようにスポーンして表示します。ここでは定数、ゲームステート、リソース、「ColorItem」構造体、「ColorType」列挙型、「TitleVisual」構造体は宣言だけしておいて後で使いますが、解説しやすいようにここでコーディングしておきます。

・サンプルコード「main.rs」ファイル

// クレート
use bevy::{
 prelude::*,
 window::PrimaryWindow,
 color::palettes::basic::*,
};
use rand::RngExt;
// 定数
const SPRITE_SIZE: f32 = 50.0;
const BOUNDS_MARGIN: f32 = 100.0;
// 状態定義
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
enum GameState {
 #[default]
 Title,
 MainLoop,
 GameOver,
 StageClear,
}
// リソース
#[derive(Resource)]
struct GameData {
 stage: usize,
 rgb_counts: Vec3,
 timer: Timer,
}
impl Default for GameData {
 fn default() -> Self {
   Self {
     stage: 2,
     rgb_counts: Vec3::ZERO,
     timer: Timer::from_seconds(1.5, TimerMode::Once),
   }
 }
}
// コンポーネント
#[derive(Component)]
struct ColorItem {
 color_type: ColorType,
 velocity: Vec2,
}
// 色タイプ
enum ColorType {
 Red,
 Green,
 Blue,
}
#[derive(Component)]
struct TitleVisual;
// メイン
fn main() {
 App::new()
   .add_plugins(DefaultPlugins.set(WindowPlugin {
     primary_window: Some(Window {
       title: "Color".into(),
       ..default()
     }),
     ..default()
   }))
   .init_state::<GameState>()
   .init_resource::<GameData>()
   .insert_resource(ClearColor(Color::from(WHITE)))
   .add_systems(Startup, setup)
   .run();
}
// 初期化
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
 commands.spawn(Camera2d);
 commands.spawn((
   Sprite::from_image(asset_server.load("images/Title.png")),
   Transform::from_xyz(0.0, 0.0, 10.0),
   TitleVisual,
 ));
}

【サンプルコードの解説】
「const」で定数を宣言します。
GameState列挙型でタイトル画面やメインループやゲームオーバーやステージクリアを宣言します。
リソース(資源)で面のstageプロパティ、背景色のrgb_countsプロパティ、タイマーのtimerプロパティをGameData構造体に宣言し、デフォルトの「default」メソッドを宣言します。
色ブロック1個ずつのColorItem構造体を宣言します。
Red、Green、Blueの列挙子を宣言します。
TitleVisual構造体を宣言します。
main関数のApp構造体のコンストラクタでGameStateを状態初期化し、GameDataをリソース初期化します。
setup関数で「images/Title.png」をアセットからスポーンします。add_systemsメソッドでECSのSystemに追加した関数には、用意した任意のComponentを引数として好きなだけ入れることができます。

【コラム】会計パズル

経理の計算をパズルのように学べる遊びを考えました。会社経営が学べる戦略マネジメントゲームみたいに遊べたらいいです。会計事務所の会計処理はAIが対処するようになるらしいですが、各クライアントの会社は今まで通り経理のおばちゃんレベルではないでしょうか?楽しく経理を学べるようにできたらいいです。実際加減の計算ぐらいなので謎解きみたいに小学生の知識でも解けるようにしたらどうでしょうか?

人気記事トップ10

人気記事ランキングをもっと見る

企画広告も役立つ情報バッチリ! Sponsored

ClickHouse Cloudが実現した広告運用SaaS「ATOM」のデータ基盤革新、パフォーマンスを向上しながら、運用負荷とコストを削減 11月11日 6:20 AIで開発が加速する時代、品質保証はどう変わる? Tricentisが示すAIエージェント活用の実像 11月9日 6:45 悪用までの猶予が「1分」に迫るフロンティアAI時代に開発と本番を貫く脆弱性管理が必要 10月29日 8:30 欧州CRA対応とSBOM運用。AI生成コードのOSS混入問題の対策とは ――スニペット単位で検出する「FossID」「Insignary Clarity」によるSBOM運用 9月16日 6:40 PostgreSQLからTiDBへ、稼働中SaaSで進める段階的DB移行の実践と再設計への展望 9月7日 6:30 機能安全開発におけるGoogleTestの活用を支援!「Parasoft C/C++test CT」で実現する効率化&AI連携 4月14日 6:30 【CNDW2025】Grafanaが明かす「オブザーバビリティの哲学」ー最小限の労力で実用的なインサイトを得るには 1月23日 6:30 【CNDW2025】プロダクト急増に備える基盤刷新 ーウェルスナビがECSからEKSへの移行で得た知見とは 1月15日 6:30 【CNDW2025】250環境を5人で運用、構築時間は30分に ーKINTOテクノロジーズが語るインフラ基盤組織の作り方 2025年12月18日 6:30 新たな自動化で熱視線! AIエージェントの「推論能力」を支える2つのコンポーネントとは? 2025年11月28日 6:30