Skip to content

Commit

Permalink
Starting a new game
Browse files Browse the repository at this point in the history
Ideally when we click the “New Game” button it will start a new game.

Inside of `[ui.rs](http://ui.rs)` in our event handler, we don’t have access to `Commands` so we can use the context argument to get a mutable reference to the world instead, which will let us `insert_resource` with the state we want to move to.

In this case, we want to move into the `Playing` state to start the game.

```rust
let on_click_new_game =
    OnEvent::new(|ctx, event| match event.event_type {
        EventType::Click(..) => {
            let mut world =
                ctx.get_global_mut::<World>().unwrap();
            world.insert_resource(NextState(
                GameState::Playing,
            ));
        }
        _ => {}
    });
```

This works, at least for getting into the `Playing` state. There’s still a bug where when we hit “New Game” a second time, it throws us into the old game.

[Screen Recording 2022-04-23 at 7.47.01 AM.mov](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e4c6325e-d898-4846-8e67-2da83d8423b9/Screen_Recording_2022-04-23_at_7.47.01_AM.mov)

We need a way to reset the game when we transition into the Playing state.

In `lib.rs` we can create a `game_reset` system.

```rust
pub fn reset_game(
    mut commands: Commands,
    mut snake: ResMut<Snake>,
    positions: Query<Entity, With<Position>>,
    mut last_pressed: ResMut<controls::Direction>,
    mut food_events: EventWriter<NewFoodEvent>,
) {
    for entity in positions.iter() {
        commands.entity(entity).despawn_recursive();
    }

    food_events.send(NewFoodEvent);
    *snake = Default::default();
    *last_pressed = Default::default();
}
```

The game reset system removes the snake and the apples from the board, sends a new food event, resets the snake, and resets the last user input entered.

Removing any `Entity` that has a `Position` component will remove all snake segments and all apples. We can do this with the entity id and calling `despawn_recursive` to make sure we remove everything.

The `snake` and `last_pressed` variables already know what type they contain, so we can use `Default::default` to trigger the default trait implementation for each type.

After setting up the system, we need to trigger it when we enter the `GameState::Playing` state. iyes_loopless helps us out here with a `add_enter_system` function on our Bevy App.

```rust
.add_enter_system(&GameState::Playing, reset_game)
```

We can also now remove the spawning logic in `board.rs` for the snake and the apples, because the game_reset system is handling the apple spawn trigger while `tick` is handling rendering the snake itself as it starts moving.

```rust
for segment in snake.segments.iter() {
    commands.add({
        SpawnSnakeSegment { position: *segment }
    });
}
commands.add(SpawnApple {
    position: Position { x: 15, y: 15 },
})
```

We can also remove the `snake` reference in the arguments for the `spawn_board` system since we aren’t using it anymore.

```rust
pub fn spawn_board(mut commands: Commands) {
```

We can now restart the game as much as we want!
  • Loading branch information
ChristopherBiscardi committed Apr 23, 2022
1 parent 3385e92 commit 346d321
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 20 deletions.
16 changes: 2 additions & 14 deletions src/board.rs
@@ -1,7 +1,7 @@
use bevy::{ecs::system::Command, prelude::*};
use itertools::Itertools;

use crate::{colors::COLORS, food::Food, snake::Snake};
use crate::{colors::COLORS, food::Food};

const TILE_SIZE: f32 = 30.0;
const TILE_SPACER: f32 = 0.0;
Expand Down Expand Up @@ -39,10 +39,7 @@ pub struct Position {
pub y: u8,
}

pub fn spawn_board(
mut commands: Commands,
snake: Res<Snake>,
) {
pub fn spawn_board(mut commands: Commands) {
let board = Board::new(20);

commands
Expand Down Expand Up @@ -83,15 +80,6 @@ pub fn spawn_board(
}
})
.insert(board);

for segment in snake.segments.iter() {
commands.add({
SpawnSnakeSegment { position: *segment }
});
}
commands.add(SpawnApple {
position: Position { x: 15, y: 15 },
})
}

pub struct SpawnSnakeSegment {
Expand Down
16 changes: 16 additions & 0 deletions src/lib.rs
Expand Up @@ -130,3 +130,19 @@ pub fn tick(
}
}
}

pub fn reset_game(
mut commands: Commands,
mut snake: ResMut<Snake>,
positions: Query<Entity, With<Position>>,
mut last_pressed: ResMut<controls::Direction>,
mut food_events: EventWriter<NewFoodEvent>,
) {
for entity in positions.iter() {
commands.entity(entity).despawn_recursive();
}

food_events.send(NewFoodEvent);
*snake = Default::default();
*last_pressed = Default::default();
}
5 changes: 3 additions & 2 deletions src/main.rs
Expand Up @@ -2,8 +2,8 @@ use bevy::prelude::*;
use iyes_loopless::prelude::*;
use snake::{
board::spawn_board, controls::ControlsPlugin,
food::FoodPlugin, snake::Snake, tick, ui::UiPlugin,
GameState, STARTING_GAME_STATE,
food::FoodPlugin, reset_game, snake::Snake, tick,
ui::UiPlugin, GameState, STARTING_GAME_STATE,
};
use std::time::Duration;

Expand All @@ -24,6 +24,7 @@ fn main() {
.add_loopless_state(STARTING_GAME_STATE)
.add_startup_system(setup)
.add_startup_system(spawn_board)
.add_enter_system(&GameState::Playing, reset_game)
.add_stage_before(
CoreStage::Update,
"snake_tick",
Expand Down
12 changes: 8 additions & 4 deletions src/ui.rs
Expand Up @@ -2,10 +2,10 @@ use bevy::{
app::AppExit,
prelude::{
AssetServer, Commands, EventWriter, Plugin, Res,
ResMut,
ResMut, World,
},
};
use iyes_loopless::state::CurrentState;
use iyes_loopless::state::{CurrentState, NextState};
use kayak_ui::{
bevy::{
BevyContext, BevyKayakUIPlugin, FontMapping,
Expand Down Expand Up @@ -108,9 +108,13 @@ fn GameMenu() {
};

let on_click_new_game =
OnEvent::new(|_, event| match event.event_type {
OnEvent::new(|ctx, event| match event.event_type {
EventType::Click(..) => {
dbg!("new game!");
let mut world =
ctx.get_global_mut::<World>().unwrap();
world.insert_resource(NextState(
GameState::Playing,
));
}
_ => {}
});
Expand Down

0 comments on commit 346d321

Please sign in to comment.