Bevy Engine入門:初心者向けゲーム開発チュートリアル

Bevy Engine 入門:初心者向けゲーム開発チュートリアル

Bevy は、Data-Oriented Technology (DOT) アプローチに基づいて構築された、シンプルでデータ駆動型のゲームエンジンです。Rust で書かれており、高速なコンパイル時間、パフォーマンス、柔軟性を備えています。このチュートリアルでは、Bevy を使用して簡単なゲームを作成する過程を通して、基本的な概念とワークフローを紹介します。

対象読者:

  • Rust の基本的な知識がある方
  • ゲーム開発の経験は必須ではありません
  • 新しいゲームエンジンを試したい方

前提条件:

  • Rust のインストール (rustup を推奨)
  • Cargo (Rust のパッケージマネージャー) の理解
  • 好みのテキストエディタまたは IDE

このチュートリアルで作成するもの:

このチュートリアルでは、画面上を動き回るシンプルな四角形(プレイヤー)を制御し、別の四角形(ターゲット)を避けるゲームを作成します。ターゲットに接触すると、ゲームは終了します。

目次:

  1. Bevy のインストールとプロジェクトのセットアップ
  2. Bevy ウィンドウの作成と基本的な設定
  3. Entity, Component, System (ECS) の概念
  4. プレイヤー Entity の作成と制御
  5. ターゲット Entity の作成と移動
  6. 衝突判定の実装
  7. ゲームオーバーの実装
  8. ボーナス:スコア表示の追加
  9. Bevy の学習リソースと次のステップ

1. Bevy のインストールとプロジェクトのセットアップ

まず、新しい Rust プロジェクトを作成します。ターミナルで次のコマンドを実行します。

bash
cargo new bevy_tutorial
cd bevy_tutorial

次に、Cargo.toml ファイルを編集して Bevy を依存関係として追加します。

“`toml
[package]
name = “bevy_tutorial”
version = “0.1.0”
edition = “2021”

[dependencies]
bevy = “0.12” # 最新の Bevy バージョンを使用してください
rand = “0.8”
“`

bevy = "0.12" の行は、Bevy エンジンをプロジェクトの依存関係として追加しています。 rand は、ランダムな数を生成するために後で使用します。

Cargo.toml を保存したら、Bevy が正常にインストールされたことを確認するために、次のコマンドを実行してプロジェクトをビルドします。

bash
cargo build

エラーが発生しなければ、Bevy のインストールは成功です。

2. Bevy ウィンドウの作成と基本的な設定

src/main.rs ファイルを開き、次のコードを追加します。

“`rust
use bevy::prelude::*;

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

このコードは、Bevy アプリケーションの基本的な構造を示しています。

  • use bevy::prelude::*;: Bevy の最も一般的なモジュールをインポートします。これにより、App, Plugin, Commands などの重要な型に簡単にアクセスできます。
  • fn main() { ... }: Rust の標準的なエントリーポイントです。
  • App::new(): 新しい Bevy アプリケーションを作成します。
  • .add_plugins(DefaultPlugins): Bevy のデフォルトプラグインを追加します。これには、レンダリング、入力、ウィンドウ管理など、基本的な機能が含まれています。
  • .run(): アプリケーションのメインループを開始します。

このコードを実行すると、Bevy ウィンドウが表示されます。ウィンドウは最初は空の黒い画面になります。

ウィンドウのサイズを変更する:

デフォルトのウィンドウサイズは小さい場合があります。ウィンドウのサイズをカスタマイズするには、WindowPlugin を使用します。 main.rs を次のように変更します。

“`rust
use bevy::prelude::*;

fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04))) // 背景色を設定
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: “Bevy Tutorial”.into(),
resolution: (640., 480.).into(), // 幅 640, 高さ 480 に設定
present_mode: bevy::window::PresentMode::AutoVsync,
..default()
}),
..default()
}))
.run();
}
“`

  • insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04))): ウィンドウの背景色を暗い灰色に設定します。
  • .add_plugins(DefaultPlugins.set(WindowPlugin { ... })): DefaultPlugins をオーバーライドして WindowPlugin を設定します。
  • primary_window: Some(Window { ... }): プライマリウィンドウの設定を行います。
  • title: "Bevy Tutorial".into(): ウィンドウのタイトルを設定します。
  • resolution: (640., 480.).into(): ウィンドウの解像度を 640×480 ピクセルに設定します。
  • present_mode: bevy::window::PresentMode::AutoVsync: 垂直同期(Vsync)を有効にします。これにより、画面のティアリングを防止できます。
  • ..default(): 他のすべてのウィンドウ設定をデフォルト値に設定します。

3. Entity, Component, System (ECS) の概念

Bevy は、Entity, Component, System (ECS) アーキテクチャを使用しています。これは、ゲーム開発において非常に強力で柔軟なパラダイムです。

  • Entity: 固有の ID を持つプレーンなデータコンテナです。エンティティ自体は何も持っていませんが、コンポーネントをアタッチすることで意味を与えられます。
  • Component: エンティティにアタッチできるデータ構造です。例えば、Position コンポーネントはエンティティの位置を格納し、Sprite コンポーネントはエンティティの視覚的な表現を格納できます。
  • System: コンポーネントを持つエンティティを処理する関数です。システムは、コンポーネントのデータを読み書きして、エンティティの状態を更新します。

ECS の核心は、データの分離です。エンティティはただの識別子であり、データ(コンポーネント)はエンティティとは別に保存されます。システムは特定のコンポーネントを持つエンティティに対してのみ動作するため、コードの再利用性と保守性が向上します。

例:

プレイヤーを表すエンティティを作成するとします。このエンティティには、次のコンポーネントをアタッチできます。

  • Position: プレイヤーの位置 (x, y 座標)
  • Velocity: プレイヤーの速度 (x, y 速度)
  • Sprite: プレイヤーの視覚的な表現 (色、形など)

次に、MovementSystem を作成して、PositionVelocity コンポーネントを持つエンティティを処理し、位置を更新できます。

4. プレイヤー Entity の作成と制御

次に、プレイヤーエンティティを作成し、キーボード入力で制御できるようにします。

まず、必要なコンポーネントを定義します。src/main.rsmain 関数の前に、次のコードを追加します。

“`rust

[derive(Component)]

struct Player {
speed: f32,
}

[derive(Component)]

struct Position {
x: f32,
y: f32,
}

[derive(Component)]

struct Size(f32, f32);
“`

  • Player: プレイヤー固有のデータ(速度など)を格納するコンポーネントです。
  • Position: エンティティの位置を格納するコンポーネントです。
  • Size: エンティティのサイズを格納するコンポーネントです。

次に、プレイヤーエンティティを作成するシステムを追加します。main.rs に次の関数を追加します。

rust
fn spawn_player(mut commands: Commands) {
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.3, 0.3, 0.7),
..default()
},
transform: Transform {
translation: Vec3::new(0., 0., 0.),
scale: Vec3::new(50., 50., 1.),
..default()
},
..default()
},
Player { speed: 200. },
Position { x: 0., y: 0. },
Size(50., 50.),
));
}

  • spawn_player: プレイヤーエンティティを作成するシステムです。
  • mut commands: Commands: Bevy のコマンドバッファへの可変なアクセスを提供します。エンティティの作成、コンポーネントの追加・削除、リソースの挿入などを実行するために使用します。
  • commands.spawn(( ... )): 新しいエンティティを生成し、指定されたコンポーネントを追加します。
  • SpriteBundle: Sprite を描画するために必要なコンポーネントのバンドルです。Sprite, Transform, Visibility などのコンポーネントが含まれます。
    • sprite: Sprite { ... }: Sprite の外観を設定します。
      • color: Color::rgb(0.3, 0.3, 0.7): Sprite の色を明るい青色に設定します。
    • transform: Transform { ... }: Sprite の位置、回転、スケールを設定します。
      • translation: Vec3::new(0., 0., 0.): Sprite の位置を (0, 0) に設定します。
      • scale: Vec3::new(50., 50., 1.): Sprite のスケールを (50, 50) に設定します。
  • Player { speed: 200. }: プレイヤーコンポーネントを追加し、速度を 200 に設定します。
  • Position { x: 0., y: 0. }: プレイヤーの位置コンポーネントを追加し、初期位置を (0, 0) に設定します。
  • Size(50., 50.): プレイヤーのサイズを50×50に設定します。

次に、プレイヤーを移動させるシステムを追加します。

“`rust
fn player_movement(
keyboard_input: Res>,
mut query: Query<(&mut Position, &Player)>,
time: Res

let mut direction_x = 0.0;
let mut direction_y = 0.0;

if keyboard_input.pressed(KeyCode::Left) {
    direction_x -= 1.0;
}
if keyboard_input.pressed(KeyCode::Right) {
    direction_x += 1.0;
}
if keyboard_input.pressed(KeyCode::Up) {
    direction_y += 1.0;
}
if keyboard_input.pressed(KeyCode::Down) {
    direction_y -= 1.0;
}

// 正規化して、斜め移動の速度が速くならないようにする
let movement_direction = Vec2::new(direction_x, direction_y).normalize_or_zero();

position.x += movement_direction.x * player.speed * time.delta_seconds();
position.y += movement_direction.y * player.speed * time.delta_seconds();

}
“`

  • player_movement: プレイヤーの移動を処理するシステムです。
  • keyboard_input: Res<Input<KeyCode>>: キーボード入力を取得するためのリソースです。
  • mut query: Query<(&mut Position, &Player)>: PositionPlayer コンポーネントを持つエンティティをクエリします。mut は、Position コンポーネントを可変として取得することを意味します。 single_mut を使用して、クエリの結果が唯一のエンティティであることを保証します。複数のプレイヤーが存在する場合は、iter_mut を使用してすべてのプレイヤーに対して繰り返す必要があります。
  • time: Res<Time>: 時間情報を取得するためのリソースです。
  • position.x += ...: プレイヤーの位置を更新します。
  • player.speed * time.delta_seconds(): 1フレームあたりの移動距離を計算します。time.delta_seconds() は、前回のフレームからの経過時間(秒)を返します。
  • Vec2::new(direction_x, direction_y).normalize_or_zero(): 進行方向を正規化します。これにより、斜め方向に移動する際に速度が上昇するのを防ぎます。

最後に、これらのシステムをアプリケーションに追加します。 main 関数を次のように変更します。

“`rust
use bevy::prelude::*;

[derive(Component)]

struct Player {
speed: f32,
}

[derive(Component)]

struct Position {
x: f32,
y: f32,
}

[derive(Component)]

struct Size(f32, f32);

fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04))) // 背景色を設定
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: “Bevy Tutorial”.into(),
resolution: (640., 480.).into(), // 幅 640, 高さ 480 に設定
present_mode: bevy::window::PresentMode::AutoVsync,
..default()
}),
..default()
}))
.add_systems(Startup, spawn_player)
.add_systems(Update, player_movement)
.run();
}

fn spawn_player(mut commands: Commands) {
// (前のコード)
}

fn player_movement(
// (前のコード)
}
“`

  • .add_systems(Startup, spawn_player): アプリケーションの起動時に spawn_player システムを一度だけ実行します。
  • .add_systems(Update, player_movement): アプリケーションの更新ループ中に player_movement システムを毎フレーム実行します。

これで、プロジェクトを実行すると、矢印キーで制御できる青い四角形が表示されます。

5. ターゲット Entity の作成と移動

次に、ターゲットエンティティを作成し、画面上をランダムに移動させます。

まず、ターゲットを表すコンポーネントを作成します。

“`rust

[derive(Component)]

struct Target;
“`

次に、ターゲットエンティティを作成するシステムを追加します。

“`rust
use rand::Rng;

fn spawn_target(mut commands: Commands) {
let mut rng = rand::thread_rng();
let x = rng.gen_range(-300.0..300.0);
let y = rng.gen_range(-200.0..200.0);

commands.spawn((
    SpriteBundle {
        sprite: Sprite {
            color: Color::rgb(0.8, 0.2, 0.2),
            ..default()
        },
        transform: Transform {
            translation: Vec3::new(x, y, 0.),
            scale: Vec3::new(50., 50., 1.),
            ..default()
        },
        ..default()
    },
    Target,
    Position { x, y },
    Size(50., 50.),
));

}
“`

  • use rand::Rng: rand クレートの Rng トレイトをインポートします。これにより、ランダムな数を生成できます。
  • let mut rng = rand::thread_rng(): 乱数ジェネレータを作成します。
  • let x = rng.gen_range(-300.0..300.0): x 座標を -300 から 300 の範囲でランダムに生成します。
  • let y = rng.gen_range(-200.0..200.0): y 座標を -200 から 200 の範囲でランダムに生成します。
  • Target: ターゲットエンティティを識別するためのマーカーコンポーネントです。
  • Position { x, y }: ランダムに生成された位置でターゲットの位置コンポーネントを設定します。

次に、ターゲットを移動させるシステムを追加します。ここでは、単純にランダムな方向に移動させます。

“`rust
fn target_movement(
mut query: Query<(&mut Position, &Target)>,
time: Res

for (mut position, _target) in query.iter_mut() {
    let x_direction = rng.gen_range(-1.0..1.0);
    let y_direction = rng.gen_range(-1.0..1.0);

    position.x += x_direction * 100.0 * time.delta_seconds();
    position.y += y_direction * 100.0 * time.delta_seconds();

    // 画面外に出ないように位置を制限する
    position.x = position.x.clamp(-320.0, 320.0);
    position.y = position.y.clamp(-240.0, 240.0);
}

}
“`

  • query: Query<(&mut Position, &Target)>: PositionTarget コンポーネントを持つエンティティをクエリします。
  • query.iter_mut(): クエリの結果をイテレートします。
  • x_direction = rng.gen_range(-1.0..1.0): x 軸方向の移動量を -1.0 から 1.0 の範囲でランダムに生成します。
  • y_direction = rng.gen_range(-1.0..1.0): y 軸方向の移動量を -1.0 から 1.0 の範囲でランダムに生成します。
  • position.x = position.x.clamp(-320.0, 320.0): ターゲットが画面外に出ないように、位置をクランプします。

最後に、これらのシステムをアプリケーションに追加します。 main 関数を次のように変更します。

“`rust
use bevy::prelude::*;
use rand::Rng;

[derive(Component)]

struct Player {
speed: f32,
}

[derive(Component)]

struct Target;

[derive(Component)]

struct Position {
x: f32,
y: f32,
}

[derive(Component)]

struct Size(f32, f32);

fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04))) // 背景色を設定
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: “Bevy Tutorial”.into(),
resolution: (640., 480.).into(), // 幅 640, 高さ 480 に設定
present_mode: bevy::window::PresentMode::AutoVsync,
..default()
}),
..default()
}))
.add_systems(Startup, (spawn_player, spawn_target))
.add_systems(Update, (player_movement, target_movement))
.run();
}

fn spawn_player(mut commands: Commands) {
// (前のコード)
}

fn spawn_target(mut commands: Commands) {
// (前のコード)
}

fn player_movement(
// (前のコード)
}

fn target_movement(
mut query: Query<(&mut Position, &Target)>,
time: Res

  • .add_systems(Startup, (spawn_player, spawn_target)): アプリケーションの起動時に spawn_playerspawn_target システムを両方とも実行します。
  • .add_systems(Update, (player_movement, target_movement)): アプリケーションの更新ループ中に player_movementtarget_movement システムを両方とも毎フレーム実行します。

これで、プロジェクトを実行すると、矢印キーで制御できる青い四角形と、ランダムに動き回る赤い四角形が表示されます。

6. 衝突判定の実装

次に、プレイヤーがターゲットに衝突したかどうかを判定するシステムを追加します。

“`rust
fn collision_detection(
mut commands: Commands,
player_query: Query<(&Position, &Size), (With, Without)>,
target_query: Query<(&Position, &Size), (With, Without)>,
) {
let (player_position, player_size) = player_query.single();
let (target_position, target_size) = target_query.single();

let player_x = player_position.x;
let player_y = player_position.y;
let player_width = player_size.0;
let player_height = player_size.1;

let target_x = target_position.x;
let target_y = target_position.y;
let target_width = target_size.0;
let target_height = target_size.1;

// AABB (Axis-Aligned Bounding Box) 衝突判定
if player_x < target_x + target_width &&
    player_x + player_width > target_x &&
    player_y < target_y + target_height &&
    player_y + player_height > target_y {
    // 衝突が発生した場合
    println!("衝突!");
    commands.insert_resource(NextState(Some(GameState::GameOver))); // ゲームオーバー状態に遷移
}

}
“`

  • collision_detection: 衝突判定を行うシステムです。
  • player_query: Query<(&Position, &Size), (With<Player>, Without<Target>)>: PositionSize コンポーネントを持ち、Player コンポーネントを持つが Target コンポーネントを持たないエンティティをクエリします。 With<Player>Without<Target> は、クエリのフィルターとして機能します。
  • target_query: Query<(&Position, &Size), (With<Target>, Without<Player>)>: PositionSize コンポーネントを持ち、Target コンポーネントを持つが Player コンポーネントを持たないエンティティをクエリします。
  • AABB (Axis-Aligned Bounding Box) 衝突判定: 2つの四角形が重なり合っているかどうかを判定します。
  • commands.insert_resource(NextState(Some(GameState::GameOver))): 衝突が発生した場合、GameState::GameOver 状態に遷移するように NextState リソースを挿入します。

7. ゲームオーバーの実装

次に、ゲームオーバー状態を実装します。

まず、ゲームの状態を表す Enum を定義します。

“`rust

[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]

enum GameState {
#[default]
Playing,
GameOver,
}
“`

  • #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]: この Enum が State として使用できるように、Bevy の State マクロを派生させます。
  • #[default] Playing: Playing 状態をアプリケーションの初期状態として設定します。

次に、ゲームオーバー画面を表示するシステムを作成します。

“`rust
fn game_over_screen(mut commands: Commands, asset_server: Res) {
commands.spawn(
(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
background_color: Color::rgba(0.0, 0.0, 0.0, 0.8).into(),
..default()
},
GameOverScreen)
.with_children(|parent| {
parent.spawn(
TextBundle::from_section(
“ゲームオーバー”,
TextStyle {
font: asset_server.load(“fonts/FiraSans-Bold.ttf”), // フォントファイルを読み込む
font_size: 60.0,
color: Color::WHITE,
},
)
.with_style(Style {
margin: UiRect::all(Val::Px(5.0)),
..default()
}),
);
}),
);
}

[derive(Component)]

struct GameOverScreen;
“`

  • game_over_screen: ゲームオーバー画面を表示するシステムです。
  • asset_server: Res<AssetServer>: アセットをロードするためのリソースです。
  • commands.spawn(NodeBundle { ... }): 新しい UI ノードを作成します。
    • NodeBundle: UI 要素の基本的なバンドルです。
    • style: Style { ... }: UI 要素のスタイルを設定します。
      • size: Size::new(Val::Percent(100.0), Val::Percent(100.0)): UI 要素のサイズを画面全体の 100% に設定します。
      • align_items: AlignItems::Center: 子要素を中央に配置します。
      • justify_content: JustifyContent::Center: コンテンツを中央に配置します。
    • background_color: Color::rgba(0.0, 0.0, 0.0, 0.8).into(): 背景色を半透明の黒色に設定します。
  • .with_children(|parent| { ... }): UI ノードの子要素を作成します。
  • parent.spawn(TextBundle::from_section( ... )): テキストを表示する UI ノードを作成します。
    • TextBundle: テキスト要素の基本的なバンドルです。
    • TextBundle::from_section( ... ): 指定されたテキストとスタイルでテキストバンドルを作成します。
    • TextStyle { ... }: テキストのスタイルを設定します。
      • font: asset_server.load("fonts/FiraSans-Bold.ttf"): 使用するフォントファイルをロードします。 注意: このフォントファイルは、プロジェクトの assets/fonts フォルダに配置する必要があります。 後でフォントアセットを追加する方法を説明します。
      • font_size: 60.0: フォントサイズを 60.0 に設定します。
      • color: Color::WHITE: テキストの色を白色に設定します。
    • .with_style(Style { ... }): テキスト要素のスタイルを設定します。
      • margin: UiRect::all(Val::Px(5.0)): テキストの周りに 5 ピクセルのマージンを設定します。
  • #[derive(Component)] struct GameOverScreen;: ゲームオーバー画面のエンティティを識別するためのマーカーコンポーネントです。

最後に、ゲームオーバー画面を削除するシステムを作成します。これは、ゲームが Playing 状態に戻ったときに、ゲームオーバー画面が残らないようにするためです。

rust
fn remove_game_over_screen(mut commands: Commands, query: Query<Entity, With<GameOverScreen>>) {
for entity in query.iter() {
commands.entity(entity).despawn_recursive();
}
}

  • remove_game_over_screen: ゲームオーバー画面を削除するシステムです。
  • query: Query<Entity, With<GameOverScreen>>: GameOverScreen コンポーネントを持つエンティティをクエリします。
  • commands.entity(entity).despawn_recursive(): エンティティとその子要素をすべて削除します。

アプリケーションに衝突判定とゲームオーバー状態の遷移を追加します。 main 関数を次のように変更します。

“`rust
use bevy::prelude::*;
use rand::Rng;

[derive(Component)]

struct Player {
speed: f32,
}

[derive(Component)]

struct Target;

[derive(Component)]

struct Position {
x: f32,
y: f32,
}

[derive(Component)]

struct Size(f32, f32);

[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]

enum GameState {
#[default]
Playing,
GameOver,
}

[derive(Component)]

struct GameOverScreen;

fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04))) // 背景色を設定
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: “Bevy Tutorial”.into(),
resolution: (640., 480.).into(), // 幅 640, 高さ 480 に設定
present_mode: bevy::window::PresentMode::AutoVsync,
..default()
}),
..default()
}))
.add_state::() // ゲームの状態を管理するために追加
.add_systems(Startup, (spawn_player, spawn_target))
.add_systems(Update, player_movement.run_if(in_state(GameState::Playing))) // Playing 状態でのみ実行
.add_systems(Update, target_movement.run_if(in_state(GameState::Playing))) // Playing 状態でのみ実行
.add_systems(Update, collision_detection.run_if(in_state(GameState::Playing))) // Playing 状態でのみ実行
.add_systems(OnEnter(GameState::GameOver), game_over_screen) // GameOver 状態に移行したときに実行
.add_systems(OnExit(GameState::GameOver), remove_game_over_screen) // GameOver 状態から移行するときに実行
.run();
}

fn spawn_player(mut commands: Commands) {
// (前のコード)
}

fn spawn_target(mut commands: Commands) {
// (前のコード)
}

fn player_movement(
// (前のコード)
}

fn target_movement(
// (前のコード)
}

fn collision_detection(
//(前のコード)
}

fn game_over_screen(mut commands: Commands, asset_server: Res) {
// (前のコード)
}

fn remove_game_over_screen(mut commands: Commands, query: Query>) {
// (前のコード)
}
“`

  • .add_state::<GameState>(): GameState Enum をアプリケーションのステートとして登録します。
  • .run_if(in_state(GameState::Playing)): 指定されたシステムを GameState::Playing 状態でのみ実行するように制限します。
  • .add_systems(OnEnter(GameState::GameOver), game_over_screen): GameState::GameOver 状態に入ったときに game_over_screen システムを実行します。
  • .add_systems(OnExit(GameState::GameOver), remove_game_over_screen): GameState::GameOver 状態から離れるときに remove_game_over_screen システムを実行します。

フォントアセットの追加

Bevy がフォントファイルを見つけられるようにするには、assets フォルダを作成し、その中に fonts フォルダを作成します。 好きなフォント(例えば、FiraSans-Bold.ttf)を assets/fonts フォルダに配置します。 Cargo.toml ファイルのルートディレクトリに assets というフォルダを作成します。

これで、プロジェクトを実行すると、プレイヤーがターゲットに衝突するとゲームオーバー画面が表示されます。

8. ボーナス:スコア表示の追加

スコアを表示するには、次の手順を実行します。

  1. スコアコンポーネントの作成:

“`rust

[derive(Component, Default)]

struct Score {
value: u32,
}
“`

  1. スコアリソースの初期化:

rust
fn setup_score(mut commands: Commands) {
commands.insert_resource(Score { value: 0 });
}

  1. スコアを表示する UI 要素の作成:

rust
fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(
TextBundle::from_section(
"Score: 0",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), // フォントファイルを読み込む
font_size: 30.0,
color: Color::WHITE,
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(5.0),
left: Val::Px(5.0),
..default()
}),
);
}

  1. スコアを更新するシステム:

rust
fn update_score(mut score: ResMut<Score>, mut query: Query<&mut Text>) {
score.value += 1;
for mut text in &mut query {
text.sections[0].value = format!("Score: {}", score.value);
}
}

  1. 衝突時にスコアをリセットするシステム:

rust
fn reset_score(mut score: ResMut<Score>) {
score.value = 0;
}

  1. システムとリソースをアプリケーションに追加:

“`rust
fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04))) // 背景色を設定
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: “Bevy Tutorial”.into(),
resolution: (640., 480.).into(), // 幅 640, 高さ 480 に設定
present_mode: bevy::window::PresentMode::AutoVsync,
..default()
}),
..default()
}))
.add_state::() // ゲームの状態を管理するために追加
.add_systems(Startup, (spawn_player, spawn_target, setup_ui, setup_score))
.add_systems(Update, player_movement.run_if(in_state(GameState::Playing))) // Playing

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール