In the GameState::Menu game state, we need to show our wonderful new menu.
In the GameState::Playing game state, we need to hide our wonderful UI.
Kayak requires that we bind to resources we want to expose to the UI before we can use it to show/hide the UI.
We need to add a new resource that is bind(GameState::Menu). This is the binding that will update over time.
Add a new system called bind_gamestate that accesses the CurrentState and the Binding for GameState.
We can use is_changed() to detect if the CurrentState has changed, and set the binding to the current GameState value. This will continually update our binding when the CurrentState changes.
impl Plugin for UiPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_plugin(BevyKayakUIPlugin)
.insert_resource(bind(GameState::Menu))
.add_startup_system(game_ui)
.add_system(bind_gamestate);
}
}
pub fn bind_gamestate(
state: Res<CurrentState<GameState>>,
binding: Res<Binding<GameState>>,
) {
if state.is_changed() {
binding.set(state.0);
}
}
You might think that we could insert this resource by querying the CurrentState in game_ui but unfortunately, the iyes_loopless GameState isn’t accessible in startup systems (our custom stage only inserts it after the startup systems have run), so our game_ui startup system can’t access it.
If we tried to, with code that looks like this:
pub fn game_ui(
mut commands: Commands,
mut font_mapping: ResMut<FontMapping>,
asset_server: Res<AssetServer>,
gamestate: Res<CurrentState<GameState>>
) {...}
Then we would get this error at runtime: Resource requested by snake::ui::game_ui does not exist: iyes_loopless::state::CurrentState<snake::GameState>
❯ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.08s
Running `target/debug/snake`
2022-04-23T12:50:49.775340Z INFO bevy_render::renderer: AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: DiscreteGpu, backend: Metal }
thread 'Compute Task Pool (2)' panicked at 'Resource requested by snake::ui::game_ui does not exist: iyes_loopless::state::CurrentState<snake::GameState>', /Users/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.7.0/src/system/system_param.rs:319:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at 'task has failed', /Users/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/async-task-4.2.0/src/task.rs:425:45
thread 'main' panicked at 'Task thread panicked while executing.: Any { .. }', /Users/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_tasks-0.7.0/src/task_pool.rs:77:21
We do want to sync up the state that we’re actually starting the game with and the state that we initialize the binding with though.
We can create a public const for this in lib.rs.
pub const STARTING_GAME_STATE: GameState = GameState::Menu;
and then use it in our add_loopless_state in main.rs.
.add_loopless_state(STARTING_GAME_STATE)
as well as our bind in ui.rs.
.insert_resource(bind(STARTING_GAME_STATE))
Connecting our widget
In our GameMenu widget, we can create a new boolean value called show_menus. This will control whether or not our menus will show.
Using context we can query the world for the Binding<GameState> and clone the binding out for our own use. This is similar to how we sent the app exit event.
The Binding type is a struct that holds an id and an Arc wrapped value, which means cloning is cheap.
Then we need to bind the relevant value to this widget so that we can react to updates. This is done with context.bind.
We can .get the value from the binding and check it against Menu to determine if the game is in the Menu state or not.
let show_menus = {
let gamestate = context
.query_world::<Res<Binding<GameState>>, _, _>(
|state| state.clone(),
);
context.bind(&gamestate);
gamestate.get() == GameState::Menu
};
We can wrap the If component around our entire menu and set the condition to our show_menus boolean.
rsx! {
<If condition={show_menus}>
<Background
styles={Some(container_styles)}
>
...
</Background>
</If>
}
A STARTING_GAME_STATE of GameState::Menu will now show us the menu.

While a STARTING_GAME_STATE of GameState::Playing will let us play the game, then transition to showing the menu when we hit a wall.