From 1ad9af26c9bc264a27c6ee11b3301aeac797164c Mon Sep 17 00:00:00 2001 From: Alexander Baranov Date: Fri, 5 Jun 2026 10:01:48 +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/debug.rs | 6 +++-- snake-game/src/domain.rs | 34 ++++++++++++++++++------ snake-game/src/gameplay.rs | 44 ++++++++++++++++++------------- snake-game/src/observers.rs | 40 +++------------------------- snake-game/src/startup.rs | 22 ++++++---------- snake-game/tests/rust_learning.rs | 28 +++++++++++++++----- 6 files changed, 87 insertions(+), 87 deletions(-) diff --git a/snake-game/src/debug.rs b/snake-game/src/debug.rs index 8bfa433..8d72744 100644 --- a/snake-game/src/debug.rs +++ b/snake-game/src/debug.rs @@ -59,8 +59,9 @@ pub fn spawn_grid(commands: &mut Commands) { let grid = commands.spawn((Grid, Transform::default(), Visibility::default())).id(); for x in -20..=20 { + let color = if x % 5 == 0 { Color::srgb(1.0, 0.2, 0.5) } else { Color::srgb(1.0, 1.0, 0.5) }; commands.spawn(( - Sprite::from_color(Color::srgb(1.0, 1.0, 0.5), vec2(0.5, 2000.0)), + Sprite::from_color(color, vec2(0.5, 2000.0)), Transform { translation: vec3(x as f32 * GRID_SIZE as f32, 0.0, -1.0), ..Default::default() @@ -69,8 +70,9 @@ pub fn spawn_grid(commands: &mut Commands) { )); } for y in -20..=20 { + let color = if y % 5 == 0 { Color::srgb(1.0, 0.2, 0.5) } else { Color::srgb(1.0, 1.0, 0.5) }; commands.spawn(( - Sprite::from_color(Color::srgb(1.0, 1.0, 0.5), vec2(2000.0, 0.5)), + Sprite::from_color(color, vec2(2000.0, 0.5)), Transform { translation: vec3(0.0, y as f32 * GRID_SIZE as f32, -1.0), ..Default::default() diff --git a/snake-game/src/domain.rs b/snake-game/src/domain.rs index 2ae2e37..05b6df1 100644 --- a/snake-game/src/domain.rs +++ b/snake-game/src/domain.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Debug}; +use std::{collections::HashMap, f32::consts::PI, fmt::Debug}; use bevy::prelude::*; @@ -24,12 +24,13 @@ pub struct End; #[derive(EntityEvent)] pub struct GridBarier { pub entity: Entity, - pub pos: Position, + pub position: Position, + pub direction: Direction, } impl GridBarier { - pub fn new(entity: Entity, pos: Position) -> Self { - Self { entity, pos } + pub fn new(entity: Entity, position: Position, direction: Direction) -> Self { + Self { entity, position, direction } } } @@ -98,6 +99,16 @@ impl Direction { }; pos } + + /// Получить ориентацию в пространстве + pub fn orientation(&self) -> Quat { + match self { + Direction::Up => Quat::default(), + Direction::Left => Quat::from_rotation_z(PI / 2.0), + Direction::Down => Quat::from_rotation_z(PI), + Direction::Right => Quat::from_rotation_z(-PI / 2.0), + } + } } impl Default for Direction { @@ -106,14 +117,21 @@ impl Default for Direction { } } +impl std::fmt::Display for Direction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Direction::Up => write!(f, "Up"), + Direction::Left => write!(f, "Left"), + Direction::Down => write!(f, "Down"), + Direction::Right => write!(f, "Right"), + } + } +} + /// Скорость движения #[derive(Component)] pub struct Velocity(pub f32); -/// Инерция -#[derive(Component, Default)] -pub struct Inert(pub Direction); - /// Путь змеи #[derive(Resource, Default, Debug, Deref, DerefMut)] pub struct SnakePath(HashMap); diff --git a/snake-game/src/gameplay.rs b/snake-game/src/gameplay.rs index 2accd67..fbf6b57 100644 --- a/snake-game/src/gameplay.rs +++ b/snake-game/src/gameplay.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use crate::{ - domain::{Direction, End, GRID_SIZE, Head, SnakePath, Tail, Velocity}, + domain::{Direction, End, GRID_SIZE, GridBarier, Head, SnakePath, Tail, Velocity}, tools::{Position, split_by_grid}, }; @@ -11,32 +11,22 @@ pub fn head_rotation( query: Single<(&mut Transform, &Direction), With>, mut snake_path: ResMut, ) { - use core::f32::consts::PI; - if input.just_pressed(KeyCode::ArrowLeft) { let (mut transform, direction) = query.into_inner(); let next_pos = direction.next_pos(&*transform); let mut direction = direction.clone(); direction.rotate_left(); - if let Some(prev_direction) = snake_path.insert(next_pos, direction) { - if direction != prev_direction { - transform.rotate_z(PI); - } - } else { - transform.rotate_z(PI / 2.0); - } + snake_path.insert(next_pos, direction); + transform.rotation = direction.orientation(); + trace!("go {direction} on {next_pos}"); } else if input.just_pressed(KeyCode::ArrowRight) { let (mut transform, direction) = query.into_inner(); let next_pos = direction.next_pos(&*transform); let mut direction = direction.clone(); direction.rotate_right(); - if let Some(prev_direction) = snake_path.insert(next_pos, direction) { - if direction != prev_direction { - transform.rotate_z(-PI); - } - } else { - transform.rotate_z(-PI / 2.0); - } + snake_path.insert(next_pos, direction); + transform.rotation = direction.orientation(); + trace!("go {direction} on {next_pos}"); } } @@ -44,9 +34,11 @@ pub fn head_rotation( pub fn snake_moving( time: Res>, snake_path: Res, - query: Query<(&mut Transform, &mut Direction, &Velocity), Or<(With, With, With)>>, + query: Query<(&mut Transform, &mut Direction, &Velocity, Entity, Has), Or<(With, With, With)>>, + mut commands: Commands, ) { - for (mut transform, mut direction, Velocity(velocity)) in query.into_iter() { + 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 sign = direction.get_change_sign(); @@ -54,6 +46,8 @@ pub fn snake_moving( 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); @@ -65,6 +59,18 @@ pub fn snake_moving( 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 { + commands.queue(move |world: &mut World| { + if let Some(mut snake_path) = world.get_resource_mut::() { + snake_path.remove(&pos); + } + }); + } } } } diff --git a/snake-game/src/observers.rs b/snake-game/src/observers.rs index 37046f8..82b7c3f 100644 --- a/snake-game/src/observers.rs +++ b/snake-game/src/observers.rs @@ -1,50 +1,16 @@ -use crate::domain::*; +use crate::{domain::*, tools::Position}; use bevy::prelude::*; -#[allow(unused)] -/// Слушатель событий [GridBarier] для хвоста, обновляющий направление его движения из ресурса [SnakePath] и позволяющий следовать за головой -pub fn tail_observer(event: On, mut query: Query<(&mut Direction, &Transform)>, snake_path: Res) { - let entity = event.entity; - if event.observer() == entity { - if let Ok((mut direction, transform)) = query.get_mut(event.entity) { - /* - if let Some((position, new_direction)) = snake_path.pick(transform) { - trace!("change direction for {entity:?} in {position:?} to {new_direction:?}"); - *direction = new_direction; - } - */ - } - } -} - -/// Слушатель событий [GridBarier] для конца змеи, обновляющий направление его движения из ресурса [SnakePath] и удаляющий полученные значения из [SnakePath] -pub fn cleanup_snake_path(event: On, mut commands: Commands, query: Query<&Transform, With>) { - if let Ok(transform) = query.get(event.entity) { - // trace!("schedule cleaning SnakePath for {:?}", transform); - let transform = transform.clone(); - commands.queue(move |world: &mut World| { - let mut snake_path = world.resource_mut::(); - /* - if let Some((pos, dir)) = snake_path.drop(&transform) { - let map = snake_path.debug(); - trace!("removed entry from SnakePath: {pos:?} => {dir:?}\n\tsnake_path: {map:?}"); - } - */ - }); - } -} - /// Запланировать установку скорости движения [Velocity] при следующем событии [GridBarier] pub fn schedule_set_velocity(tail: Entity) -> impl Fn(On, Commands, Query<(&mut Velocity, &Transform)>) { move |event: On, mut commands: Commands, mut query: Query<(&mut Velocity, &Transform)>| { if let Ok((mut velocity, transform)) = query.get_mut(tail) { - /* - if event.dst_pos != transform.into() { + let position = Position::from(transform); + if event.position != position { velocity.0 = SNAKE_VELOCITY as f32; commands.entity(event.observer()).despawn(); } - */ } } } diff --git a/snake-game/src/startup.rs b/snake-game/src/startup.rs index aef3734..901d0e1 100644 --- a/snake-game/src/startup.rs +++ b/snake-game/src/startup.rs @@ -1,13 +1,13 @@ use bevy::prelude::*; use crate::{ - domain::{End, SnakePath, Tail}, - observers::{cleanup_snake_path, schedule_set_velocity, tail_observer}, + domain::{End, GridBarier, SnakePath, Tail}, + observers::schedule_set_velocity, }; /// Инициализация игрового мира -pub fn startup(mut commands: Commands, mut timer: ResMut>) { - timer.set_timestep_hz(32.0); +pub fn startup(mut commands: Commands, mut time: ResMut>) { + time.set_timestep_hz(32.0); spawn_snake(&mut commands); crate::debug::spawn_grid(&mut commands); commands.spawn(Camera2d); @@ -15,25 +15,21 @@ pub fn startup(mut commands: Commands, mut timer: ResMut>) { /// Создать голову змеи fn spawn_snake(commands: &mut Commands) { - use crate::domain::{Direction, HEAD_SIZE, Head, Inert, SNAKE_VELOCITY, Velocity}; + use crate::domain::{Direction, HEAD_SIZE, Head, SNAKE_VELOCITY, Velocity}; commands.insert_resource(SnakePath::default()); - commands.spawn(Observer::new(cleanup_snake_path)); - /* commands.spawn(Observer::new(|e: On| { let entity = e.entity; - let src = e.src_pos; - let dst = e.dst_pos; - trace!("...<{entity:?}> moving {src:>16} => {dst:>16}"); + let position = e.position; + let direction = e.direction; + trace!("...<{entity:?}> is moving on {position} {direction:?}"); })); - */ let head = commands .spawn(( Head, Direction::default(), - Inert::default(), Velocity(SNAKE_VELOCITY as f32), Sprite::from_color(Color::srgb(1., 1., 1.), vec2(HEAD_SIZE as f32, HEAD_SIZE as f32)), Transform { @@ -64,14 +60,12 @@ fn spawn_snake(commands: &mut Commands) { Tail, End, Direction::default(), - Inert::default(), Velocity(0.0), Sprite::from_color(Color::srgb(0.7, 0.7, 0.7), vec2(HEAD_SIZE as f32 * 0.8, HEAD_SIZE as f32 * 0.8)), Transform { translation: vec3(0.0, 0.0, 0.0), ..Default::default() }, - Observer::new(tail_observer), )) .id(); commands.entity(head).observe(schedule_set_velocity(tail)); diff --git a/snake-game/tests/rust_learning.rs b/snake-game/tests/rust_learning.rs index 74cbecc..5162f9d 100644 --- a/snake-game/tests/rust_learning.rs +++ b/snake-game/tests/rust_learning.rs @@ -1,6 +1,6 @@ use std::f32::consts::PI; -use bevy::{math::Quat, transform::components::Transform}; +use bevy::transform::components::Transform; #[test] fn test_saturating_sub() { @@ -52,10 +52,24 @@ fn test_ranges() { fn test_transform() { let mut transform = Transform::default(); dbg!(transform); - transform.rotate_z(PI / 2.0); - transform.rotate_z(PI / 2.0); - transform.rotate_z(PI / 2.0); - transform.rotate_z(PI / 2.0); - dbg!(transform); - let mut quat = Quat::default(); + dbg!({ + transform.rotate_z(PI / 2.0); + transform + }); + dbg!({ + transform.rotate_z(PI / 2.0); + transform + }); + dbg!({ + transform.rotate_z(PI / 2.0); + transform + }); + dbg!({ + transform.rotate_z(PI / 2.0); + transform + }); + dbg!({ + transform.rotate_z(PI * 2.0); + transform + }); }