Result画面(結果表示)の作成
リザルト画面は、今回のスコア表示、これまでのスコア履歴(ランキング形式)、およびタイトル画面へ戻るためのリトライボタンで構成されます。まずは、src/result/mod.rs を作成しましょう。
リザルト画面のUIを初期化するシステムを実装します。
#![allow(unused)]
fn main() {
use crate::playing::{Score, ScoreList};
use crate::state;
use bevy::prelude::*;
#[derive(Component)]
struct RetryButton;
#[derive(Component)]
struct ResultText;
fn setup_result_ui(commands: &mut Commands, asset_server: &AssetServer, score_list: &ScoreList) {
let mut score_score_list = String::from("Ranking\n");
let mut sorted_score_list = score_list.0.clone();
sorted_score_list.sort_by(|a, b| a.score().partial_cmp(&b.score()).expect("NaN in Score"));
sorted_score_list
.iter()
.rev()
.map(|score| score.score())
.enumerate()
.for_each(|(rank, score)| {
score_score_list.push_str(&format!("No. {}: {:.2}\n", rank + 1, score))
});
commands.spawn((
DespawnOnExit(state::GameState::Result),
Node {
width: percent(100),
height: percent(100),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
flex_direction: FlexDirection::Column,
row_gap: px(10.0),
..default()
},
children![
(
Text::new(score_list.0.last().unwrap_or(&Score::default()).to_string()),
ResultText,
TextFont {
font: asset_server
.load("embedded://invader_tutorial/fonts/NotoSansJP-Bold.ttf"),
font_size: 40.0,
..default()
},
TextLayout::new_with_justify(Justify::Center),
TextColor::WHITE,
),
(
Button,
RetryButton,
Node {
width: percent(20),
height: percent(10),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
border_radius: BorderRadius::MAX,
..default()
},
BorderColor::all(Color::WHITE),
BackgroundColor(Color::WHITE),
children![(
Text::new("Retry"),
TextFont {
font: asset_server.load(
"embedded://invader_tutorial/fonts/NotoSansJP-Bold.ttf"
),
font_size: 40.0,
..default()
},
TextLayout::new_with_justify(Justify::Center),
TextColor::BLACK,
)]
),
(
Text::new(score_score_list),
ResultText,
TextFont {
font: asset_server
.load("embedded://invader_tutorial/fonts/NotoSansJP-Bold.ttf"),
font_size: 40.0,
..default()
},
TextLayout::new_with_justify(Justify::Center),
TextColor::WHITE,
),
],
));
}
fn setup_result_screen(
mut commands: Commands,
asset_server: Res<AssetServer>,
score_list: Res<ScoreList>,
) {
// spawn a camera
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 30.0, 0.0).looking_at(Vec3::ZERO, Vec3::Z),
DespawnOnExit(state::GameState::Result),
));
setup_result_ui(&mut commands, &asset_server, &score_list);
}
}
リトライボタンが押された際にタイトル画面(Home)へ戻るためのシステムを作成します。
#![allow(unused)]
fn main() {
type RetryButtonInputs = (Changed<Interaction>, With<RetryButton>);
fn update_retry_button(
mut query: Query<(&Interaction, &mut BackgroundColor), RetryButtonInputs>,
mut game_state: ResMut<NextState<state::GameState>>,
) {
for (interaction, mut background_color) in query.iter_mut() {
match interaction {
Interaction::Pressed => {
background_color.0 = Color::srgb(0.5, 0.5, 0.5);
game_state.set(state::GameState::Home);
}
Interaction::Hovered => {
background_color.0 = Color::srgb(0.7, 0.7, 0.7);
}
Interaction::None => {
background_color.0 = Color::srgb(0.9, 0.9, 0.9);
}
}
}
}
}
これらのシステムをまとめて管理する ResultPlugin を作成します。
#![allow(unused)]
fn main() {
pub struct ResultPlugin;
impl Plugin for ResultPlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnEnter(state::GameState::Result), setup_result_screen)
.add_systems(
Update,
update_retry_button.run_if(in_state(state::GameState::Result)),
);
}
}
}
最後に、この ResultPlugin を main.rs に登録します。これですべての画面の実装が揃いました! 最終的な main.rs の内容は以下の通りです。
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use bevy::prelude::*;
mod home;
mod state;
mod playing;
mod result;
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins);
bevy::asset::embedded_asset!(app, "fonts/NotoSansJP-Bold.ttf");
bevy::asset::embedded_asset!(app, "img/invader_background.png");
app.add_plugins(state::GameStatePlugin)
.add_plugins(home::HomePlugin)
.add_plugins(playing::PlayingPlugin)
.add_plugins(result::ResultPlugin)
.run();
}
これで完成です。お疲れ様でした。