接上篇继续,贪吃蛇游戏中食物是不能缺少的,先来解决这个问题:
一、随机位置生成食物
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个小问题:
//随机位置生成食物
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/