From 5cc62bc4305649cc8a4fcc6ec461358fd2d64e94 Mon Sep 17 00:00:00 2001 From: Alexander Baranov Date: Sun, 7 Jun 2026 11:30:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20-=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- snake-game/src/domain.rs | 2 +- snake-game/src/domain/snake_path.rs | 50 +---------- snake-game/src/gameplay.rs | 129 +++++++++++++++------------- snake-game/src/lib.rs | 2 +- snake-game/src/main.rs | 4 +- snake-game/src/observers.rs | 7 ++ snake-game/src/startup.rs | 4 +- 7 files changed, 85 insertions(+), 113 deletions(-) diff --git a/snake-game/src/domain.rs b/snake-game/src/domain.rs index e923dad..827072d 100644 --- a/snake-game/src/domain.rs +++ b/snake-game/src/domain.rs @@ -18,7 +18,7 @@ pub const SNAKE_VELOCITY: u8 = (GRID_SIZE as f32 / 1.0) as u8; /// Голова змеи #[derive(Component)] -pub struct Head; +pub struct Head(pub Direction); /// Сегмент змеиного хвоста #[derive(Component)] diff --git a/snake-game/src/domain/snake_path.rs b/snake-game/src/domain/snake_path.rs index 92156db..59666a2 100644 --- a/snake-game/src/domain/snake_path.rs +++ b/snake-game/src/domain/snake_path.rs @@ -4,51 +4,5 @@ use std::collections::HashMap; use crate::domain::{Direction, Position}; /// Путь змеи -#[derive(Resource)] -pub struct SnakePath { - positions: HashMap, - fresh: Option, -} - -impl SnakePath { - pub fn new() -> Self { - SnakePath { - positions: HashMap::with_capacity(16), - fresh: None, - } - } - - pub fn push(&mut self, position: Position, direction: Direction) -> bool { - if let Some(_) = self.positions.get(&position) { - if let Some(fresh) = self.fresh { - if position == fresh { - self.positions.insert(position, direction); - return true; - } - } - return false; - } - self.positions.insert(position, direction); - self.fresh = Some(position); - return true; - } - - pub fn peek(&mut self, position: &Position) -> Option { - if let Some(fresh) = self.fresh { - if fresh == *position { - self.fresh = None; - } - } - self.positions.get(position).cloned() - } - - pub fn drop(&mut self, position: &Position) { - self.positions.remove(position); - } -} - -impl Default for SnakePath { - fn default() -> Self { - Self::new() - } -} +#[derive(Resource, Default, Deref, DerefMut)] +pub struct SnakePath(HashMap); diff --git a/snake-game/src/gameplay.rs b/snake-game/src/gameplay.rs index a6c5998..5bac638 100644 --- a/snake-game/src/gameplay.rs +++ b/snake-game/src/gameplay.rs @@ -1,92 +1,101 @@ use bevy::prelude::*; use crate::{ - domain::{Direction, End, GRID_SIZE, GridBarier, Head, Position, SnakePath, Tail, Velocity}, + domain::{Direction, GRID_SIZE, GridBarier, Head, Position, SnakePath, Tail, Velocity}, tools::split_by_grid, }; /// Система поворота головы змеи pub fn head_rotation( input: Res>, // - query: Single<(&mut Transform, &Direction), With>, - mut snake_path: ResMut, + query: Single<(&mut Transform, &mut Head, &Direction)>, ) { let _span = trace_span!("head_rotation").entered(); if input.just_pressed(KeyCode::ArrowLeft) { - let (mut transform, direction) = query.into_inner(); + let (mut transform, mut head, direction) = query.into_inner(); let translation = transform.translation.clone(); let cur_pos = Position::from(&*transform); - let mut dst_pos = direction.dst_pos(&*transform); let mut new_direction = direction.clone(); new_direction.rotate_left(); - if !snake_path.push(dst_pos, new_direction) { - trace!("try 1 failed: cannot push [{dst_pos} => {new_direction}] into SnakePath"); - dst_pos.shift(&direction); - if !snake_path.push(dst_pos, new_direction) { - trace!("try 2 failed: cannot push [{dst_pos} => {new_direction}] into SnakePath"); - return; - } - } + head.0 = new_direction; transform.rotation = new_direction.orientation(); - trace!("running {direction} at {cur_pos} {translation}: go {new_direction} on {dst_pos}"); + trace!("running {direction} at {cur_pos} {translation}: go {new_direction}"); } else if input.just_pressed(KeyCode::ArrowRight) { - let (mut transform, direction) = query.into_inner(); + let (mut transform, mut head, direction) = query.into_inner(); let translation = transform.translation.clone(); let cur_pos = Position::from(&*transform); - let mut dst_pos = direction.dst_pos(&*transform); let mut new_direction = direction.clone(); new_direction.rotate_right(); - if !snake_path.push(dst_pos, new_direction) { - trace!("try 1 failed: cannot push [{dst_pos} => {new_direction}] into SnakePath"); - dst_pos.shift(&direction); - if !snake_path.push(dst_pos, new_direction) { - trace!("try 2 failed: cannot push [{dst_pos} => {new_direction}] into SnakePath"); - return; - } - } + head.0 = new_direction; transform.rotation = new_direction.orientation(); - trace!("running {direction} at {cur_pos} {translation}: go {new_direction} on {dst_pos}"); + trace!("running {direction} at {cur_pos} {translation}: go {new_direction}"); } } -/// Система движения змеи -pub fn snake_moving( - time: Res>, - mut snake_path: ResMut, - query: Query<(&mut Transform, &mut Direction, &Velocity, Entity, Has), Or<(With, With, With)>>, - mut commands: Commands, +fn moving( + velocity: &f32, + time: &Time, + direction: &mut Direction, + mut transform: &mut Transform, + entity: Entity, + commands: &mut Commands, + switch_direction: impl FnOnce(Position) -> Option, ) { - let _span = trace_span!("snake_moving").entered(); - for (mut transform, mut direction, Velocity(velocity), entity, has_end) in query.into_iter() { - // первая часть движения - до границы сетки - let delta = velocity * time.delta_secs(); - let src = direction.get_coord(&transform); + // первая часть движения - до границы сетки + let delta = velocity * time.delta_secs(); + let src = direction.get_coord(&transform); + let sign = direction.get_change_sign(); + let dst = src + sign * delta; + let mut splited = split_by_grid(src, dst, GRID_SIZE as f32).into_iter(); + let first = splited.next().unwrap_or((0.0, 0.0)); + direction.set_coord(&mut transform, first.0); + + // вторая часть движения - после границы сетки + if let Some(second) = splited.next() { + let delta = second.1.abs(); + let pos = Position::from(&*transform); + if let Some(dir) = switch_direction(pos) { + trace!("set new direction for {entity} in {pos} --> {dir}"); + *direction = dir; + } + let src = direction.get_coord(&*transform); let sign = direction.get_change_sign(); let dst = src + sign * delta; - let mut splited = split_by_grid(src, dst, GRID_SIZE as f32).into_iter(); - let first = splited.next().unwrap_or((0.0, 0.0)); - direction.set_coord(&mut transform, first.0); + direction.set_coord(&mut transform, dst); - // вторая часть движения - после границы сетки - if let Some(second) = splited.next() { - let delta = second.1.abs(); - let pos = Position::from(&*transform); - if let Some(dir) = snake_path.peek(&pos) { - trace!("set new direction for {entity} in {pos} --> {dir}"); - *direction = dir.clone(); - } - let src = direction.get_coord(&*transform); - let sign = direction.get_change_sign(); - let dst = src + sign * delta; - direction.set_coord(&mut transform, dst); - - // создать событие прохождения границы сетки - commands.entity(entity).trigger(|e| GridBarier::new(e, pos, (&*direction).clone())); - - // если это кончик хвоста, то нужно очистить [SnakePath] за собой - if has_end { - snake_path.drop(&pos); - } - } + // создать событие прохождения границы сетки + commands.entity(entity).trigger(|e| GridBarier::new(e, pos, (&*direction).clone())); + } +} + +pub fn head_moving( + query: Query<(&mut Transform, &mut Direction, &Velocity, &Head, Entity)>, + time: Res>, + mut snake_path: ResMut, + mut commands: Commands, +) { + let _span = trace_span!("head_moving").entered(); + for (mut transform, mut direction, Velocity(velocity), head, entity) in query.into_iter() { + let next_dir = head.0.clone(); + let snake_path = &mut snake_path; + let switch_direction = move |pos: Position| { + snake_path.insert(pos.clone(), next_dir.clone()); + Some(next_dir) + }; + moving(velocity, &time, &mut direction, &mut transform, entity, &mut commands, switch_direction); + } +} + +pub fn snake_moving( + query: Query<(&mut Transform, &mut Direction, &Velocity, Entity), (With, Without)>, + time: Res>, + snake_path: Res, + mut commands: Commands, +) { + let _span = trace_span!("off_head_snake_moving").entered(); + for (mut transform, mut direction, Velocity(velocity), entity) in query.into_iter() { + let snake_path = &snake_path; + let switch_direction = move |pos: Position| snake_path.get(&pos).cloned(); + moving(velocity, &time, &mut direction, &mut transform, entity, &mut commands, switch_direction); } } diff --git a/snake-game/src/lib.rs b/snake-game/src/lib.rs index 27967f2..3d9b088 100644 --- a/snake-game/src/lib.rs +++ b/snake-game/src/lib.rs @@ -5,6 +5,6 @@ mod observers; mod startup; mod tools; -pub use gameplay::{head_rotation, snake_moving}; +pub use gameplay::{head_moving, head_rotation, snake_moving}; pub use startup::startup; pub use tools::define_log_layer; diff --git a/snake-game/src/main.rs b/snake-game/src/main.rs index 4fdefb5..c3dd902 100644 --- a/snake-game/src/main.rs +++ b/snake-game/src/main.rs @@ -1,5 +1,5 @@ use bevy::prelude::*; -use snake_game::{head_rotation, snake_moving, startup}; +use snake_game::{head_moving, head_rotation, snake_moving, startup}; fn main() { App::new() @@ -33,7 +33,7 @@ fn main() { ), ) .add_systems(Update, (head_rotation,)) - .add_systems(FixedUpdate, (snake_moving,)) + .add_systems(FixedUpdate, (head_moving, snake_moving)) .add_systems( PostUpdate, ( diff --git a/snake-game/src/observers.rs b/snake-game/src/observers.rs index 9cbf6a4..b403660 100644 --- a/snake-game/src/observers.rs +++ b/snake-game/src/observers.rs @@ -14,3 +14,10 @@ pub fn schedule_set_velocity(tail: Entity) -> impl Fn(On, Commands, } } } + +/// Очистка [SnakePath] от старых значений +pub fn clean_snake_path(event: On, query: Query<&End>, mut snake_path: ResMut) { + if query.contains(event.entity) { + snake_path.remove(&event.position); + } +} diff --git a/snake-game/src/startup.rs b/snake-game/src/startup.rs index 5d7eb4c..aa5b5c9 100644 --- a/snake-game/src/startup.rs +++ b/snake-game/src/startup.rs @@ -19,11 +19,13 @@ fn spawn_snake(commands: &mut Commands) { commands.insert_resource(SnakePath::default()); + commands.spawn(Observer::new(crate::observers::clean_snake_path)); + commands.spawn(Observer::new(crate::debug::trace_snake_moving)); let head = commands .spawn(( - Head, + Head(Direction::default()), Direction::default(), Velocity(SNAKE_VELOCITY as f32), Sprite::from_color(Color::srgb(1., 1., 1.), vec2(HEAD_SIZE as f32, HEAD_SIZE as f32)),