Behind Coleus

大学生なのに特別大学で楽しいことが一 つもない。虐待と男子校病のせいにしてみる。→日本脱出しました 今ジャカルタにいます。→ジャカルタから帰ってきました。→6月19日から小笠原沖で、インドネシア人とメカジキ漁

昨日は夜全然寝られなくて、12時30くらいにむくむくと起き上がって、2時くらいまでMTG Arenaやってました。でも、9時には起きたよ。

これ、結構よかったですね。

今日2時くらいから急に寒くなってきた

昨日の夜は、もう駄目だーみたいな気持ちになっていたんですけれども、今日の10時くらいからは意外と集中できてなんと15時30にプログラミングを終わらすことが出来た。

そのあと、16時30くらいから自炊もして、めちゃめちゃおいしい料理が出来た。

ただ、明日大学の先生に会うって言うのになんも準備してない。

バイトもあるし。。。

 

youtu.be今日ものんびりBevy コード解説やっていきましょうねー

github.com

 

目的

今回は、前回作ったゲームに、エスケープキーを押すことでのゲーム終了機能、敵が数秒ごとに、勝手に増える機能を追加する機能、敵がヒットした時のゲームオーバーイベントを追加して最終スコアを表示する機能、それら3つを追加することが目的です。

 

目的への取り組み

今回作ったコードは以下の通りである。bevy 0.15.3をダウンしたWindowsVScodeを動かした環境である。

use bevy::{prelude::*, window::PrimaryWindow, app::AppExit, };
use rand::random;
pub const PLAYER_SPEED: f32 = 500.0;
pub const PLAYER_SIZE: f32 = 130.0;
pub const NUMBER_OF_ENEMIES: usize = 3;
pub const ENEMY_SPEED: f32 = 200.0;
pub const ENEMY_SIZE: f32 = 130.0;
pub const NUMBER_OF_STARS:usize = 10;
pub const STAR_SIZE: f32 = 30.0;
pub const STAR_SPAWN_TIME: f32 = 0.3;
pub const ENEMY_SPAWN_TIME: f32 = 5.0;

fn main() {
    App::new()
    .add_plugins(DefaultPlugins)
    .init_resource::<Score>()
    .init_resource::<StarSpawnTimer>()
    .init_resource::<EnemySpawnTimer>()
    .init_resource::<HighScores>()
    .add_event::<GameOver>()
    .add_systems(Startup, spawn_camera)
    .add_systems(Startup, spawn_player)
    .add_systems(Startup, (spawn_enemies, spawn_stars))
    .add_systems(Update, (player_movement, confine_player_movement).chain())
    .add_systems(Update,  (enemy_movement, update_enemy_direction, confine_enemy_movement).chain())
    .add_systems(Update, (player_hit_star, update_score, tick_star_spawn_timer, spawn_stars_over_time, enemy_hit_player))
    .add_systems(Update, (handle_game_over, exit_game, spawn_enemies_over_time, tick_enemy_spawn_timer, ))
    .add_systems(Update, (update_high_scores, high_scores_updated).chain())
    .run();
}
#[derive(Component)]
pub struct Player{}

#[derive(Component)]
pub struct Enemy{
    pub direction: Vec2,
}

#[derive(Component)]
pub struct Star{}

#[derive(Resource)]
pub struct Score{
    pub value: u32,
}

impl Default for Score {
    fn default() -> Self {
        Score {value: 0}
    }
   
}

#[derive(Resource, Debug)]
pub struct HighScores{
    pub scores: Vec<(String, u32)>
}

impl Default for HighScores{
    fn default() -> Self {
        HighScores{ scores: Vec::new() }
    }
}


#[derive(Resource)]
pub struct StarSpawnTimer{
    pub timer: Timer,
}

impl Default for  StarSpawnTimer {
    fn default() -> Self {
        StarSpawnTimer{
            timer: Timer::from_seconds(STAR_SPAWN_TIME, TimerMode::Repeating),
        }
    }    
}

#[derive(Resource)]
pub struct EnemySpawnTimer{
    pub timer: Timer,
}

impl Default for  EnemySpawnTimer {
    fn default() -> Self {
        EnemySpawnTimer{
            timer: Timer::from_seconds(ENEMY_SPAWN_TIME, TimerMode::Repeating),
        }
    }    
}

#[derive(Event)]
pub struct GameOver{
    pub score: u32,
}

pub fn spawn_player(
    mut commands: Commands,
    window_query: Query<&Window, With<PrimaryWindow>>,
    asset_server: Res<AssetServer>,
){
    let window = window_query.get_single().unwrap();

    commands.spawn(
        (
            Sprite{
            image: asset_server.load("sprites/ball_blue_large.png"),
            ..default()
        },
        Transform::from_xyz(window.width()/2.0, window.height()/2.0, 0.0),
        Player {},        
    ));
}

pub fn spawn_camera(mut commands: Commands, window_query: Query<&Window, With<PrimaryWindow>>) {
    let window = window_query.get_single().unwrap();

    commands.spawn(
        (
            Camera2d::default(),
            Camera{
                hdr:true, ..default()
            },
            Transform::from_xyz(window.width()/2.0, window.height()/2.0, 0.0),
        )

    );
}

pub fn spawn_enemies(
    mut commands: Commands,
    window_query: Query<&Window, With<PrimaryWindow>>,
    asset_server: Res<AssetServer>,
){
    let window = window_query.get_single().unwrap();
    for _ in 0..NUMBER_OF_ENEMIES{
        let random_x = random::<f32>() * (window.width() - ENEMY_SIZE);
        let random_y = random::<f32>() * (window.height() - ENEMY_SIZE);

        commands.spawn(
            (
                Sprite{
                image: asset_server.load("sprites/ball_red_large.png"),
                ..default()
            },
            Transform::from_xyz(random_x, random_y, 0.0),
            Enemy {
                direction: Vec2::new(random::<f32>()-0.5, random::<f32>()-0.5)
                .normalize(),
            },
        ));
    }

}

pub fn spawn_stars(
    mut commands: Commands,
    window_query: Query<&Window, With<PrimaryWindow>>,
    asset_server: Res<AssetServer>
){
    let window = window_query.get_single().unwrap();

    for _ in 0..NUMBER_OF_STARS{
        let random_x = random::<f32>() * (window.width() - STAR_SIZE);
        let random_y = random::<f32>() * (window.height() - STAR_SIZE);

        commands.spawn(
            (
                Sprite{
                image: asset_server.load("sprites/star.png"),
                ..default()
            },
            Transform::from_xyz(random_x, random_y, 0.0),
            Star{},
        ));
    }
}

pub fn player_movement(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut player_query: Query<&mut Transform, With<Player>>,
    time: Res<Time>,
){
    if let Ok(mut transform) = player_query.get_single_mut(){
        let mut direction = Vec3::ZERO;

        if keyboard_input.pressed(KeyCode::ArrowLeft)||keyboard_input.pressed(KeyCode::KeyA){
            direction += Vec3::new(-1.0, 0.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::ArrowRight)||keyboard_input.pressed(KeyCode::KeyD){
            direction += Vec3::new(1.0, 0.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::ArrowUp) || keyboard_input.pressed(KeyCode::KeyW) {
            direction += Vec3::new(0.0, 1.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::ArrowDown) || keyboard_input.pressed(KeyCode::KeyS) {
            direction += Vec3::new(0.0, -1.0, 0.0);
        }

        if direction.length() > 0.0{
            direction = direction.normalize();
        }

        transform.translation += direction * PLAYER_SPEED * time.delta_secs();
    }
}

pub fn confine_player_movement(
    mut player_query: Query<&mut Transform, With<Player>>,
    window_query: Query<&Window, With<PrimaryWindow>>
){
    if let Ok(mut player_transform) = player_query.get_single_mut(){
        let window = window_query.get_single().unwrap();

        let half_player_size = PLAYER_SIZE / 2.0;
        let x_min = 0.0 + half_player_size;
        let x_max = window.width()/1.0 - half_player_size;
        let y_min = 0.0 + half_player_size;
        let y_max = window.height()/1.0 -half_player_size;

        let mut translation = player_transform.translation;

        translation.x = translation.x.clamp(x_min, x_max);
        translation.y = translation.y.clamp(y_min, y_max);
        player_transform.translation = translation;

    }
}

pub fn enemy_movement(
    mut enemy_query: Query<(&mut Transform, &Enemy)>, time: Res<Time>){
    for(mut transform, enemy)in enemy_query.iter_mut(){
        let direction = Vec3::new(enemy.direction.x, enemy.direction.y, 0.0);
        transform.translation += direction * ENEMY_SPEED * time.delta_secs();
    }
}

pub fn update_enemy_direction(
    mut enemy_query: Query<(&Transform, &mut Enemy)>,
    window_query: Query<&Window, With<PrimaryWindow>>,
    mut commands: Commands,
    asset_server: Res<AssetServer>,
){
    let window = window_query.get_single().unwrap();
    let half_enemy_size = ENEMY_SIZE / 2.0;
    let x_min = 0.0 + half_enemy_size;
    let x_max = window.width() - half_enemy_size;
    let y_min = 0.0 + half_enemy_size;
    let y_max = window.height() - half_enemy_size;

    for (transform, mut enemy) in enemy_query.iter_mut() {
        let mut direction_changed = false;


        let translation = transform.translation;
        if translation.x < x_min || translation.x > x_max {
            enemy.direction.x *= -1.0;
            direction_changed = true;        
        }
        if translation.y < y_min || translation.y > y_max {
            enemy.direction.y *= -1.0;
            direction_changed = true;        
        }

           
        if direction_changed{

            let sound_effect_1 = "audio/pluck_001.ogg";
            let sound_effect_2 = "audio/pluck_002.ogg";

            let sound_effect:bool = random::<bool>();
            if sound_effect{
                commands.spawn(AudioPlayer::new(
                    asset_server.load(sound_effect_1),
                ));
            }else {
                commands.spawn(AudioPlayer::new(
                    asset_server.load(sound_effect_2),
                ));
            };

        }
    }
}
pub fn confine_enemy_movement(
    mut enemy_query: Query<&mut Transform, With<Enemy>>,
    window_query: Query<&Window, With<PrimaryWindow>>
){

        let window = window_query.get_single().unwrap();

        let half_enemy_size = PLAYER_SIZE / 2.0;
        let x_min = 0.0 + half_enemy_size;
        let x_max = window.width()/1.0 - half_enemy_size;
        let y_min = 0.0 + half_enemy_size;
        let y_max = window.height()/1.0 - half_enemy_size;

        for mut transform in enemy_query.iter_mut(){
            let mut translation = transform.translation;

            translation.x = translation.x.clamp(x_min, x_max);
            translation.y = translation.y.clamp(y_min, y_max);
            transform.translation = translation;
        }
   
}

pub fn enemy_hit_player(
    mut commands: Commands,
    mut game_over_event_writer: EventWriter<GameOver>,
    player_query: Query<(Entity, &Transform), With<Player>>,
    enemy_query: Query<&Transform, With<Enemy>>,
    asset_server: Res<AssetServer>,
    score: Res<Score>
){
    if let Ok*1 = player_query.get_single(){
        for enemy_transform in enemy_query.iter(){
            let distance = player_transform
                            .translation
                            .distance(enemy_transform.translation);
            let player_radius = PLAYER_SIZE /2.0;
            let enemy_radius = ENEMY_SIZE /2.0;
            if distance < player_radius + enemy_radius{
                println!("Enemy hit player! Game Over!!");
                let sound_effect = "audio/explosionCrunch_000.ogg";
                commands.spawn(AudioPlayer::new(
                    asset_server.load(sound_effect),
                ));
                commands.entity(player_entity).despawn();
                game_over_event_writer.send(GameOver { score: score.value});
            }
        }
    }
}

pub fn player_hit_star(
    mut commands: Commands,
    player_query: Query<&Transform, With<Player>>,
    star_query: Query<(Entity, &Transform), With<Star>>,
    asset_server: Res<AssetServer>,
    mut score: ResMut<Score>,
){
    if let Ok(player_transform) = player_query.get_single(){
        for (star_entity, star_transform) in star_query.iter(){
            let distance = player_transform
            .translation
            .distance(star_transform.translation);
 
        if distance < PLAYER_SIZE / 2.0 + STAR_SIZE / 2.0{
            println!("Player hit star!");
            score.value += 1;
            let sound_effect = "audio/laserLarge_000.ogg";
            commands.spawn(AudioPlayer::new(
                asset_server.load(sound_effect),
            ));
            commands.entity(star_entity).despawn();
            }        
        }
    }
}

pub fn update_score(score: Res<Score>){
    if score.is_changed(){
        println!("score: {}", score.value.to_string())
    }
}
pub fn tick_star_spawn_timer(mut star_spawn_timer: ResMut<StarSpawnTimer>, time: Res<Time>) {
    star_spawn_timer.timer.tick(time.delta());
}

pub fn spawn_stars_over_time(
    mut commands: Commands,
    window_query: Query<&Window, With<PrimaryWindow>>,
    asset_server: Res<AssetServer>,
    star_spawn_timer: ResMut<StarSpawnTimer>
){
    if star_spawn_timer.timer.finished(){
        let window = window_query.get_single().unwrap();
        let random_x = random::<f32>() * (window.width() - STAR_SIZE);
        let random_y = random::<f32>() * (window.height() - STAR_SIZE);

        commands.spawn(
            (
                Sprite{
                image: asset_server.load("sprites/star.png"),
                ..default()
            },
            Transform::from_xyz(random_x, random_y, 0.0),
            Star{},
        ));
    }
}
   
pub fn handle_game_over(mut game_over_event_reader: EventReader<GameOver>){
    for event in game_over_event_reader.read(){
        println!("Your final score is : {}", event.score);
    }
}

pub fn exit_game(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut app_exit_event_writer: EventWriter<AppExit>,
){
    if keyboard_input.just_pressed(KeyCode::Escape){
        app_exit_event_writer.send(AppExit::Success);
    }

}

pub fn tick_enemy_spawn_timer(mut enemy_spawn_timer: ResMut<EnemySpawnTimer>, time: Res<Time>){
    enemy_spawn_timer.timer.tick(time.delta());
}

pub fn spawn_enemies_over_time(
    mut commands: Commands,
    window_query: Query<&Window, With<PrimaryWindow>>,
    asset_server: Res<AssetServer>,
    enemy_spawn_timer: Res<EnemySpawnTimer>
){
    let window = window_query.get_single().unwrap();
    if enemy_spawn_timer.timer.finished(){
        let random_x = random::<f32>() * (window.width() - ENEMY_SIZE);
        let random_y = random::<f32>() * (window.height() - ENEMY_SIZE);

        commands.spawn(
            (
                Sprite{
                image: asset_server.load("sprites/ball_red_large.png"),
                ..default()
            },
            Transform::from_xyz(random_x, random_y, 0.0),
            Enemy {
                direction: Vec2::new(random::<f32>()-0.5, random::<f32>()-0.5)
                .normalize(),
            },
        ));
    }

}

pub fn update_high_scores(
    mut game_over_event_reader: EventReader<GameOver>,
    mut high_scores: ResMut<HighScores>,
){
    for event in game_over_event_reader.read(){
        high_scores.scores.push*2;
    }
}

pub fn high_scores_updated(high_scores: Res<HighScores>){
    if high_scores.is_changed(){
        println!("High Scores: {:?}", high_scores)
    }
}

まずは、exit gameシステムから解説していく。

このシステムは、エスケープキーを押した際にアプリを終了させるシステムです。エスケープキーを押すと初期から入っているeventであるAppExitに、AppExit::sendを返し、これが行われると、ウィンドウタブが閉じられてアプリが終了するようになっています。

引数で、ButtonInput<Keycode>と、AppExitを取得しています。イベントであるAppExitはEventWriterを使って可変参照しないと、変えられません。

ButtonInputのif文は、player_movementシステムの動作システムの分からコピペしてきて、キーコードを変えただけです。.pressed()と、just_pressed()では、pressed()はキーが押下されている間、ずっと信号を発し続けますが、just_pressed()では、キーが押下された瞬間だけを認知するようです。今回の場合、アプリを終了させるので、どちらでも問題ないです。

次に、spawn enemies over time システムとtick enemy spawn timerシステムの組み合わせで、敵を時間経過で発生させるシステムについて解説します。

これは、spawn stars over timeとdirectionを設定する以外同じです。

まずは、timerを設定します。

#[derive(Resource)]
pub struct EnemySpawnTimer{
    pub timer: Timer,
}

impl Default for  EnemySpawnTimer {
    fn default() -> Self {
        EnemySpawnTimer{
            timer: Timer::from_seconds(ENEMY_SPAWN_TIME, TimerMode::Repeating),
        }
    }    
}

これにより、timerは、ENEMY_SPAWN_TIMEに到達すると、元に戻るようになっています。

    .init_resource::<EnemySpawnTimer>()

これをメイン関数にいれて読み込んでおきます。

次に、

pub fn tick_enemy_spawn_timer(mut enemy_spawn_timer: ResMut<EnemySpawnTimer>, time: Res<Time>){
    enemy_spawn_timer.timer.tick(time.delta());
}

により、タイマーをフレームごとに起動し、更新します。

そして、

pub fn spawn_enemies_over_time(
    mut commands: Commands,
    window_query: Query<&Window, With<PrimaryWindow>>,
    asset_server: Res<AssetServer>,
    enemy_spawn_timer: Res<EnemySpawnTimer>
){
    let window = window_query.get_single().unwrap();
    if enemy_spawn_timer.timer.finished(){
        let random_x = random::<f32>() * (window.width() - ENEMY_SIZE);
        let random_y = random::<f32>() * (window.height() - ENEMY_SIZE);

        commands.spawn(
            (
                Sprite{
                image: asset_server.load("sprites/ball_red_large.png"),
                ..default()
            },
            Transform::from_xyz(random_x, random_y, 0.0),
            Enemy {
                direction: Vec2::new(random::<f32>()-0.5, random::<f32>()-0.5)
                .normalize(),
            },
        ));
    }

}

enemy_spawn_timer.timer.finished()を用いて、if文を使って敵をランダムな位置に登場させる文を呼び出しています。ifの中身は、spawn enemiesシステムからのコピペです。

 

最後に、ゲームオーバーイベントを追加して、それをトリガーにして、最終スコアを表示する関数について解説します。

今回は、ゲームオーバーというイベントをシステムと分けて管理してみます。システムは基本的には、プレイヤーがレベルアップした時などのフラグを管理するために使う。今回の場合は、ゲームが終了した時の処理を複数書きたいため、それをイベントにした。

  1. システム間の通信:
    • イベントは、異なるシステム間でデータをやり取りするための手段です。これにより、システムがどのように相互作用するかを明確にし、機能を分離することができます。
    • たとえば、プレイヤーがレベルアップしたときに、その情報を他のシステム(UI更新、アニメーション表示など)に通知するためにイベントを使用します。
  2. 機能の抽象化:
    • イベントを使用することで、異なる機能を明確に分離し、どのシステムが何を担当しているのかを理解しやすくします。
    • これにより、新しい機能を追加する際も柔軟性が増し、既存のシステムに影響を与えることなく拡張可能になります。
  3. カップリング:
    • イベントを使うことで、システム間の依存関係が減り、開発が容易になります。これにより、将来的に機能を追加する際の保守性が向上します。

ChatGptより引用。
使い方としては、

#[derive(Event)]
struct LevelUpEvent(Entity);

fn player_level_up(
    mut ev_levelup: EventWriter<LevelUpEvent>,
    query: Query<(Entity, &PlayerXp)>,
) {
    for (entity, xp) in query.iter() {
        if xp.0 > 1000 {
            ev_levelup.send(LevelUpEvent(entity));
        }
    }
}

fn debug_levelups(
    mut ev_levelup: EventReader<LevelUpEvent>,
) {
    for ev in ev_levelup.read() {
        eprintln!("Entity {:?} leveled up!", ev.0);
    }
}

以上の様に、イベントの宣言、EventWriter, EventReader, の3つの組み合わせで処理を分割するようになっている。

bevy-cheatbook.github.io

まず、

#[derive(Event)]
pub struct GameOver{
    pub score: u32,
}

上記のように、#[derive(Event)]を用いて、

イベントを宣言します。

今回はゲームオーバー時のスコアを記録したいので、scoreというフィールドも宣言しました。例のコードでは、#[derive(Event)]がありませんでしたが、それを書かないと、エラーがでてコンパイルができません。

それを、メイン関数にてappに登録します。

    .add_event::<GameOver>()

そして、enemy hit player関数で、呼び出し、書き込みます

pub fn enemy_hit_player(
    mut commands: Commands,
    mut game_over_event_writer: EventWriter<GameOver>,
    player_query: Query<(Entity, &Transform), With<Player>>,
    enemy_query: Query<&Transform, With<Enemy>>,
    asset_server: Res<AssetServer>,
    score: Res<Score>
){
    if let Ok*3 = player_query.get_single(){
        for enemy_transform in enemy_query.iter(){
            let distance = player_transform
                            .translation
                            .distance(enemy_transform.translation);
            let player_radius = PLAYER_SIZE /2.0;
            let enemy_radius = ENEMY_SIZE /2.0;
            if distance < player_radius + enemy_radius{
                println!("Enemy hit player! Game Over!!");
                let sound_effect = "audio/explosionCrunch_000.ogg";
                commands.spawn(AudioPlayer::new(
                    asset_server.load(sound_effect),
                ));
                commands.entity(player_entity).despawn();
                game_over_event_writer.send(GameOver { score: score.value});
            }
        }
    }
}

前回までのコードに、引数が二つ加わり、

衝突した際の処理が一つ増えました。

    mut game_over_event_writer: EventWriter<GameOver>,    score: Res<Score>

この二つの引数は、先の、GameOverの値を可変参照で取得します。今回は、イベントの送信をしたいので、EventWriterを使います。Scoreは何だったかというと、scoreというインスタンスの中に、u32型のvalueというフィールドを持ちます。で、valueは、スターと衝突する度に1点加算されるものでした。

最後の処理は、GameOverイベントに、衝突した際のscore.valueを、GameOverの中の、scoreフィールドに送信する処理である

この、GameOverは初期化処理をしていなかったので、
.send(GameOver { score: score.value});
このような記述となっている。

これにより、GameOverのイベントが送信された。game overのイベントにより呼び出される関数は二つある。

pub fn handle_game_over(mut game_over_event_reader: EventReader<GameOver>){
    for event in game_over_event_reader.read(){
        println!("Your final score is : {}", event.score);
    }
}
pub fn update_high_scores(
    mut game_over_event_reader: EventReader<GameOver>,
    mut high_scores: ResMut<HighScores>,
){
    for event in game_over_event_reader.read(){
        high_scores.scores.push*4;
    }
}

この二つである。

handle_game_overは、GameOverイベントを受け、そのなかの、scoreフィールドを、表示するようにしています。

 

update_high_scoresは同様の関数ですが、リソース、HighScoresをVec型で宣言しています。

#[derive(Resource, Debug)]
pub struct HighScores{
    pub scores: Vec<(String, u32)>
}

impl Default for HighScores{
    fn default() -> Self {
        HighScores{ scores: Vec::new() }
    }
}

設定しています。Vec型は.push*5でaとbを配列の末尾に追加するという意味です。

 

qiita.comDebugというオプションがついていますが、それは、あとで、説明します。

兎に角この関数は、GameOverになった時、HighScoresの中に、playerという文字列とその時の点数をリストに加えるという事です。

pub fn high_scores_updated(high_scores: Res<HighScores>){
    if high_scores.is_changed(){
        println!("High Scores: {:?}", high_scores)
    }
}

そして、update_high_scoresに依存した関数として、high_scores_updated関数があります。これでは、先ほど変更したリソースのHighScoresの変更を参照して、high scoresを表示するという事になっています。 

普段は、println!の中に{}を入れますが、今回は{:?}が入っています。 これはHighScoresを宣言した時に#[derive(Resource, Debug)]を指定したから使えています。

以上の画像の様に、Debugを追加しないと{:?}でエラーが出ます。

{:?}これは、フォーマットありで出力するという意味で、

このような処理結果が出ます。

Vec型の変数をフォーマットなしで出力するには、

        if let Some(first_score) = high_scores.scores.get(0) {
            // プレイヤー名とスコアを個別に出力
            println!("Player: {}, Score: {}", first_score.0, first_score.1);
        } else {
            println!("No scores available.");
        }

このように取得しないといけません。話がそれましたが、この関数では、取得したスコアをフォーマットありで表示する操作を行います。

説明した3つのシステムにより、ゲームオーバーイベントが発生した際に、最終スコアを表示するシステムを作ることが出来ました。

イベントの宣言、appへのイベントの追加、ゲームオーバーイベントを送信する関数、ゲームオーバーイベントを読み込んで動作する関数、この4つの組み合わせによって、ゲームオーバー時の動作を作ることが出来た。

 

検証

以下の画像は、ビルド後約10秒後の実行結果のwindowのスクリーンショットである。

初期値は、3こだったエネミーエンティティが10秒後には5つに増えている。

また、星が0.3秒で追加されることを考えると、その湧き時間には、違いがあることも確認できている。

以下の画像は、実行後すぐにエスケープキーを押した時の端末のスクリーンショットである。

これにより、エスケープキーを押した際、エラー無くゲームが終了できていることが分かる。なお、この際には、ゲームオーバーイベントは送信されないので、最終スコアは表示されていない。

以下の画像は、エネミーエンティティと接触し、ゲームオーバーイベントを発生させてから、エスケープを押し、ゲームを終了させたときの端末のスクリーンショットである。

これをみると final scoreの表示もなされている。それは、最後のスコア(score: 4)と同じ値である。そして、フォーマットありの、Vec型のHighScoresという変数もフォーマットありで出力されている。それも、scoresに、playerと4が格納されているため、最後のスコアと同じ値になっている。

これにより、イベントの宣言、appへのイベントの追加、ゲームオーバーイベントを送信する関数、ゲームオーバーイベントを読み込んで動作する関数が全て動作していることが分かった。

以上により、今回の目的は満たされた。

*1:player_entity, player_transform

*2:"player".to_string(), event.score

*3:player_entity, player_transform

*4:"player".to_string(), event.score

*5:a, b