RustとBevyで始める!簡単な2Dゲーム開発チュートリアル

Rustは、高速で安全性の高いシステムプログラミング言語として注目を集めています。その中でも、Bevyはモダンなゲームエンジンとして、Rust初心者から経験者まで幅広い層に支持されています。本記事では、Bevyを用いてシンプルな2Dゲームをゼロから構築する方法を詳しく解説します。ゲーム開発の基礎を学びつつ、RustとBevyの組み合わせがどのように効率的な開発を可能にするのかを体験できる内容となっています。ぜひ、このチュートリアルを通じて、Rustを活用したゲーム開発の魅力を発見してください。

目次

Bevyとは何か


BevyはRustで開発されたオープンソースのゲームエンジンで、モジュール性と直感的な設計が特徴です。エンティティコンポーネントシステム(ECS)を基盤としており、ゲーム開発の効率性を大幅に向上させる設計がされています。

Bevyの特徴

  • エンティティコンポーネントシステム(ECS): 複雑なゲームロジックを簡潔に管理するための柔軟な設計。
  • クロスプラットフォーム対応: Windows、MacOS、Linuxをはじめ、WebAssemblyやモバイル環境にも対応可能。
  • 拡張性: モジュールごとに機能を追加・削除できるため、プロジェクトのスケールに応じた柔軟な開発が可能。
  • Rustの利点: メモリ安全性や高速性といったRustの強みを活かした設計。

なぜBevyを選ぶべきか


Bevyは、Rustエコシステムを最大限に活用しながら、モダンで使いやすいAPIを提供します。複雑な依存関係を最小限に抑え、初心者にも扱いやすい設計が魅力です。また、活発なコミュニティと豊富なドキュメントがサポートされており、開発者がスムーズにプロジェクトを進められる環境が整っています。

これらの理由から、BevyはRustでゲーム開発を始める際の理想的な選択肢となります。

開発環境の準備

Bevyを使って2Dゲームを開発するためには、Rustの環境を整えることが第一歩です。ここでは、RustのインストールからBevyのセットアップまでの手順を解説します。

1. Rustのインストール


Rustをまだインストールしていない場合、公式のRustインストーラ「rustup」を使用して簡単にセットアップできます。

  1. ターミナルを開き、以下のコマンドを実行します:
   curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  1. 指示に従ってインストールを完了させます。
  2. インストール後、Rustのバージョンを確認して正しくインストールされていることを確認します:
   rustc --version

2. Bevyのプロジェクトセットアップ

  1. 新しいRustプロジェクトを作成します:
   cargo new bevy_game --bin
   cd bevy_game
  1. プロジェクトディレクトリ内のCargo.tomlファイルを編集し、Bevyを依存関係として追加します:
   [dependencies]
   bevy = "0.11"  # 最新バージョンを確認してください
  1. 依存関係をインストールします:
   cargo build

3. 必要なツールの確認

  • コードエディタ: Visual Studio CodeやJetBrains CLionがおすすめです。Rust専用のプラグインをインストールして開発効率を高めましょう。
  • Debuggingツール: Bevyの実行中の問題を見つけるためにデバッガの導入を検討してください(例えばlldbgdb)。

4. 最初のプログラムを実行する


Bevyが正しくインストールされていることを確認するため、プロジェクトをビルドして実行します:

cargo run


プロジェクトが正しく動作すれば、次のステップとして2Dゲーム開発を開始できます。

これで開発環境の準備は完了です!

初めてのプロジェクト作成

ここでは、Bevyを使用して基本的なプロジェクトを作成し、ウィンドウを表示するところまでを実現します。この段階で、Bevyが正しく動作しているかを確認できます。

1. プロジェクトの雛形作成


前のステップで作成したプロジェクトを基に、Bevyを用いたプログラムを記述します。src/main.rsを開き、以下のコードを記述してください:

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .run();
}

2. コードの説明

  • App::new()
    Bevyのアプリケーションを初期化します。ゲームやアプリケーションのエントリポイントです。
  • add_plugins(DefaultPlugins)
    Bevyが提供する基本的なプラグイン(ウィンドウ管理、レンダリング、入力処理など)を追加します。
  • run()
    アプリケーションを開始します。

3. プログラムの実行


ターミナルで以下のコマンドを実行して、プログラムをビルドし、実行します:

cargo run


成功すれば、シンプルなウィンドウが表示されます。

4. よくあるエラーとその対処方法

  • エラー: “no suitable graphics device found”
    このエラーは、環境がBevyの必要とするVulkanやMetalなどのグラフィックAPIに対応していない場合に発生します。ドライバの更新や適切な環境の設定が必要です。
  • 依存関係のビルドエラー
    cargo updateで最新の依存関係を取得し、再試行してください。

5. 確認事項

  • ウィンドウが正常に表示されること。
  • ビルドと実行にエラーが発生しないこと。

これで、Bevyを用いた初めてのプロジェクトが完成です。この段階で、開発環境が正しく整っていることが確認できます。次のステップでは、さらにゲーム要素を追加していきます。

ゲームの基本構造

Bevyのゲーム開発では、エンティティコンポーネントシステム(ECS)を活用した設計が鍵となります。このセクションでは、Bevyを用いたゲームの基本構造を理解し、簡単なエンティティとシステムを作成します。

1. ECSとは何か


ECSは、エンティティ、コンポーネント、システムという3つの要素から成り立つデザインパターンです。

  • エンティティ: ゲーム内のあらゆるオブジェクトを表す一意の識別子。
  • コンポーネント: エンティティに関連付けられるデータや属性(例: 位置、速度)。
  • システム: コンポーネントを処理し、ゲームのロジックを実現する関数やモジュール。

このアプローチにより、ゲームロジックを柔軟かつモジュール化して設計できます。

2. 基本的なエンティティの作成


以下は、Bevyを使って簡単なエンティティ(例: 四角形)を作成するコード例です:

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup)
        .run();
}

fn setup(mut commands: Commands) {
    // カメラを追加
    commands.spawn(Camera2dBundle::default());

    // 四角形エンティティを追加
    commands.spawn(SpriteBundle {
        transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
        sprite: Sprite {
            color: Color::rgb(0.5, 0.5, 1.0),
            custom_size: Some(Vec2::new(50.0, 50.0)),
            ..Default::default()
        },
        ..Default::default()
    });
}

3. コードの説明

  • commands.spawn()
    エンティティを作成し、関連するコンポーネントを追加します。
  • Camera2dBundle
    2Dビューのレンダリングを可能にするカメラ。
  • SpriteBundle
    四角形のスプライトを表すデータ構造。SpriteTransformコンポーネントを含みます。
  • Transform::from_translation()
    エンティティの位置を設定します。

4. システムの追加


Bevyでは、システムは関数として定義され、ゲームのロジックを実行します。以下の例では、スプライトを上下に動かします:

fn move_sprite(mut query: Query<&mut Transform>) {
    for mut transform in query.iter_mut() {
        transform.translation.y += 1.0; // 上方向に移動
    }
}


このシステムをアプリケーションに追加するには、次のようにします:

App::new()
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .add_system(move_sprite)
    .run();

5. 実行結果の確認


プログラムを実行すると、四角形が画面上で上下に移動する様子を確認できます。

これで、Bevyの基本構造であるエンティティ、コンポーネント、システムを用いたゲームの仕組みを構築できました。この知識を基に、さらに複雑なゲームロジックを作成していきます。

スプライトとアニメーションの追加

ゲームに視覚的な要素を加えるために、スプライト(画像)を表示し、アニメーションを適用します。このセクションでは、キャラクターや背景をスプライトとして設定し、簡単なアニメーションを実装する方法を説明します。

1. スプライトの追加


スプライトを表示するには、画像をプロジェクト内に保存し、それをスプライトコンポーネントに設定します。

1.1. 画像の準備


プロジェクトのルートにassetsフォルダを作成し、画像ファイル(例: character.png)を保存します。

1.2. コードでスプライトを設定


以下のコードをsrc/main.rsに記述します:

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // カメラを追加
    commands.spawn(Camera2dBundle::default());

    // スプライトエンティティを追加
    commands.spawn(SpriteBundle {
        texture: asset_server.load("character.png"), // 画像ファイルをロード
        transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
        ..Default::default()
    });
}

2. アニメーションの追加


スプライトにアニメーションを適用するため、タイマーを使用して画像を変更します。

2.1. アニメーション用コンポーネントを定義


アニメーションのフレームを管理するためのカスタムコンポーネントを作成します:

struct Animation {
    timer: Timer,
}

2.2. アニメーションシステムの実装


画像を切り替えるシステムを作成します:

fn animate_sprite(
    time: Res<Time>,
    mut query: Query<(&mut Animation, &mut Handle<Image>)>,
    asset_server: Res<AssetServer>,
) {
    for (mut animation, mut texture) in query.iter_mut() {
        animation.timer.tick(time.delta());
        if animation.timer.finished() {
            *texture = asset_server.load("character_frame2.png"); // 次のフレームをロード
        }
    }
}

2.3. アプリケーションにアニメーションを追加


アプリケーションのセットアップにアニメーションを追加します:

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(SpriteBundle {
        texture: asset_server.load("character_frame1.png"),
        ..Default::default()
    })
    .insert(Animation {
        timer: Timer::from_seconds(0.1, TimerMode::Repeating),
    });
}


アニメーションシステムをアプリケーションに登録します:

App::new()
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .add_system(animate_sprite)
    .run();

3. 実行結果


実行すると、スプライトがアニメーションし、キャラクターが動いているように見えます。アニメーションの速度やフレームを調整して、より自然な動きを実現してください。

これで、スプライトとアニメーションをゲームに追加する方法を学べました。次のステップでは、入力処理を実装してキャラクターを操作できるようにします。

入力処理の実装

ゲームにおいて、プレイヤーがキャラクターやオブジェクトを操作する機能は欠かせません。このセクションでは、キーボードやマウス入力を処理し、ゲームのインタラクティビティを実現します。

1. 基本的な入力処理


まずは、キーボード入力を取得し、キャラクターを移動させる方法を解説します。

1.1. コードの実装


以下のコードを追加します:

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup)
        .add_system(player_movement)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // カメラを追加
    commands.spawn(Camera2dBundle::default());

    // キャラクタースプライトを追加
    commands.spawn((
        SpriteBundle {
            texture: asset_server.load("character.png"),
            transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
            ..Default::default()
        },
        Player, // カスタムコンポーネント
    ));
}

// プレイヤーを識別するためのカスタムコンポーネント
struct Player;

// プレイヤーの移動を制御するシステム
fn player_movement(
    keyboard_input: Res<Input<KeyCode>>,
    mut query: Query<(&Player, &mut Transform)>,
) {
    for (_, mut transform) in query.iter_mut() {
        let mut direction = Vec3::ZERO;

        if keyboard_input.pressed(KeyCode::W) {
            direction.y += 1.0; // 上
        }
        if keyboard_input.pressed(KeyCode::S) {
            direction.y -= 1.0; // 下
        }
        if keyboard_input.pressed(KeyCode::A) {
            direction.x -= 1.0; // 左
        }
        if keyboard_input.pressed(KeyCode::D) {
            direction.x += 1.0; // 右
        }

        transform.translation += direction * 2.0; // 移動速度を調整
    }
}

1.2. コードの説明

  • Input<KeyCode>: キーボードの入力を検知するリソース。特定のキーが押されているかどうかを確認します。
  • Query: プレイヤーコンポーネントを持つエンティティを取得し、その位置を更新します。
  • Vec3: キャラクターの移動方向を3次元ベクトルで表現します。

2. マウス入力の処理


キーボードだけでなく、マウスのクリックやカーソル位置を使った操作も実装できます。

2.1. マウスクリックの検知


以下のコードを追加します:

fn mouse_click(
    mouse_input: Res<Input<MouseButton>>,
    windows: Res<Windows>,
    mut query: Query<&mut Transform, With<Player>>,
) {
    if mouse_input.just_pressed(MouseButton::Left) {
        if let Some(window) = windows.get_primary() {
            if let Some(cursor_position) = window.cursor_position() {
                for mut transform in query.iter_mut() {
                    transform.translation = Vec3::new(
                        cursor_position.x - window.width() / 2.0,
                        cursor_position.y - window.height() / 2.0,
                        0.0,
                    );
                }
            }
        }
    }
}

2.2. システムの登録


上記のシステムをアプリケーションに登録します:

App::new()
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .add_system(player_movement)
    .add_system(mouse_click)
    .run();

3. 実行結果

  • キーボード操作でキャラクターが移動します(W/A/S/D)。
  • マウスクリックでキャラクターがカーソル位置に移動します。

4. 入力処理の応用


入力処理を組み合わせて、攻撃やアイテム使用、ゲームメニューの操作など多彩な機能を実装できます。

これで、入力処理を実装する方法を学びました。次のセクションでは、簡単なゲームロジックを加えてゲーム性を高めていきます。

簡単なゲームロジックの実装

ここでは、スコア計算や敵キャラクターの動きを追加し、ゲームらしい要素を取り入れます。プレイヤーと敵キャラクターの衝突判定を実装し、ゲームロジックを構築します。

1. スコアシステムの追加

1.1. スコアの管理


スコアを管理するために、リソースを定義します:

struct Score(u32);

1.2. スコアの初期化


スコアをアプリケーションに追加します:

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(Score(0)) // スコアを初期化
        .add_startup_system(setup)
        .add_system(player_movement)
        .add_system(enemy_movement)
        .add_system(check_collision)
        .add_system(update_score)
        .run();
}

1.3. スコアの更新


スコアを更新するシステムを追加します:

fn update_score(score: Res<Score>) {
    println!("Score: {}", score.0);
}

2. 敵キャラクターの追加

2.1. 敵の生成


敵キャラクターをスポーンするコードを記述します:

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2dBundle::default());

    // プレイヤー
    commands.spawn((
        SpriteBundle {
            texture: asset_server.load("character.png"),
            transform: Transform::from_translation(Vec3::new(0.0, -200.0, 0.0)),
            ..Default::default()
        },
        Player,
    ));

    // 敵キャラクター
    commands.spawn((
        SpriteBundle {
            texture: asset_server.load("enemy.png"),
            transform: Transform::from_translation(Vec3::new(0.0, 200.0, 0.0)),
            ..Default::default()
        },
        Enemy,
    ));
}

2.2. 敵の移動


敵が画面を上下に移動するシステムを追加します:

fn enemy_movement(mut query: Query<(&Enemy, &mut Transform)>, time: Res<Time>) {
    for (_, mut transform) in query.iter_mut() {
        transform.translation.y -= time.delta_seconds() * 100.0;
        if transform.translation.y < -300.0 {
            transform.translation.y = 300.0; // リセット
        }
    }
}

3. 衝突判定の実装

3.1. 衝突判定システム


プレイヤーと敵キャラクターの衝突を検知します:

fn check_collision(
    mut commands: Commands,
    mut score: ResMut<Score>,
    player_query: Query<&Transform, With<Player>>,
    enemy_query: Query<(Entity, &Transform), With<Enemy>>,
) {
    if let Ok(player_transform) = player_query.get_single() {
        for (enemy_entity, enemy_transform) in enemy_query.iter() {
            let distance = player_transform
                .translation
                .distance(enemy_transform.translation);

            if distance < 30.0 {
                println!("Collision!");
                commands.entity(enemy_entity).despawn(); // 敵を削除
                score.0 += 1; // スコアを加算
            }
        }
    }
}

4. 実行結果

  • 敵キャラクターが画面上を移動します。
  • プレイヤーが敵に接触すると敵が消え、スコアが加算されます。

5. 応用例

  • 敵を一定時間ごとにスポーンさせるロジックを追加する。
  • スコアを画面に表示するUIを実装する。
  • 敵キャラクターの動きをランダム化して難易度を調整する。

これで、ゲームロジックを簡単に実装する方法を学べました。次のステップでは、ゲームのデバッグやトラブルシューティングについて学びます。

デバッグとトラブルシューティング

ゲーム開発では、動作確認と問題の解決が不可欠です。このセクションでは、Bevyを使ったプロジェクトでよく発生する問題と、そのトラブルシューティング方法を紹介します。また、効率的なデバッグの手法についても解説します。

1. よくあるエラーと解決方法

1.1. コンパイルエラー

  • 原因: Bevyの依存関係が正しくインストールされていない、またはRustのバージョンが古い。
  • 解決方法:
  1. Rustのバージョンを最新に更新します:
    bash rustup update
  2. 依存関係を再インストールします:
    bash cargo clean cargo build

1.2. ウィンドウが表示されない

  • 原因: グラフィックAPIの問題(Vulkan, Metal, DirectXなど)。
  • 解決方法:
  • 必要なグラフィックドライバをインストールします。
  • BevyがサポートするAPIを確認し、環境を設定します。

1.3. リソースがロードされない

  • 原因: assetsフォルダのパスが間違っている、またはファイル名が一致していない。
  • 解決方法:
  • プロジェクトディレクトリ内のassetsフォルダが正しい場所にあるか確認します。
  • ロードするファイル名とパスが正確であることをコードで確認します。

2. デバッグの手法

2.1. ログの活用


Bevyにはログシステムが組み込まれており、問題を特定するのに役立ちます。

  • ログを有効化するには、以下を追加します:
  use bevy::log::LogPlugin;

  App::new()
      .add_plugin(LogPlugin::default())
      .run();

2.2. デバッグモードでの実行


開発中はデバッグモードを使用し、パフォーマンスより詳細なエラー情報を優先します:

cargo run

2.3. ブレークポイントの活用


Rust用デバッガ(lldbgdb)を使ってブレークポイントを設定し、特定のコードが正しく動作しているかを確認します。

3. より効率的なデバッグ

  • 小さなステップで開発: 一度に多くの変更を加えず、少しずつ進めて問題を特定しやすくする。
  • ユニットテスト: 独立したロジックのテストを実装し、エラーの発生を防ぐ。
  • ドキュメントを活用: Bevy公式ドキュメントやフォーラムで類似の問題を調査する。

4. トラブルシューティングのコツ

  • 問題の再現性: エラーが再現する特定の手順を確認し、原因を絞り込む。
  • 他の環境でのテスト: 異なるOSやハードウェアで動作を確認し、環境依存の問題を特定する。
  • コミュニティの活用: BevyのGitHubリポジトリやDiscordコミュニティで質問し、フィードバックを得る。

5. 問題の防止策

  • 開発中に頻繁にコードをコミットし、エラー発生時にすぐに以前の状態に戻せるようにする。
  • 不要なコードや未使用の機能を削除し、シンプルな状態を保つ。

これで、Bevyを使ったプロジェクトのデバッグとトラブルシューティングの基本を学べました。次のステップでは、応用例やさらなる発展方法について解説します。

応用例と次のステップ

ここでは、完成したゲームをさらに発展させるためのアイデアと、Bevyを用いた次の学習ステップを提案します。これらを実装することで、より高度なゲーム開発に挑戦できます。

1. 応用例

1.1. 敵の自動生成とウェーブシステム

  • 実装内容: 一定時間ごとに敵キャラクターを自動生成し、敵の出現数や速度を段階的に増やして難易度を調整します。
  • 方法: タイマーを用いて敵生成ロジックをシステムに追加します:
  fn spawn_enemies(
      mut commands: Commands,
      time: Res<Time>,
      mut timer: ResMut<Timer>,
      asset_server: Res<AssetServer>,
  ) {
      if timer.tick(time.delta()).just_finished() {
          commands.spawn(SpriteBundle {
              texture: asset_server.load("enemy.png"),
              transform: Transform::from_translation(Vec3::new(0.0, 300.0, 0.0)),
              ..Default::default()
          })
          .insert(Enemy);
      }
  }

1.2. スコアのUI表示

  • 実装内容: ゲーム画面にスコアをリアルタイムで表示します。
  • 方法: BevyのUIシステムを活用してスコアを表示します:
  fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
      commands.spawn(TextBundle {
          text: Text::with_section(
              "Score: 0",
              TextStyle {
                  font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                  font_size: 30.0,
                  color: Color::WHITE,
              },
              Default::default(),
          ),
          ..Default::default()
      });
  }

1.3. レベルアップ機能

  • 実装内容: プレイヤーのスコアに応じてゲームの難易度やフィールドを変更します。
  • 方法: スコアリソースを利用し、一定値を超えると新しい敵や障害物をスポーンさせるロジックを追加します。

2. 次の学習ステップ

2.1. 3Dゲーム開発への移行

  • Bevyを用いて3D空間でのゲーム開発を学びます。カメラ操作や3Dモデルのインポート、ライト設定などが含まれます。

2.2. ネットワーク対応ゲームの開発

  • BevyとRustのネットワークライブラリを組み合わせ、マルチプレイヤーゲームの基礎を構築します。

2.3. Bevyプラグインの作成

  • 自作のプラグインを開発し、再利用可能なゲームロジックや機能をモジュール化する方法を学びます。

3. コミュニティとリソース

  • 公式ドキュメント: Bevy公式サイトで最新情報をチェック。
  • GitHubリポジトリ: 他の開発者が公開しているプロジェクトを参考にする。
  • コミュニティ: Discordやフォーラムで質問や議論に参加し、知識を深める。

これらの応用例や次の学習ステップに挑戦することで、Bevyをさらに深く理解し、より複雑なゲーム開発に取り組むことができます。次のプロジェクトを始めてみましょう!

まとめ

本記事では、RustとBevyを使ったシンプルな2Dゲーム開発の手順を詳しく解説しました。Bevyの基本構造であるエンティティ、コンポーネント、システムを理解し、スプライトの追加、アニメーションの実装、入力処理、ゲームロジックの構築までを学びました。さらに、デバッグ方法やトラブルシューティングの重要性についても触れ、発展的な応用例を紹介しました。

Rustの高速性と安全性、そしてBevyの直感的なデザインを活用すれば、初心者でも効率的にゲームを開発できます。この知識を基に、より高度なプロジェクトや独自のゲームアイデアに挑戦してください!

コメント

コメントする

目次