錆でゲームを作る-パート9 -メインメニュー画面


今日、我々はbeisでUISについて学ぶつもりです.私たちの主な目標は、2つのボタンは、1つのゲームを起動し、別のアプリケーションを終了するには、メインメニューで終わることです.この記事では、UI要素を画面に表示する方法、およびボタンイベントを聞く方法について説明します.

ノードとスタイル
BevyのUIをHTMLに簡単に比較することができました:あなたは、要素/ノードの階層を作成し、いくつかのスタイルを適用します、そして、Bevyのエンジンはあなたが求めたものをレンダリングするのに注意します.いくつかの「バンドル」は、あなたのUIを構築するのに役立つようにbevyによって公開されます.
  • NodeBar :基本ノード
  • fn root(materials: &Res<MenuMaterials>) -> NodeBundle {
        NodeBundle {
            style: Style {
                size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
                justify_content: JustifyContent::Center,
                align_items: AlignItems::Center,
                ..Default::default()
            },
            material: materials.root.clone(),
            ..Default::default()
        }
    }
    
  • ボタンをレンダリングするには
  • fn button(materials: &Res<MenuMaterials>) -> ButtonBundle {
        ButtonBundle {
            style: Style {
                size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
                justify_content: JustifyContent::Center,
                align_items: AlignItems::Center,
                ..Default::default()
            },
            material: materials.button.clone(),
            ..Default::default()
        }
    }
    
  • TextBar :テキストをレンダリングするには
  • fn button_text(asset_server: &Res<AssetServer>, materials: &Res<MenuMaterials>, label: &str) -> TextBundle {
        return TextBundle {
            style: Style {
                margin: Rect::all(Val::Px(10.0)),
                ..Default::default()
            },
            text: Text::with_section(
                label,
                TextStyle {
                    font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                    font_size: 30.0,
                    color: materials.button_text.clone(),
                },
                Default::default(),
            ),
            ..Default::default()
        };
    }
    
    これらの要素の各々はstyle ノードのレンダリング方法を表すプロパティ.
    このstyle プロパティは、CSS . 他にも、
  • マージンとパディング:UI要素の周囲にスペースを追加するには
  • PositionRank型:相対的または絶対的な位置決めの場合
  • 大部分のflexbox プロパティ:Bevyのレンダリングエンジンは、Flexbox
  • この2つのノードもあります
    fn border(materials: &Res<MenuMaterials>) -> NodeBundle {
        NodeBundle {
            style: Style {
                size: Size::new(Val::Px(400.0), Val::Auto),
                border: Rect::all(Val::Px(8.0)),
                ..Default::default()
            },
            material: materials.border.clone(),
            ..Default::default()
        }
    }
    
    fn menu_background(materials: &Res<MenuMaterials>) -> NodeBundle {
        NodeBundle {
            style: Style {
                size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
                align_items: AlignItems::Center,
                justify_content: JustifyContent::Center,
                flex_direction: FlexDirection::ColumnReverse,
                padding: Rect::all(Val::Px(5.0)),
                ..Default::default()
            },
            material: materials.menu.clone(),
            ..Default::default()
        }
    }
    

    テキストスタイル
    The TextBundle , に加えてstyle property 1 iプロパティを指定するtext にはstyle プロパティ.これは、テキストのフォントサイズ、色、フォントを変更する可能性を公開します.
    上記のテキストノードを見てみると、Res<AssetServer> . このユーティリティは、画像やフォントなどの非錆リソースを読み込むことができます.
    それを使用するには、我々はプロジェクトのルートに新しいフォルダを作成し、それを命名assets . リソースを読み込むためにアセットサーバーを使用する場合は、このフォルダーに関連するリソースのパスを渡します.EX :
    asset_server.load("fonts/FiraSans-Bold.ttf")
    


    ノードとボタンにはmaterial プロパティ.このプロパティは、以前に我々のプレーヤーとマップに追加された材料と同じように動作します.メニューの開発を容易にするために、これらの色の定義をリソースに集約することができます.
    struct MenuMaterials {
        root: Handle<ColorMaterial>,
        border: Handle<ColorMaterial>,
        menu: Handle<ColorMaterial>,
        button: Handle<ColorMaterial>,
        button_hovered: Handle<ColorMaterial>,
        button_pressed: Handle<ColorMaterial>,
        button_text: Color,
    }
    
    impl FromWorld for MenuMaterials {
        fn from_world(world: &mut World) -> Self {
            let mut materials = world.get_resource_mut::<Assets<ColorMaterial>>().unwrap();
            MenuMaterials {
                root: materials.add(Color::NONE.into()),
                border: materials.add(Color::rgb(0.65, 0.65, 0.65).into()),
                menu: materials.add(Color::rgb(0.15, 0.15, 0.15).into()),
                button: materials.add(Color::rgb(0.15, 0.15, 0.15).into()),
                button_hovered: materials.add(Color::rgb(0.25, 0.25, 0.25).into()),
                button_pressed: materials.add(Color::rgb(0.35, 0.75, 0.35).into()),
                button_text: Color::WHITE,
            }
        }
    }
    
    このリソースをインスタンス化するには、まだbevyに指示する必要があります.
    // on our AppBuilder
    .init_resource::<MenuMaterials>()
    

    ノード階層の定義
    UI要素を追加することができますAppBuilder.spawn_bundle と子ノードをwith_children メソッド:
    enum MenuButton {
        Play,
        Quit,
    }
    
    fn setup(
        mut commands: Commands,
        asset_server: Res<AssetServer>,
        materials: Res<MenuMaterials>,
    ) {
        commands.spawn_bundle(UiCameraBundle::default());
    
        commands
            .spawn_bundle(root(&materials))
            .with_children(|parent| {
                // left vertical fill (border)
                parent
                    .spawn_bundle(border(&materials))
                    .with_children(|parent| {
                        // left vertical fill (content)
                        parent
                            .spawn_bundle(menu_background(&materials))
                            .with_children(|parent| {
                                parent.spawn_bundle(button(&materials))
                                    .with_children(|parent| {
                                        parent.spawn_bundle(button_text(&asset_server, &materials, "New Game"));
                                    })
                                    .insert(MenuButton::Play);
                                parent.spawn_bundle(button(&materials))
                                    .with_children(|parent| {
                                        parent.spawn_bundle(button_text(&asset_server, &materials, "Quit"));
                                    })
                                    .insert(MenuButton::Quit);
                            });
                    });
            });
    }
    

    配線
    すべてを配線するには、新しい錆モジュールをmain_menu , にはMainMenuPlugin .
    pub struct MainMenuPlugin;
    
    impl Plugin for MainMenuPlugin {
        fn build(&self, app: &mut AppBuilder) {
            app.init_resource::<MenuMaterials>()
                .add_system_set(
                    SystemSet::on_enter(AppState::MainMenu)
                        .with_system(cleanup.system())
                        .with_system(setup.system()),
                )
                .add_system_set(SystemSet::on_exit(AppState::MainMenu).with_system(cleanup.system()));
        }
    }
    
    // main.rs
    mod main_menu;
    use main_menu::MainMenuPlugin;
    
    // ...
    
    .add_plugin(MainMenuPlugin)
    
    ゲームの実行


    相互作用に異なるスタイルを適用する
    システムでボタンの外観を変更することができます.
    // MainMenuPlugin
    .add_system(button_system.system())
    
    // system implementation
    fn button_system(
        materials: Res<MenuMaterials>,
        mut buttons: Query<
            (&Interaction, &mut Handle<ColorMaterial>),
            (Changed<Interaction>, With<Button>),
        >
    ) {
        for (interaction, mut material) in buttons.iter_mut() {
            match *interaction {
                Interaction::Clicked => *material = materials.button_pressed.clone(),
                Interaction::Hovered => *material = materials.button_hovered.clone(),
                Interaction::None => *material = materials.button.clone(),
            }
        }
    }
    
    この方法では、私たちのボタンが色を変更する必要がありますときにホバリングし、緑色に押されたときに押された.


    ボタンをクリックする
    ボタンスタイルを変更したときと同じように、クリック時にシステムボタンをクリックすることで反応できます.
    // main menu plugin
    .add_system(button_press_system.system())
    
    // System implementation
    fn button_press_system(
        buttons: Query<(&Interaction, &MenuButton), (Changed<Interaction>, With<Button>)>,
        mut state: ResMut<State<AppState>>,
        mut exit: EventWriter<AppExit>
    ) {
        for (interaction, button) in buttons.iter() {
            if *interaction == Interaction::Clicked {
                match button {
                    MenuButton::Play => state
                        .set(AppState::InGame)
                        .expect("Couldn't switch state to InGame"),
                    MenuButton::Quit => exit.send(AppExit),
                };
            }
        }
    }
    
    このシステムが動くために、我々はそれを確実にする必要がありますMenuButton コンポーネントは、我々のボタンに加えられます.このシステムは、MenuButton それは私たちのエンティティに追加されました.
    ここで、プレスPlay Game 新しいゲームを開始し、プレスQuit ゲームを終了する必要があります.

    コードの最終ビット
    我々は今、新しいゲームを開始するハンドルをメニューがあるので、我々はmain_menu_controls で定義されたシステムmain.rs . 代わりに、我々は新しいback_to_main_menu_controls 我々の中でgame モジュール
    // GamePlugin
    .add_system_set(SystemSet::on_update(AppState::InGame).with_system(back_to_main_menu_controls.system()))
    
    // System implementation
    fn back_to_main_menu_controls(mut keys: ResMut<Input<KeyCode>>, mut app_state: ResMut<State<AppState>>) {
        if *app_state.current() == AppState::InGame {
            if keys.just_pressed(KeyCode::Escape) {
                app_state.set(AppState::MainMenu).unwrap();
                keys.reset(KeyCode::Escape);
            }
        }
    }
    
    すべてのコードが利用可能ですhere . ゲームの最新バージョンをプレイすることができますhere .