Bevy Engine 入門:初心者向けゲーム開発チュートリアル
Bevy は、Data-Oriented Technology (DOT) アプローチに基づいて構築された、シンプルでデータ駆動型のゲームエンジンです。Rust で書かれており、高速なコンパイル時間、パフォーマンス、柔軟性を備えています。このチュートリアルでは、Bevy を使用して簡単なゲームを作成する過程を通して、基本的な概念とワークフローを紹介します。
対象読者:
- Rust の基本的な知識がある方
- ゲーム開発の経験は必須ではありません
- 新しいゲームエンジンを試したい方
前提条件:
- Rust のインストール (rustup を推奨)
- Cargo (Rust のパッケージマネージャー) の理解
- 好みのテキストエディタまたは IDE
このチュートリアルで作成するもの:
このチュートリアルでは、画面上を動き回るシンプルな四角形(プレイヤー)を制御し、別の四角形(ターゲット)を避けるゲームを作成します。ターゲットに接触すると、ゲームは終了します。
目次:
- Bevy のインストールとプロジェクトのセットアップ
- Bevy ウィンドウの作成と基本的な設定
- Entity, Component, System (ECS) の概念
- プレイヤー Entity の作成と制御
- ターゲット Entity の作成と移動
- 衝突判定の実装
- ゲームオーバーの実装
- ボーナス:スコア表示の追加
- 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
を作成して、Position
と Velocity
コンポーネントを持つエンティティを処理し、位置を更新できます。
4. プレイヤー Entity の作成と制御
次に、プレイヤーエンティティを作成し、キーボード入力で制御できるようにします。
まず、必要なコンポーネントを定義します。src/main.rs
の main
関数の前に、次のコードを追加します。
“`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)>
:Position
とPlayer
コンポーネントを持つエンティティをクエリします。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)>
:Position
とTarget
コンポーネントを持つエンティティをクエリします。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_player
とspawn_target
システムを両方とも実行します。.add_systems(Update, (player_movement, target_movement))
: アプリケーションの更新ループ中にplayer_movement
とtarget_movement
システムを両方とも毎フレーム実行します。
これで、プロジェクトを実行すると、矢印キーで制御できる青い四角形と、ランダムに動き回る赤い四角形が表示されます。
6. 衝突判定の実装
次に、プレイヤーがターゲットに衝突したかどうかを判定するシステムを追加します。
“`rust
fn collision_detection(
mut commands: Commands,
player_query: Query<(&Position, &Size), (With
target_query: Query<(&Position, &Size), (With
) {
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>)>
:Position
とSize
コンポーネントを持ち、Player
コンポーネントを持つがTarget
コンポーネントを持たないエンティティをクエリします。With<Player>
とWithout<Target>
は、クエリのフィルターとして機能します。target_query: Query<(&Position, &Size), (With<Target>, Without<Player>)>
:Position
とSize
コンポーネントを持ち、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. ボーナス:スコア表示の追加
スコアを表示するには、次の手順を実行します。
- スコアコンポーネントの作成:
“`rust
[derive(Component, Default)]
struct Score {
value: u32,
}
“`
- スコアリソースの初期化:
rust
fn setup_score(mut commands: Commands) {
commands.insert_resource(Score { value: 0 });
}
- スコアを表示する 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()
}),
);
}
- スコアを更新するシステム:
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);
}
}
- 衝突時にスコアをリセットするシステム:
rust
fn reset_score(mut score: ResMut<Score>) {
score.value = 0;
}
- システムとリソースをアプリケーションに追加:
“`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