meet Part I Go on, you can't lack food in the greedy Snake game. Let's solve this problem first:
1, Random location generated food
use rand::prelude::random; ... struct Food; //Random location generated 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)); }
Then add a food item in materials_ Material member
... 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()), // < -- during initialization, the specified food is purple }); } ... fn main() { App::build() ... .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(2.0)) //< -- generate food once in 2 seconds .with_system(food_spawner.system()), ) ... .add_plugins(DefaultPlugins) .add_plugin(DebugLinesPlugin) .run(); }

2, Let the snake head move forward on its own
So far, the snakehead can only move by pressing the direction key. Everyone has played this game. When not pressing the key, the snakehead should keep the original direction and move on. Only when there is a key to change the movement direction, the following is to achieve this effect:
#[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, }
The direction enumeration is added to record the direction of snake head movement, and the direction member is added in SnakeHead. During initialization, the snake head moves upward by default
.insert(SnakeHead { direction: Direction::Up, })
The key processing and position movement shall also be adjusted accordingly:
/** *Direction keys change the direction of movement */ 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 }; //Snakeheads cannot walk in the opposite direction, or they will eat their own bodies if dir != head.direction.opposite() { head.direction = dir; } } } /** * Adjust the position of snake head in the grid according to the direction of motion */ 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; } }; } }
There is a problem here. There are actually two trigger mechanisms when the snake head moves. One is to continue to move in the original direction without pressing the key; The other is to press the key to change the motion direction (Input). Considering that you may eat food during exercise and grow after eating food, consider these states comprehensively, and then introduce an enumeration:
#[derive(SystemLabel, Debug, Hash, PartialEq, Eq, Clone)] pub enum SnakeMovement { Input, Movement, Eating, Growth, }
Add to bevy_ System, the input should be processed before the Movement, that is, the priority should be to respond to the key to change the direction. Here, the label mechanism in bevy must be used
.add_system( snake_movement_input .system() .label(SnakeMovement::Input) //Key processing and Input label .before(SnakeMovement::Movement),//The Input tag should be processed before the Movement tag ) .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(0.5)) .with_system(snake_movement.system().label(SnakeMovement::Movement)),//Label the position change with Movement

3, Add snake body
struct Materials { head_material: Handle<ColorMaterial>, segment_material: Handle<ColorMaterial>, // < -- snake body food_material: Handle<ColorMaterial>, } struct SnakeSegment; #[derive(Default)] struct SnakeSegments(Vec<Entity>);
First add two struct s to represent the snake body. Note that there must be more than one snake body box with the increase of food, so use VEC < T > to represent a list. Slightly adjust the initialization:
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()), // < -- the color of the snake 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 { //Snake head square material: materials.head_material.clone(), sprite: Sprite::new(Vec2::new(10.0, 10.0)), ..Default::default() }) .insert(SnakeHead { //Snakehead moves upward by default direction: Direction::Up, }) .insert(SnakeSegment) //Snake body .insert(Position { x: 3, y: 3 }) .insert(Size::square(0.8)) .id(), spawn_segment( //Generate snake body commands, &materials.segment_material, //The snake's body follows the snake's head Position { x: 3, y: 2 }, ), ]; }
To facilitate observation, you can comment out the code related to position movement. After running, it is roughly as follows:

Next, let's deal with the following movement of the snake:
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() { //Take out all positions in the snake body list first let segment_positions = segments .0 .iter() .map(|e| *positions.get_mut(*e).unwrap()) .collect::<Vec<Position>>(); //Then find the Position of the snake head let mut head_pos = positions.get_mut(head_entity).unwrap(); //Adjust the position of the snake head in the grid according to the snake head direction 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; } } }; //The position of the snake body follows the position of the snake head segment_positions .iter() .zip(segments.0.iter().skip(1)) .for_each(|(pos, segment)| { *positions.get_mut(*segment).unwrap() = *pos; }); } }

4, Eat food
struct GrowthEvent; //After eating food, the snake grew up 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 { //When the position of the snake head is the same as that of the food, destroy the food (i.e. eat the food) commands.entity(ent).despawn(); //Casually send Growth events growth_writer.send(GrowthEvent); } } } }
The code is not complicated. The core logic is to judge whether the position of the snake is the same as that of the food. When the position is the same, it is considered that the food is eaten, and then an external event is triggered. Snap_ Adding eating to the system:
.add_event::<GrowthEvent>()//Add event .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(0.5)) .with_system(snake_movement.system().label(SnakeMovement::Movement)) //Label the position change with Movement .with_system( //<-- snake_eating .system() .label(SnakeMovement::Eating)//Food handling is labeled Eating .after(SnakeMovement::Movement),//The Eating tag is processed after the Movement ) )

5, Long body
First add a struct to record the position of the last snake box
#[derive(Default)] struct LastTailPosition(Option<Position>);
Then add this resource in main
.insert_resource(LastTailPosition::default()) // <--
The function of the long body is as follows:
//The snake grows up fn snake_growth( commands: Commands, last_tail_position: Res<LastTailPosition>, mut segments: ResMut<SnakeSegments>, mut growth_reader: EventReader<GrowthEvent>, materials: Res<Materials>, ) { //If the GrowthEvent event event is received if growth_reader.iter().next().is_some() { //Add a new block to the tail of the snake segments.0.push(spawn_segment( commands, &materials.segment_material, last_tail_position.0.unwrap(), )); } }
In add_ system_ Add snake to set_ growth
.add_system_set( SystemSet::new() ... .with_system( //<-- snake_growth .system() .label(SnakeMovement::Growth) .after(SnakeMovement::Eating),//Growth treats after eating ) )

Now it looks like a greedy snake, but there is an obvious bug that the snake head can pass through the snake.
Vi. GameOver processing
If the snake head hits a wall or encounters his own body, he should GameOver and start over again. This can also be completed by the Event
struct GameOverEvent;
First define the GameOverEvent event, and then add "boundary detection" and "detection of head encountering body" to the snake head movement
fn snake_movement( ... mut game_over_writer: EventWriter<GameOverEvent>, //<-- ... ) { if let Some((head_entity, head)) = heads.iter_mut().next() { ... //Then find the Position of the snake head let mut head_pos = positions.get_mut(head_entity).unwrap(); //Adjust the position of the snake head in the grid according to the snake head direction 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; } }; //Boundary detection, beyond which 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); } //When the snake head meets the snake body, gameover//<-- if segment_positions.contains(&head_pos) { game_over_writer.send(GameOverEvent); } //The position of the snake body follows the position of the snake head segment_positions .iter() .zip(segments.0.iter().skip(1)) .for_each(|(pos, segment)| { *positions.get_mut(*segment).unwrap() = *pos; }); ... } }
After receiving the GameOver event, start over
/** * game over handle */ 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>>, ) { //If a GameOver event is received if reader.iter().next().is_some() { //Destroy all food and snakes for ent in food.iter().chain(segments.iter()) { commands.entity(ent).despawn(); } //Reinitialize spawn_snake(commands, materials, segments_res); } }
Finally, put the game_over and GameOver events are added to the system
fn main() { App::build() ... .add_event::<GameOverEvent>() ... .add_system(game_over.system().after(SnakeMovement::Movement))//< -- gameover processing ... .run(); }
Run the following:

7, Food production optimization
There are two minor problems:
1. There are too many foods. Usually, only one food is generated at a time. It is best to generate the next food after the current food is eaten
2. At present, the location of food generation is randomly generated. The possible location is right in the snake, which looks strange.
//Random location generated food fn food_spawner( mut commands: Commands, materials: Res<Materials>, foods: Query<&Food>, //<-- positions: Query<&Position, With<SnakeSegment>>, //<-- ) { match foods.iter().next() { //Generated only when there is no food currently 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 detection, whether the randomly generated position is in the snake 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)); } _ => {} } }
The solution is not complicated. Check before generation:
1. Whether there is food currently,
2. The generated position is scanned in the position of the snake body in advance. If it already exists, a new position is randomly generated until the detection passes.

Finally, attach main RS complete code:
//Snake game based on rust best engine //For detailed analysis, see: https://www.cnblogs.com/yjmyzz/p/Creating_a_Snake_Clone_in_Rust_with_Bevy_1.html //by Yang Guo under the bodhi tree use bevy::core::FixedTimestep; use bevy::prelude::*; use bevy_prototype_debug_lines::*; use rand::prelude::random; //Number of grids (10 equally divided horizontally and 10 equally divided vertically, i.e. 10 * 10 grid) const CELL_X_COUNT: u32 = 10; const CELL_Y_COUNT: u32 = 10; /** * Position in Grid */ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] struct Position { x: i32, y: i32, } /** * The size of the snake head in the grid */ struct Size { width: f32, height: f32, } impl Size { //Greedy snakes use squares, so the width/height is set to 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; //Random location generated food fn food_spawner( mut commands: Commands, materials: Res<Materials>, foods: Query<&Food>, //<-- positions: Query<&Position, With<SnakeSegment>>, //<-- ) { match foods.iter().next() { //Generated only when there is no food currently 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 detection, whether the randomly generated position is in the snake 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()), //The color of the snake 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 { //Snake head square material: materials.head_material.clone(), sprite: Sprite::new(Vec2::new(10.0, 10.0)), ..Default::default() }) .insert(SnakeHead { //Snakehead moves upward by default direction: Direction::Up, }) .insert(SnakeSegment) .insert(Position { x: 3, y: 3 }) .insert(Size::square(0.8)) .id(), spawn_segment( //Generate snake body commands, &materials.segment_material, //The snake's body follows the snake's head Position { x: 3, y: 2 }, ), ]; } //Scale the box size according to the grid size 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), ); } } /** * According to the position of the box, put it into the appropriate grid */ fn position_translation(windows: Res<Windows>, mut q: Query<(&Position, &mut Transform)>) { // <-- fn convert(pos: f32, window_size: f32, cell_count: f32) -> f32 { //Calculate the size of each grid let tile_size = window_size / cell_count; //Calculate final coordinate value 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, ); } } //Draw grid guides 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; } //Draw a vertical line lines.line( Vec3::new(0., -1. * half_win_height, 0.0), Vec3::new(0., half_win_height, 0.0), 0.0, ); } /** *Direction keys change the direction of movement */ 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 }; //Snakeheads cannot walk in the opposite direction, or they will eat their own bodies if dir != head.direction.opposite() { head.direction = dir; } } } struct GrowthEvent; //After eating food, the snake grew up 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 { //When the position of the snake head is the same as that of the food, destroy the food (i.e. eat the food) commands.entity(ent).despawn(); //Casually send Growth events 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() { //Take out all positions in the snake body list first let segment_positions = segments .0 .iter() .map(|e| *positions.get_mut(*e).unwrap()) .collect::<Vec<Position>>(); //Then find the Position of the snake head let mut head_pos = positions.get_mut(head_entity).unwrap(); //Adjust the position of the snake head in the grid according to the snake head direction 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; } }; //Boundary detection, beyond which 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); } //When the snake head meets the snake body, GameOver if segment_positions.contains(&head_pos) { game_over_writer.send(GameOverEvent); } //The position of the snake body follows the position of the snake head segment_positions .iter() .zip(segments.0.iter().skip(1)) .for_each(|(pos, segment)| { *positions.get_mut(*segment).unwrap() = *pos; }); //Record the position of the last square of the snake last_tail_position.0 = Some(*segment_positions.last().unwrap()); } } /** * game over handle */ 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>>, ) { //If a GameOver event is received if reader.iter().next().is_some() { //Destroy all food and snakes for ent in food.iter().chain(segments.iter()) { commands.entity(ent).despawn(); } //Reinitialize spawn_snake(commands, materials, segments_res); } } //The snake grows up fn snake_growth( commands: Commands, last_tail_position: Res<LastTailPosition>, mut segments: ResMut<SnakeSegments>, mut growth_reader: EventReader<GrowthEvent>, materials: Res<Materials>, ) { //If the GrowthEvent event event is received if growth_reader.iter().next().is_some() { //Add a new block to the tail of the snake segments.0.push(spawn_segment( commands, &materials.segment_material, last_tail_position.0.unwrap(), )); } } //Generate snake body 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) //Key processing and Input label .before(SnakeMovement::Movement), //The Input tag should be processed before the Movement tag ) .add_event::<GrowthEvent>() //Add event .add_event::<GameOverEvent>() .add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(0.5)) .with_system(snake_movement.system().label(SnakeMovement::Movement)) //Label the position change with Movement .with_system( snake_eating .system() .label(SnakeMovement::Eating) //Food handling is labeled Eating .after(SnakeMovement::Movement), //The Eating tag is processed after the Movement ) .with_system( //<-- snake_growth .system() .label(SnakeMovement::Growth) .after(SnakeMovement::Eating), //Growth treats after eating ), ) .add_system(game_over.system().after(SnakeMovement::Movement)) //< -- gameover processing .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(); }
Reference article:
https://bevyengine.org/learn/book/getting-started/