学习Bevy练手的Snake
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
5170
Cargo.lock
generated
Normal file
5170
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
Cargo.toml
Normal file
8
Cargo.toml
Normal 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
12
README.md
Normal 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
12
src/constant.rs
Normal 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
83
src/entity.rs
Normal 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
18
src/main.rs
Normal 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
84
src/system.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user