Rust – Bevyでゲームを作る勉強記録

Rust

Rustのamethystを利用した本があったので、それを読み進めていたのですが実行ができなかった。多分私のGPUとかの問題?vulkan物理デバイスが見つからないと怒られmacはxcode入れるのが面倒くさいので止めました。

途方に暮れていたらBEVYを見つけました。まずちゃんと起動できるかどうかを先にサンプルで試したところ、ブロック崩しのゲームが開始されたので、これはいけると思い始めました。

環境構築

Bevyのプロジェクトを立ち上げるため、環境構築をします。公式ドキュメントの通りに進めていきます。

まずは普通にプロジェクトを作成

D:\rust>cargo new my_sample
     Created binary (application) `my_sample` package

D:\rust>cd my_sample

こうすると空のプロジェクトが作られるのでCargo.tomlは以下のようになっている

[package]
name = "my_sample"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

次に依存を追加する

cargo add bevy

依存関係が追加される

[package]
name = "my_sample"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bevy = "0.8.1"

まずは動かす

まずはログにテキストを出力してみます。

2d/sprite.rsを作成します。

適当にテキストをプリントしているだけです。
この時点で補完が効かないのでCargo.tomlに以下を追記します。
※この辺のCargo.toml修正の動きはよく理解してないのでとりあえず公式を参考にやってます。

[[example]]
name = "sprite"
path = "2d/sprite.rs"

これで実行するとログが出力されます

アプリケーションの作成

今度はBevyアプリケーションを作成します。アプリケーションに対してスケジュールを登録するという単純なことをやります

sprite.rsを以下のようにして実行します

use bevy::prelude::*;

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

fn setup(){
    print!("グッジョブです!と、ミサカは惜しみない賞賛を贈ります。");  
}
   Compiling my_sample v0.1.0 (D:\rust\my_sample)
    Finished dev [unoptimized + debuginfo] target(s) in 2.28s
     Running `target\debug\examples\sprite.exe`
グッジョブです!と、ミサカは惜しみない賞賛を贈ります。 *  ターミナルはタスクで再利用されます、閉じるには任意のキーを押してください。 

画像を表示する

次に画像を表示させます。

assets/imagesフォルダを作成して、画像を入れておきます。

sprinte.rsを以下のように修正して実行します

use bevy::prelude::*;

fn main(){
    App::new()
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .run();
    // print!("グッジョブです!と、ミサカは惜しみない賞賛を贈ります。");  
}

fn setup(mut command: Commands, asset_server: Res<AssetServer>){
    command.spawn_bundle(Camera2dBundle::default());
    command.spawn_bundle(SpriteBundle {
        texture: asset_server.load("images/chara.png"),
        ..default()
    });
}

巨大な画像が表示されました。

Bevyは様々な機能をプラグインとして提供しているので、必要な機能のみを選んで利用することができます。例えばUIが不要ならばUiPluginを入れなければいいという感じです。

手間のかからないフルエンジンであるデフォルトプラグインを利用しました。CorePluginやInputPlugin、WindowPluginなどが追加されます。

ウインドウサイズを変更する

fn main(){
    App::new()
    .insert_resource(WindowDescriptor{
        title:"サンプルゲーム".to_string(),
        width:480.0,
        height:320.0,
        ..Default::default()
    })
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .run(); 
}

指定したサイズで画面が表示さており、タイトルもサンプルゲームに変更されています。

画像のスケール

表示されている画像が大きいので縮小して表示させます。利用した画像が大きかったのでかなり縮小して表示しています。

SpriteBundleのtransformを指定しています

fn setup(mut command: Commands, asset_server: Res<AssetServer>){
    command.spawn_bundle(Camera2dBundle::default());
    command.spawn_bundle(SpriteBundle {
        texture: asset_server.load("images/chara.png"),
        transform: Transform {
            scale: Vec3::new(0.1,0.1,1.0),
            ..Default::default()
        },
        ..default()
    });
}

かなり縮小してイイ感じ

動かす

表示したキャラクターを動かしてみます。通常は画像を切り替えてアニメーションを表現しますが、今回はこの画像のまま座標移動のみをやってみます。

ところで画像を表示する時はadd_startup_systemを使いましたが、add_systemというのもあり、これを使うと何度も呼ばれることになります。

以下のようなコンソール出力を行うsample関数を登録すると…

fn main(){
    App::new()
    .insert_resource(WindowDescriptor{
        title:"サンプルゲーム".to_string(),
        width:480.0,
        height:320.0,
        ..Default::default()
    })    
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .add_system(sample)
    .run(); 
}

fn sample(){
    println!("御坂美琴");
}

何度もログが出力されていることがわかります。

そのため移動系のメソッドは、add_systemを利用して関数を登録することにします

Query

Entityのcomponentsを取得することができます。ここを見ると詳しく書いてます

Queries - Unofficial Bevy Cheat Book

とりあえずコンポーネントを付けないといけないのでNameというコンポーネントをSpriteBundleに対して登録します。

そしてmovementメソッドをadd_systemに渡します

use bevy::prelude::*;

// コンポーネントを定義する
#[derive(Component)]
struct Name {
    name: String,
}

fn main(){
    App::new()
    .insert_resource(WindowDescriptor{
        title:"サンプルゲーム".to_string(),
        width:480.0,
        height:320.0,
        ..Default::default()
    })    
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .add_system(movement)
    .run(); 
}

// NameとTransformコンポーネントを持つエンティティへのアクセス
fn movement(mut query: Query<(&Name, &mut Transform)>){
    for( name, mut transform) in query.iter_mut(){
        transform.translation.x += 0.1;
        println!("{}", name.name);
    }
}

fn setup(mut command: Commands, asset_server: Res<AssetServer>){
    command.spawn_bundle(Camera2dBundle::default());
    command.spawn_bundle(SpriteBundle {
        texture: asset_server.load("images/chara.png"),
        transform: Transform {
            scale: Vec3::new(0.1,0.1,1.0),
            ..Default::default()
        },
        ..default()
    })
    // コンポーネントを登録する
    .insert(Name { name:"垣根提督".to_string() } );
}

少しずつ画像が移動しているのがわかります。(無駄にログ出力していますがお許しください)

取得できるEntityが一つだと保証できる場合はsingle_mutメソッドが利用できます。

fn movement(mut query: Query<(&Name, &mut Transform)>){
    let (name, mut transform) = query.single_mut();
    transform.translation.x += 0.1;
    println!("{}", name.name);
}

この辺のライフタイムは全く勉強してないので宿題です…

Playerにする

今はNameというコンポーネントで無理やりクエリに適合させていますが?Playerというコンポーネントを使って再定義します。

use bevy::prelude::*;


// Playerコンポーネントを定義する
#[derive(Component)]
struct Player;

fn main(){
    App::new()
    .insert_resource(WindowDescriptor{
        title:"サンプルゲーム".to_string(),
        width:480.0,
        height:320.0,
        ..Default::default()
    })    
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .add_system(movement)
    .run(); 
}


// TransformとPlayerコンポーネントを持つエンティティへのアクセス(クエリフィルタの利用)
fn movement(mut query: Query<&mut Transform,With<Player>>){
    let mut transform = query.single_mut();
    transform.translation.x += 0.1;
}


fn setup(mut command: Commands, asset_server: Res<AssetServer>){
    command.spawn_bundle(Camera2dBundle::default());
    command.spawn_bundle(SpriteBundle {
        texture: asset_server.load("images/chara.png"),
        transform: Transform {
            scale: Vec3::new(0.1,0.1,1.0),
            ..Default::default()
        },
        ..default()
    })
    // Playerを登録する
    .insert(Player);
}

出力結果は同じですが、こっちのほうがわかりやすいですね。

キーボードで動かす

最後に画像をキーボードで動かします。

movementメソッドでkeyイベントを受け取るようにすればOKです。

use bevy::prelude::*;

// Playerコンポーネントを定義する
#[derive(Component)]
struct Player;

fn main(){
    App::new()
    .insert_resource(WindowDescriptor{
        title:"サンプルゲーム".to_string(),
        width:480.0,
        height:320.0,
        ..Default::default()
    })    
    .add_plugins(DefaultPlugins)
    .add_startup_system(setup)
    .add_system(movement)
    .run(); 
}

fn movement(
    // キーイベントを追加
    key_input : Res<Input<KeyCode>>,
    mut query: Query<&mut Transform,With<Player>>
){
    // transformの取得
    let mut transform = query.single_mut();
    // →キーを押している場合
    if key_input.pressed(KeyCode::Right) {
        // 移動量増やしています
        transform.translation.x += 0.8;
    }
}

/*
// TransformとPlayerコンポーネントを持つエンティティへのアクセス(クエリフィルタの利用)
fn movement(mut query: Query<&mut Transform,With<Player>>){
    let mut transform = query.single_mut();
    transform.translation.x += 0.1;
}
*/

fn setup(mut command: Commands, asset_server: Res<AssetServer>){
    command.spawn_bundle(Camera2dBundle::default());
    command.spawn_bundle(SpriteBundle {
        texture: asset_server.load("images/chara.png"),
        transform: Transform {
            scale: Vec3::new(0.1,0.1,1.0),
            ..Default::default()
        },
        ..default()
    })
    // Playerを登録する
    .insert(Player);
}

矢印キーの右を押している間だけ右に動きます。

駆け足で色々無視しながらですが、キーボードで画像を動かすところまではできました。
簡単にキーイベントも受け取れるのが便利です。

最後に一言

そもそも、もっとRust自体の勉強をしないといけないということがよくわかりました(笑)

利用させていただいた画像

イラストレーター 早瀬ひろむ_SDイラストレーターさんのプロフィール/無料イラストなら「イラストAC」
おしゃれでかわいいイラストレーター 早瀬ひろむ_SDイラストレーターさんのプロフィールの無料イラスト・人物・フレーム・動物・年賀状などの素材がフリー。AI・EPSやJPEG・PNG形式の画像も無料でダウンロードOK!商用利用、編集もOKなイラストAC。

コメント

タイトルとURLをコピーしました