Gerador de Comidas
Nosso próximo passo é começarmos um sistema que gere comidas de forma aleatória pela grade. O primeiro passo é definir qual sera a cor da comida. Como pretendemos fazer um jogo multiplayer, não faz sentido termos comidas coloridas, já que estas serão dos jogadores, sendo assim podemos criar um módulo chamado food
e adicionar a constante const FOOD_COLOR: Color = Color::rgb(1.0, 1.0, 1.0)
. Próximo passo é criamos um componente chamado Food
para representar a comida:
// food.rs
#[derive(Component)]
pub struct Food;
Próximo passo é criarmos um sistema que gera uma comida em um local aleatório da grade. Como este sistema utiliza aleatoriedade, podemos utilizar uma biblioteca de property testing
semelhante a proptest do python, a propcheck do Elixir e a quickcheck do Haskell, chamada proptest
para gerar centenas de cenários de teste. Para isso, adicionamos proptest = "1.0.0"
como uma dev-dependencies
no Cargo.toml e para utilizarmos basta utilizar a macro proptest!
e determinar os valores a serem executados (ou quantidade de cenários) como argumento da função de teste como em _execution in 0u32..1000
:
#[cfg(test)]
mod test {
use crate::components::Position;
use super::*;
use proptest::prelude::*;
proptest!{
#[test]
fn spawns_food_inplace(_execution in 0u32..1000) {
// Setup app
let mut app = App::new();
// Add startup system
app.add_startup_system(spawn_system);
// Run systems
app.update();
let mut query = app.world.query_filtered::<&Position, With<Food>>();
assert_eq!(query.iter(&app.world).count(), 1);
query.iter(&app.world).for_each(|position| {
let x = position.x;
let y = position.y;
assert!(x >= 0 && x as i32 <= (GRID_WIDTH -1) as i32);
assert!(y >= 0 && y as i32 <= (GRID_HEIGHT -1) as i32);
})
}
}
}
A vantagem de um proptest é que ele permite executar diversos cenários e podemos definir regras de limite para falha, executando centenas de cenários em poucos segundos. Para este teste passar, precisamos implementar a função spawn_system
para o módulo food
:
// food.rs
pub fn spawn_system(mut commands: Commands) {
commands
.spawn_bundle(SpriteBundle {
sprite: Sprite {
color: FOOD_COLOR,
..default()
},
..default()
})
.insert(Food)
.insert(Position {
x: (random::<u16>() % GRID_WIDTH) as i16,
y: (random::<u16>() % GRID_HEIGHT) as i16,
})
.insert(Size::square(0.8));
}
O próximo passo é adicionar o sistema a App
na função main
, porém este sistema tem uma pegadinha. Como não queremos que o sistema gere uma nova comdia para cada frame, precisamos definir um tempo de intervalo para as comidas serem geradas. Como este cenário de executar uma função somente a cada x segundos é muito comum no desenvolvimento de jogos a Bevy nos disponibiliza a struct FixedTimestep
que nos permite definir um passo (step
) em segundos, que será usada com a função with_run_criteria
:
// main.rs
pub mod food;
fn main() {
App::new()
.insert_resource(WindowDescriptor {
title: "Snake Game".to_string(),
width: 500.0,
height: 500.0,
..default()
}) // <--
.add_startup_system(setup_camera)
.add_startup_system(snake::spawn_system)
.add_plugins(DefaultPlugins)
.add_system(snake::movement_system)
.add_system_set(
SystemSet::new()
.with_run_criteria(FixedTimestep::step(1.0)) // <-- Pegadinha
.with_system(food::spawn_system), // <-- Sistema
) // <--
.add_system_set_to_stage(
CoreStage::PostUpdate,
SystemSet::new()
.with_system(grid::position_translation)
.with_system(grid::size_scaling),
)
.run();
}
Próximo passo será melhorar o movimento da cabeça da cobra, tornando ele mais lento e cadenciado.