学习Bevy练手的Snake

This commit is contained in:
lkhsss
2025-07-07 21:25:18 +08:00
commit 49c0c42d2a
8 changed files with 5388 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

5170
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "snake"
version = "0.1.0"
edition = "2024"
[dependencies]
bevy = "0.16.1"
rand = "0.9.1"

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# Bevy Snake
学习Bevy练手的Snake
# CHANGELOG
## 0.1.0 - [2025.7.7]
### ADD
- 完成蛇的基本控制和移动系统
# TODO
[x] 蛇的基本控制和移动系统
[ ] 食物生成
[ ] 碰撞系统
[ ] 分数展示

12
src/constant.rs Normal file
View File

@ -0,0 +1,12 @@
use bevy::color::Color;
pub const SNAKE_HEAD_COLOR: Color = Color::srgb(0.7, 0.7, 0.7);
pub const SNAKE_SEGMENT_COLOR: Color = Color::srgb(0.1, 0.7, 0.7);
pub const SPEED:f32 = 200.;//暂时固定
pub const SNAKE_SIZE:f32 = 10.;
pub const WINDOWS_WIDTH:f32 = 800.;
pub const WINDOWS_HEIGHT:f32 = 600.;

83
src/entity.rs Normal file
View File

@ -0,0 +1,83 @@
use std::ops::Not;
use bevy::{prelude::*};
use crate::constant::*;
#[derive(Component)]
pub struct SnakeHead{pub direction:Direction}
impl SnakeHead {
pub fn new(d:Direction)->Self{
Self{ direction: d }
}
}
impl Default for SnakeHead {
fn default() -> Self {
Self { direction: Default::default() }
}
}
// 蛇的身体组件
#[derive(Component)]
pub struct SnakeSegment;
#[derive(Component, Clone, Copy, PartialEq, Eq, Debug)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Default for Direction {
fn default() -> Self {
Self::Right
}
}
impl Direction {
pub fn opposite(&self) -> Self {
match self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
}
#[derive(Default)]
#[derive(Resource)]
pub struct Positions(pub Vec<Vec2>);
// 游戏状态
#[derive(Resource)]
pub struct GameState {
pub game_over: bool,
pub score: u32,
pub highest_score: u32,
pub windows_size: (f32, f32),
}
impl Default for GameState {
fn default() -> Self {
Self {
game_over: false,
score: 0,
highest_score: 0,
windows_size: (WINDOWS_WIDTH, WINDOWS_HEIGHT),
}
}
}
impl GameState{
pub fn get_windows_size(&self)->(f32,f32){
self.windows_size
}
pub fn get_windows_size_width(&self)->f32{
self.windows_size.0
}
pub fn get_windows_size_height(&self)->f32{
self.windows_size.1
}
}

18
src/main.rs Normal file
View File

@ -0,0 +1,18 @@
use bevy::prelude::*;
use crate::entity::{GameState, Positions};
mod system;
mod entity;
mod constant;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, (system::setup_camera,system::setup).chain())
.init_resource::<GameState>()
.init_resource::<Positions>()
.add_systems(Startup, (system::spawn))
.add_systems(Update, (system::input,system::snake_movement))
.run();
}

84
src/system.rs Normal file
View File

@ -0,0 +1,84 @@
use crate::{
constant::*,
entity::*,
};
use bevy::prelude::* ;
pub fn setup_camera(mut commands: Commands) {
commands.spawn(Camera2d);
}
pub fn setup(mut game_state: ResMut<GameState>) {
*game_state = GameState::default();
}
pub fn spawn(mut commands: Commands, mut position: ResMut<Positions>) {
commands.spawn((
SnakeHead::default(),
Sprite::from_color(SNAKE_HEAD_COLOR, vec2(SNAKE_SIZE, SNAKE_SIZE)),
Transform::from_xyz(0.0, 0.0, 1.0),
// Position::new(0.,0.)
));
position.0.push(vec2(0.0, 0.0));
for i in 1..=4000 {
commands.spawn((
SnakeSegment,
Sprite {
color: SNAKE_SEGMENT_COLOR,
custom_size: Some(Vec2::new(SNAKE_SIZE, SNAKE_SIZE)),
..default()
},
Transform::from_xyz(-i as f32, 0.0, 0.0),
));
position.0.push(vec2(-i as f32, 0.0));
}
}
pub fn snake_movement(
mut head_positions: Query<(&SnakeHead, &mut Transform), With<SnakeHead>>,
mut segments: Query<&mut Transform, (With<SnakeSegment>, Without<SnakeHead>)>,
time: Res<Time>,
mut position: ResMut<Positions>,
) {
let during = time.delta_secs();
if let Ok((head, mut transform)) = head_positions.single_inner() {
//头坐标
position.0[0] = vec2(transform.translation.x,transform.translation.y);
let mut prev = position.0[0];
for (i, p) in position.0.iter_mut().enumerate().skip(1) {
let new_val = (*p + prev) / 2.;
prev = *p;
*p = new_val;
}
match head.direction {
Direction::Up => transform.translation.y += SPEED * during,
Direction::Down => transform.translation.y -= SPEED * during,
Direction::Left => transform.translation.x -= SPEED * during,
Direction::Right => transform.translation.x += SPEED * during,
}
for (i,mut transform) in segments.iter_mut().enumerate(){
transform.translation = vec3(position.0[i].x,position.0[i].y,0.);
}
}
}
pub fn input(keyboard_input: Res<ButtonInput<KeyCode>>, mut heads: Query<&mut SnakeHead>) {
if let Ok(mut head) = heads.single_mut() {
let new_direction = if keyboard_input.pressed(KeyCode::ArrowUp) {
Direction::Up
} else if keyboard_input.pressed(KeyCode::ArrowDown) {
Direction::Down
} else if keyboard_input.pressed(KeyCode::ArrowLeft) {
Direction::Left
} else if keyboard_input.pressed(KeyCode::ArrowRight) {
Direction::Right
} else {
return;
};
if new_direction != head.direction.opposite() {
head.direction = new_direction;
}
}
}