Rust: 如何用bevy写一个贪吃蛇(下)
接上篇继续,贪吃蛇游戏中食物是不能缺少的,先来解决这个问题:
一、随机位置生成食物
use rand::prelude::random; ... struct Food; //随机位置生成食物 fn food_spawner( //<-- mut commands: Commands, materials: Res<Materials>, ) { commands .spawn_bundle(SpriteBundle { material: materials.food_material.clone(), ..Default::default() }) .insert(Food) .insert(Position { x: (random::<f32>() * CELL_X_COUNT as f32) as i32, y: (random::<f32>() * CELL_Y_COUNT as f32) as i32, }) .insert(Size::square(0.6)); }
然后要在Meterials中,加一项food_material成员
... struct Materials { head_material: Handle<ColorMaterial>, food_material: Handle<ColorMaterial>, // <-- }
fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) { let mut camera = OrthographicCameraBundle::new_2d(); camera.transform = Transform::from_translation(Vec3::new(0.0, 0.0, 5.0)); commands.spawn_bundle(camera); commands.insert_resource(Materials { head_material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()), food_material: materials.add(Color::rgb(1.0, 0.0, 1.0).into()), // <-- 初始化时,指定食物为紫色 }); } ... fn main() { App::build() ... .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(2.0)) //<--2秒生成1次食物 .with_system(food_spawner.system()), ) ... .add_plugins(DefaultPlugins) .add_plugin(DebugLinesPlugin) .run(); }
二、让蛇头能自行前进
到目前为止,必须按方向键蛇头才能移动,大家都玩过这个游戏,不按键时,蛇头应该保持原来的方向继续前进,只到有按键改变运动方向,下面就来实现这一效果:
#[derive(PartialEq, Copy, Clone)] enum Direction { Left, Up, Right, Down, } impl Direction { fn opposite(self) -> Self { match self { Self::Left => Self::Right, Self::Right => Self::Left, Self::Up => Self::Down, Self::Down => Self::Up, } } } struct SnakeHead { direction: Direction, }
添加了Direction枚举以记录蛇头运动的方向,并在SnakeHead中添加了direction成员,初始化时,默认蛇头向上运动
.insert(SnakeHead { direction: Direction::Up, })
按键处理和位置移动时,也要做相应调整:
/** *方向键改变运动方向 */ fn snake_movement_input(keyboard_input: Res<Input<KeyCode>>, mut heads: Query<&mut SnakeHead>) { if let Some(mut head) = heads.iter_mut().next() { let dir: Direction = if keyboard_input.pressed(KeyCode::Left) { Direction::Left } else if keyboard_input.pressed(KeyCode::Down) { Direction::Down } else if keyboard_input.pressed(KeyCode::Up) { Direction::Up } else if keyboard_input.pressed(KeyCode::Right) { Direction::Right } else { head.direction }; //蛇头不能向反方向走,否则就把自己的身体给吃了 if dir != head.direction.opposite() { head.direction = dir; } } } /** * 根据运动方向,调整蛇头在网格中的位置 */ fn snake_movement(mut heads: Query<(&mut Position, &SnakeHead)>) { if let Some((mut head_pos, head)) = heads.iter_mut().next() { match &head.direction { Direction::Left => { head_pos.x -= 1; } Direction::Right => { head_pos.x += 1; } Direction::Up => { head_pos.y += 1; } Direction::Down => { head_pos.y -= 1; } }; } }
这里会遇到一个问题,蛇头运动时其实有2种触发机制,1是不按键时,继续保持原来的方向运动(Movement);另1种是按键改变运动方向(Input)。再考虑到运动过程中,可能会吃掉食物(Eating),以及吃掉食物后身体长大(Growth),综合考虑这几种状态,再引入一个枚举:
#[derive(SystemLabel, Debug, Hash, PartialEq, Eq, Clone)] pub enum SnakeMovement { Input, Movement, Eating, Growth, }
在向bevy中add_system时,应该是input的处理要在Movement之前,即优先响应按键改变方向,这里就得使用bevy中的label机制
.add_system( snake_movement_input .system() .label(SnakeMovement::Input) //按键处理,打上Input标签 .before(SnakeMovement::Movement),//Input标签要在Movement标签前处理 ) .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(0.5)) .with_system(snake_movement.system().label(SnakeMovement::Movement)),//给位置变化打上Movement标签
三、添加蛇身
struct Materials { head_material: Handle<ColorMaterial>, segment_material: Handle<ColorMaterial>, // <--蛇身 food_material: Handle<ColorMaterial>, } struct SnakeSegment; #[derive(Default)] struct SnakeSegments(Vec<Entity>);
先添加2个struct表示蛇身,注意蛇身方块随着不断吃食物增加,肯定不止一个,所以使用Vec<T>表示1个列表。初始化的地方,相应略做调整:
fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) { let mut camera = OrthographicCameraBundle::new_2d(); camera.transform = Transform::from_translation(Vec3::new(0.0, 0.0, 5.0)); commands.spawn_bundle(camera); commands.insert_resource(Materials { head_material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()), segment_material: materials.add(Color::rgb(0.3, 0.3, 0.3).into()), // <--蛇身的颜色 food_material: materials.add(Color::rgb(1.0, 0.0, 1.0).into()), }); } fn spawn_snake( mut commands: Commands, materials: Res<Materials>, mut segments: ResMut<SnakeSegments>, ) { segments.0 = vec![ commands .spawn_bundle(SpriteBundle { //蛇头方块 material: materials.head_material.clone(), sprite: Sprite::new(Vec2::new(10.0, 10.0)), ..Default::default() }) .insert(SnakeHead { //蛇头默认向上运动 direction: Direction::Up, }) .insert(SnakeSegment) //蛇身 .insert(Position { x: 3, y: 3 }) .insert(Size::square(0.8)) .id(), spawn_segment( //生成蛇身 commands, &materials.segment_material, //蛇身跟在蛇头后面 Position { x: 3, y: 2 }, ), ]; }
为了方便观察,可以把位置移动相关的代码注释掉,跑起来后,大致如下:
接下来再来处理蛇身的运动跟随:
fn snake_movement( segments: ResMut<SnakeSegments>, mut heads: Query<(Entity, &SnakeHead)>, mut positions: Query<&mut Position>, ) { if let Some((head_entity, head)) = heads.iter_mut().next() { //先取出蛇身列表中的所有Position let segment_positions = segments .0 .iter() .map(|e| *positions.get_mut(*e).unwrap()) .collect::<Vec<Position>>(); //再找到蛇头的Position let mut head_pos = positions.get_mut(head_entity).unwrap(); //根据蛇头方向,调整蛇头在网格中的位置 match &head.direction { Direction::Left => { if head_pos.x > 0 { head_pos.x -= 1; } } Direction::Right => { if head_pos.x < CELL_X_COUNT as i32 - 1 { head_pos.x += 1; } } Direction::Up => { if head_pos.y < CELL_Y_COUNT as i32 - 1 { head_pos.y += 1; } } Direction::Down => { if head_pos.y > 0 { head_pos.y -= 1; } } }; //蛇身的position跟随蛇头的position segment_positions .iter() .zip(segments.0.iter().skip(1)) .for_each(|(pos, segment)| { *positions.get_mut(*segment).unwrap() = *pos; }); } }
四、吃食物
struct GrowthEvent; //吃掉食物后,蛇身长大的事件 fn snake_eating( mut commands: Commands, mut growth_writer: EventWriter<GrowthEvent>, food_positions: Query<(Entity, &Position), With<Food>>, head_positions: Query<&Position, With<SnakeHead>>, ) { for head_pos in head_positions.iter() { for (ent, food_pos) in food_positions.iter() { if food_pos == head_pos { //蛇头位置与食物位置相同时,销毁食物(即:吃掉食物) commands.entity(ent).despawn(); //随便发送Growth事件 growth_writer.send(GrowthEvent); } } } }
代码不复杂,核心逻辑就是判断蛇身与食物position是否相同,相同时视为食物被吃掉,然后对外触发事件。将snake_eating添加到系统里:
.add_event::<GrowthEvent>()//添加事件 .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(0.5)) .with_system(snake_movement.system().label(SnakeMovement::Movement)) //给位置变化打上Movement标签 .with_system( //<-- snake_eating .system() .label(SnakeMovement::Eating)//吃食物处理打上Eating标签 .after(SnakeMovement::Movement),//Eating标签在Movement后处理 ) )
五、长身体
先添加一个struct,记录最后1个蛇身方块的位置
#[derive(Default)] struct LastTailPosition(Option<Position>);
然后在main中添加这个资源
.insert_resource(LastTailPosition::default()) // <--
长身体的函数如下:
//蛇身长大 fn snake_growth( commands: Commands, last_tail_position: Res<LastTailPosition>, mut segments: ResMut<SnakeSegments>, mut growth_reader: EventReader<GrowthEvent>, materials: Res<Materials>, ) { //如果收到了GrowthEvent事件 if growth_reader.iter().next().is_some() { //向蛇身尾部新块1个方块 segments.0.push(spawn_segment( commands, &materials.segment_material, last_tail_position.0.unwrap(), )); } }
在add_system_set中添加snake_growth
.add_system_set( SystemSet::new() ... .with_system( //<-- snake_growth .system() .label(SnakeMovement::Growth) .after(SnakeMovement::Eating),//Growth在吃完食物后处理 ) )
现在看上去有点象贪吃蛇的样子了,不过有1个明显bug,蛇头可以从蛇身中穿过。
六 GameOver处理
蛇头撞到墙,或者遇到自己身体,应该GameOver,重新来过,这个同样可以供应Event完成
struct GameOverEvent;
先定义GameOverEvent事件,然后在蛇头移动中加入“边界检测”及“头遇到身体的检测”
fn snake_movement( ... mut game_over_writer: EventWriter<GameOverEvent>, //<-- ... ) { if let Some((head_entity, head)) = heads.iter_mut().next() { ... //再找到蛇头的Position let mut head_pos = positions.get_mut(head_entity).unwrap(); //根据蛇头方向,调整蛇头在网格中的位置 match &head.direction { Direction::Left => { head_pos.x -= 1; } Direction::Right => { head_pos.x += 1; } Direction::Up => { head_pos.y += 1; } Direction::Down => { head_pos.y -= 1; } }; //边界检测,超出则GameOver //<-- if head_pos.x < 0 || head_pos.y < 0 || head_pos.x as u32 >= CELL_X_COUNT || head_pos.y as u32 >= CELL_Y_COUNT { game_over_writer.send(GameOverEvent); } //蛇头遇到蛇身,则GameOver //<-- if segment_positions.contains(&head_pos) { game_over_writer.send(GameOverEvent); } //蛇身的position跟随蛇头的position segment_positions .iter() .zip(segments.0.iter().skip(1)) .for_each(|(pos, segment)| { *positions.get_mut(*segment).unwrap() = *pos; }); ... } }
收到GameOver事件后,重头来过
/** * game over处理 */ fn game_over( //<-- mut commands: Commands, mut reader: EventReader<GameOverEvent>, materials: Res<Materials>, segments_res: ResMut<SnakeSegments>, food: Query<Entity, With<Food>>, segments: Query<Entity, With<SnakeSegment>>, ) { //如果收到GameOver事件 if reader.iter().next().is_some() { //销毁所有食物及蛇身 for ent in food.iter().chain(segments.iter()) { commands.entity(ent).despawn(); } //重新初始化 spawn_snake(commands, materials, segments_res); } }
最后要把game_over以及GameOver事件加到系统中
fn main() { App::build() ... .add_event::<GameOverEvent>() ... .add_system(game_over.system().after(SnakeMovement::Movement))//<-- GameOver处理 ... .run(); }
运行一下:
七、食物生成优化
目前还有2个小问题:
1. 食物过多,通常1次只生成1个食物,最好是当前食物被吃掉后,才能生成下1个
2. 食物生成的位置,目前是随机生成的,有可能生成的位置,正好在蛇身中,看上去比较奇怪。
//随机位置生成食物 fn food_spawner( mut commands: Commands, materials: Res<Materials>, foods: Query<&Food>, //<-- positions: Query<&Position, With<SnakeSegment>>, //<-- ) { match foods.iter().next() { //当前无食物时,才生成 None => { let mut x = (random::<f32>() * CELL_X_COUNT as f32) as i32; let mut y = (random::<f32>() * CELL_Y_COUNT as f32) as i32; //循环检测,随机生成的位置,是否在蛇身中 loop { let mut check_pos = true; for p in positions.iter() { if p.x == x && p.y == y { check_pos = false; x = (random::<f32>() * CELL_X_COUNT as f32) as i32; y = (random::<f32>() * CELL_Y_COUNT as f32) as i32; break; } } if check_pos { break; } } commands .spawn_bundle(SpriteBundle { material: materials.food_material.clone(), ..Default::default() }) .insert(Food) .insert(Position { x, y }) .insert(Size::square(0.65)); } _ => {} } }
解决的方法也不复杂,生成前检查下:
1 当前是否已有食物,
2 生成的位置提前在蛇身的position中扫描一圈,如果已存在,再随机生成1个新位置,直到检测通过。
最后附上main.rs完整代码:
//基于Rust Bevy引擎的贪吃蛇游戏 //详细分析见:https://www.cnblogs.com/yjmyzz/p/Creating_a_Snake_Clone_in_Rust_with_Bevy_1.html //by 菩提树下的杨过 use bevy::core::FixedTimestep; use bevy::prelude::*; use bevy_prototype_debug_lines::*; use rand::prelude::random; //格子的数量(横向10等分,纵向10等分,即10*10的网格) const CELL_X_COUNT: u32 = 10; const CELL_Y_COUNT: u32 = 10; /** * 网格中的位置 */ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] struct Position { x: i32, y: i32, } /** * 蛇头在网格中的大小 */ struct Size { width: f32, height: f32, } impl Size { //贪吃蛇都是用方块,所以width/height均设置成x pub fn square(x: f32) -> Self { Self { width: x, height: x, } } } #[derive(PartialEq, Copy, Clone)] enum Direction { Left, Up, Right, Down, } impl Direction { fn opposite(self) -> Self { match self { Self::Left => Self::Right, Self::Right => Self::Left, Self::Up => Self::Down, Self::Down => Self::Up, } } } struct SnakeHead { direction: Direction, } struct Materials { head_material: Handle<ColorMaterial>, segment_material: Handle<ColorMaterial>, food_material: Handle<ColorMaterial>, } #[derive(Default)] struct LastTailPosition(Option<Position>); struct GameOverEvent; struct SnakeSegment; #[derive(Default)] struct SnakeSegments(Vec<Entity>); #[derive(SystemLabel, Debug, Hash, PartialEq, Eq, Clone)] pub enum SnakeMovement { Input, Movement, Eating, Growth, } struct Food; //随机位置生成食物 fn food_spawner( mut commands: Commands, materials: Res<Materials>, foods: Query<&Food>, //<-- positions: Query<&Position, With<SnakeSegment>>, //<-- ) { match foods.iter().next() { //当前无食物时,才生成 None => { let mut x = (random::<f32>() * CELL_X_COUNT as f32) as i32; let mut y = (random::<f32>() * CELL_Y_COUNT as f32) as i32; //循环检测,随机生成的位置,是否在蛇身中 loop { let mut check_pos = true; for p in positions.iter() { if p.x == x && p.y == y { check_pos = false; x = (random::<f32>() * CELL_X_COUNT as f32) as i32; y = (random::<f32>() * CELL_Y_COUNT as f32) as i32; break; } } if check_pos { break; } } commands .spawn_bundle(SpriteBundle { material: materials.food_material.clone(), ..Default::default() }) .insert(Food) .insert(Position { x, y }) .insert(Size::square(0.65)); } _ => {} } } fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) { let mut camera = OrthographicCameraBundle::new_2d(); camera.transform = Transform::from_translation(Vec3::new(0.0, 0.0, 5.0)); commands.spawn_bundle(camera); let materials = Materials { head_material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()), segment_material: materials.add(Color::rgb(0.3, 0.3, 0.3).into()), //蛇身的颜色 food_material: materials.add(Color::rgb(1.0, 0.0, 1.0).into()), }; commands.insert_resource(materials); } fn spawn_snake( mut commands: Commands, materials: Res<Materials>, mut segments: ResMut<SnakeSegments>, ) { segments.0 = vec![ commands .spawn_bundle(SpriteBundle { //蛇头方块 material: materials.head_material.clone(), sprite: Sprite::new(Vec2::new(10.0, 10.0)), ..Default::default() }) .insert(SnakeHead { //蛇头默认向上运动 direction: Direction::Up, }) .insert(SnakeSegment) .insert(Position { x: 3, y: 3 }) .insert(Size::square(0.8)) .id(), spawn_segment( //生成蛇身 commands, &materials.segment_material, //蛇身跟在蛇头后面 Position { x: 3, y: 2 }, ), ]; } //根据网格大小,对方块尺寸进行缩放 fn size_scaling(windows: Res<Windows>, mut q: Query<(&Size, &mut Sprite)>) { // <-- let window = windows.get_primary().unwrap(); for (sprite_size, mut sprite) in q.iter_mut() { sprite.size = Vec2::new( sprite_size.width * (window.width() as f32 / CELL_X_COUNT as f32), sprite_size.height * (window.height() as f32 / CELL_Y_COUNT as f32), ); } } /** * 根据方块的position,将其放入适合的网格中 */ fn position_translation(windows: Res<Windows>, mut q: Query<(&Position, &mut Transform)>) { // <-- fn convert(pos: f32, window_size: f32, cell_count: f32) -> f32 { //算出每1格的大小 let tile_size = window_size / cell_count; //计算最终坐标值 pos * tile_size - 0.5 * window_size + 0.5 * tile_size } let window = windows.get_primary().unwrap(); for (pos, mut transform) in q.iter_mut() { transform.translation = Vec3::new( convert(pos.x as f32, window.width() as f32, CELL_X_COUNT as f32), convert(pos.y as f32, window.height() as f32, CELL_Y_COUNT as f32), 0.0, ); } } //画网格辅助线 fn draw_grid(windows: Res<Windows>, mut lines: ResMut<DebugLines>) { // <-- let window = windows.get_primary().unwrap(); let half_win_width = 0.5 * window.width(); let half_win_height = 0.5 * window.height(); let x_space = window.width() / CELL_X_COUNT as f32; let y_space = window.height() / CELL_Y_COUNT as f32; let mut i = -1. * half_win_height; while i < half_win_height { lines.line( Vec3::new(-1. * half_win_width, i, 0.0), Vec3::new(half_win_width, i, 0.0), 0.0, ); i += y_space; } i = -1. * half_win_width; while i < half_win_width { lines.line( Vec3::new(i, -1. * half_win_height, 0.0), Vec3::new(i, half_win_height, 0.0), 0.0, ); i += x_space; } //画竖线 lines.line( Vec3::new(0., -1. * half_win_height, 0.0), Vec3::new(0., half_win_height, 0.0), 0.0, ); } /** *方向键改变运动方向 */ fn snake_movement_input(keyboard_input: Res<Input<KeyCode>>, mut heads: Query<&mut SnakeHead>) { if let Some(mut head) = heads.iter_mut().next() { let dir: Direction = if keyboard_input.pressed(KeyCode::Left) { Direction::Left } else if keyboard_input.pressed(KeyCode::Down) { Direction::Down } else if keyboard_input.pressed(KeyCode::Up) { Direction::Up } else if keyboard_input.pressed(KeyCode::Right) { Direction::Right } else { head.direction }; //蛇头不能向反方向走,否则就把自己的身体给吃了 if dir != head.direction.opposite() { head.direction = dir; } } } struct GrowthEvent; //吃掉食物后,蛇身长大的事件 fn snake_eating( mut commands: Commands, mut growth_writer: EventWriter<GrowthEvent>, food_positions: Query<(Entity, &Position), With<Food>>, head_positions: Query<&Position, With<SnakeHead>>, ) { for head_pos in head_positions.iter() { for (ent, food_pos) in food_positions.iter() { if food_pos == head_pos { //蛇头位置与食物位置相同时,销毁食物(即:吃掉食物) commands.entity(ent).despawn(); //随便发送Growth事件 growth_writer.send(GrowthEvent); } } } } fn snake_movement( segments: ResMut<SnakeSegments>, mut heads: Query<(Entity, &SnakeHead)>, mut last_tail_position: ResMut<LastTailPosition>, mut game_over_writer: EventWriter<GameOverEvent>, mut positions: Query<&mut Position>, ) { if let Some((head_entity, head)) = heads.iter_mut().next() { //先取出蛇身列表中的所有Position let segment_positions = segments .0 .iter() .map(|e| *positions.get_mut(*e).unwrap()) .collect::<Vec<Position>>(); //再找到蛇头的Position let mut head_pos = positions.get_mut(head_entity).unwrap(); //根据蛇头方向,调整蛇头在网格中的位置 match &head.direction { Direction::Left => { head_pos.x -= 1; } Direction::Right => { head_pos.x += 1; } Direction::Up => { head_pos.y += 1; } Direction::Down => { head_pos.y -= 1; } }; //边界检测,超出则GameOver if head_pos.x < 0 || head_pos.y < 0 || head_pos.x as u32 >= CELL_X_COUNT || head_pos.y as u32 >= CELL_Y_COUNT { game_over_writer.send(GameOverEvent); } //蛇头遇到蛇身,则GameOver if segment_positions.contains(&head_pos) { game_over_writer.send(GameOverEvent); } //蛇身的position跟随蛇头的position segment_positions .iter() .zip(segments.0.iter().skip(1)) .for_each(|(pos, segment)| { *positions.get_mut(*segment).unwrap() = *pos; }); //记录蛇身最后1个方块的位置 last_tail_position.0 = Some(*segment_positions.last().unwrap()); } } /** * game over处理 */ fn game_over( //<-- mut commands: Commands, mut reader: EventReader<GameOverEvent>, materials: Res<Materials>, segments_res: ResMut<SnakeSegments>, food: Query<Entity, With<Food>>, segments: Query<Entity, With<SnakeSegment>>, ) { //如果收到GameOver事件 if reader.iter().next().is_some() { //销毁所有食物及蛇身 for ent in food.iter().chain(segments.iter()) { commands.entity(ent).despawn(); } //重新初始化 spawn_snake(commands, materials, segments_res); } } //蛇身长大 fn snake_growth( commands: Commands, last_tail_position: Res<LastTailPosition>, mut segments: ResMut<SnakeSegments>, mut growth_reader: EventReader<GrowthEvent>, materials: Res<Materials>, ) { //如果收到了GrowthEvent事件 if growth_reader.iter().next().is_some() { //向蛇身尾部新块1个方块 segments.0.push(spawn_segment( commands, &materials.segment_material, last_tail_position.0.unwrap(), )); } } //生成蛇身 fn spawn_segment( mut commands: Commands, material: &Handle<ColorMaterial>, position: Position, ) -> Entity { commands .spawn_bundle(SpriteBundle { material: material.clone(), ..Default::default() }) .insert(SnakeSegment) .insert(position) .insert(Size::square(0.65)) .id() } fn main() { App::build() .insert_resource(WindowDescriptor { title: "snake".to_string(), width: 300., height: 300., resizable: false, ..Default::default() }) .insert_resource(LastTailPosition::default()) // <-- .insert_resource(SnakeSegments::default()) .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04))) .add_startup_system(setup.system()) .add_startup_stage("game_setup", SystemStage::single(spawn_snake.system())) .add_system(draw_grid.system()) .add_system( snake_movement_input .system() .label(SnakeMovement::Input) //按键处理,打上Input标签 .before(SnakeMovement::Movement), //Input标签要在Movement标签前处理 ) .add_event::<GrowthEvent>() //添加事件 .add_event::<GameOverEvent>() .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(0.5)) .with_system(snake_movement.system().label(SnakeMovement::Movement)) //给位置变化打上Movement标签 .with_system( snake_eating .system() .label(SnakeMovement::Eating) //吃食物处理打上Eating标签 .after(SnakeMovement::Movement), //Eating标签在Movement后处理 ) .with_system( //<-- snake_growth .system() .label(SnakeMovement::Growth) .after(SnakeMovement::Eating), //Growth在吃完食物后处理 ), ) .add_system(game_over.system().after(SnakeMovement::Movement)) //<-- GameOver处理 .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(2.0)) .with_system(food_spawner.system()), ) .add_system_set_to_stage( CoreStage::PostUpdate, SystemSet::new() .with_system(position_translation.system()) .with_system(size_scaling.system()), ) .add_plugins(DefaultPlugins) .add_plugin(DebugLinesPlugin) .run(); }
参考文章:
https://bevyengine.org/learn/book/getting-started/
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。