Проектная работа - WIP
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub const FRAMES_PER_SECOND: u8 = 24;
|
||||
@@ -5,16 +7,28 @@ pub const GRID_SIZE: u8 = 48;
|
||||
pub const HEAD_SIZE: u8 = (GRID_SIZE as f32 * 1.1) as u8;
|
||||
pub const SNAKE_VELOCITY: u8 = (GRID_SIZE as f32 / 1.0) as u8;
|
||||
|
||||
/// Координатная сетка
|
||||
#[derive(Component)]
|
||||
pub struct Grid;
|
||||
|
||||
/// Голова змеи
|
||||
#[derive(Component)]
|
||||
pub struct Head;
|
||||
|
||||
/// Сегмент змеиного хвоста
|
||||
#[derive(Component)]
|
||||
pub struct Tail(pub u16);
|
||||
pub struct Tail;
|
||||
|
||||
/// Конец змеиного хвоста
|
||||
#[derive(Component)]
|
||||
pub struct End;
|
||||
|
||||
/// Событие прохождения границы сетки
|
||||
#[derive(EntityEvent)]
|
||||
pub struct GridBarier(pub Entity);
|
||||
|
||||
/// Направление движения
|
||||
#[derive(Component, PartialEq, Clone, Copy)]
|
||||
#[derive(Component, PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Left,
|
||||
@@ -81,3 +95,51 @@ pub struct Velocity(pub f32);
|
||||
/// Инерция
|
||||
#[derive(Component, Default)]
|
||||
pub struct Inert(pub Direction);
|
||||
|
||||
/// Путь змеи
|
||||
#[derive(Resource, Default, Debug)]
|
||||
pub struct SnakePath(HashMap<(i16, i16), Direction>);
|
||||
|
||||
impl SnakePath {
|
||||
fn to_key(transform: &Transform) -> (i16, i16) {
|
||||
(
|
||||
(transform.translation.x / GRID_SIZE as f32).floor() as i16,
|
||||
(transform.translation.y / GRID_SIZE as f32).floor() as i16,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, transform: &Transform, direction: Direction) {
|
||||
let key = SnakePath::to_key(transform);
|
||||
self.0.insert(key, direction.clone());
|
||||
}
|
||||
|
||||
pub fn pick(&self, transform: &Transform) -> Option<Direction> {
|
||||
let key = SnakePath::to_key(transform);
|
||||
self.0.get(&key).cloned()
|
||||
}
|
||||
|
||||
pub fn pop(&mut self, transform: &Transform) -> Option<Direction> {
|
||||
let key = SnakePath::to_key(transform);
|
||||
self.0.remove(&key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::domain::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_snake_path() {
|
||||
let mut snake_path = SnakePath(HashMap::default());
|
||||
snake_path.push(&Transform::default(), Direction::default());
|
||||
assert_eq!(snake_path.0.len(), 1);
|
||||
|
||||
assert!(snake_path.pick(&Transform::default()) == Some(Direction::default()));
|
||||
assert_eq!(snake_path.0.len(), 1);
|
||||
|
||||
assert!(snake_path.pop(&Transform::default()) == Some(Direction::default()));
|
||||
assert_eq!(snake_path.0.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::domain::{Direction, GRID_SIZE, Head, Inert, Velocity};
|
||||
use crate::domain::{Direction, GRID_SIZE, GridBarier, Head, Inert, SnakePath, Velocity};
|
||||
|
||||
/// Система поворота головы змеи
|
||||
pub fn head_rotation(
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
query: Single<(&mut Transform, &mut Direction), With<Head>>,
|
||||
mut snake_path: ResMut<SnakePath>,
|
||||
) {
|
||||
use core::f32::consts::PI;
|
||||
|
||||
let (mut transform, mut direction) = query.into_inner();
|
||||
|
||||
if input.just_pressed(KeyCode::ArrowLeft) {
|
||||
transform.rotate_z(PI / 2.);
|
||||
transform.rotate_z(PI / 2.0);
|
||||
direction.rotate_left();
|
||||
snake_path.push(&transform, direction.clone());
|
||||
}
|
||||
if input.just_pressed(KeyCode::ArrowRight) {
|
||||
transform.rotate_z(-PI / 2.);
|
||||
transform.rotate_z(-PI / 2.0);
|
||||
direction.rotate_right();
|
||||
snake_path.push(&transform, direction.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn moving(time: Res<Time>, query: Query<(&mut Transform, &mut Inert, &Direction, &Velocity)>) {
|
||||
for (mut transform, mut inert, direction, Velocity(velocity)) in query.into_iter() {
|
||||
/// Системе движения для всех движущихся сущностей
|
||||
pub fn moving(
|
||||
time: Res<Time>,
|
||||
query: Query<(&mut Transform, &mut Inert, &Direction, &Velocity, Entity)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (mut transform, mut inert, direction, Velocity(velocity), entity) in query.into_iter() {
|
||||
let delta = velocity * time.delta_secs();
|
||||
let src = inert.0.get_coord(&transform);
|
||||
let sign = inert.0.get_change_sign();
|
||||
@@ -36,6 +45,7 @@ pub fn moving(time: Res<Time>, query: Query<(&mut Transform, &mut Inert, &Direct
|
||||
let sign = inert.0.get_change_sign();
|
||||
let dst = src + sign * delta;
|
||||
inert.0.set_coord(&mut transform, dst);
|
||||
commands.trigger(GridBarier(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +67,9 @@ fn split_by_grid(src: f32, dst: f32, grid: f32) -> Vec<(f32, f32)> {
|
||||
let mut src: f32 = src;
|
||||
for step in old_grid_right as i32..new_grid_right as i32 {
|
||||
let step = step as f32 * grid;
|
||||
output.push((step, step - src));
|
||||
if step != src {
|
||||
output.push((step, step - src));
|
||||
}
|
||||
src = step;
|
||||
}
|
||||
output.push((dst, dst - src));
|
||||
@@ -65,7 +77,9 @@ fn split_by_grid(src: f32, dst: f32, grid: f32) -> Vec<(f32, f32)> {
|
||||
let mut dst: f32 = dst;
|
||||
for step in new_grid_right as i32..old_grid_right as i32 {
|
||||
let step = step as f32 * grid;
|
||||
output.push((dst, dst - step));
|
||||
if step != dst {
|
||||
output.push((dst, dst - step));
|
||||
}
|
||||
dst = step;
|
||||
}
|
||||
output.push((dst, dst - src));
|
||||
@@ -127,5 +141,11 @@ mod test {
|
||||
// сценарии с нулевой сеткой
|
||||
assert_eq!(split_by_grid(1.0, 4.0, 0.0), vec![(4.0, 3.0)]);
|
||||
assert_eq!(split_by_grid(4.0, 1.0, 0.0), vec![(1.0, -3.0)]);
|
||||
|
||||
// Сценарии со стартом в узле сетки
|
||||
assert_eq!(split_by_grid(0.0, 1.5, 5.0), vec![(1.5, 1.5)]);
|
||||
assert_eq!(split_by_grid(5.0, 7.0, 5.0), vec![(7.0, 2.0)]);
|
||||
assert_eq!(split_by_grid(0.0, -1.5, 5.0), vec![(-1.5, -1.5)]);
|
||||
assert_eq!(split_by_grid(5.0, 3.0, 5.0), vec![(3.0, -2.0)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,4 +42,4 @@ mod gameplay;
|
||||
mod startup;
|
||||
|
||||
pub use gameplay::{head_rotation, moving};
|
||||
pub use startup::{setup_frame_rate, startup};
|
||||
pub use startup::{print_all_entities, setup_frame_rate, startup};
|
||||
|
||||
@@ -19,6 +19,7 @@ fn main() {
|
||||
.insert_resource(bevy::winit::WinitSettings::desktop_app())
|
||||
.insert_resource(ClearColor(Color::srgb(0.3, 0.6, 0.8)))
|
||||
.add_systems(Startup, (startup, setup_frame_rate))
|
||||
// .add_systems(PostStartup, snake_game::print_all_entities)
|
||||
.add_systems(Update, (head_rotation, moving))
|
||||
.run();
|
||||
}
|
||||
|
||||
+46
-19
@@ -1,8 +1,7 @@
|
||||
use bevy::{ecs::observer::ObservedBy, prelude::*, winit::WinitSettings};
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy::{prelude::*, winit::WinitSettings};
|
||||
|
||||
use crate::domain::Tail;
|
||||
use crate::domain::{End, Grid, GridBarier, SnakePath, Tail};
|
||||
|
||||
/// Настройка частоты кадров
|
||||
pub fn setup_frame_rate(mut winit: ResMut<WinitSettings>) {
|
||||
@@ -25,6 +24,8 @@ pub fn startup(mut commands: Commands) {
|
||||
fn spawn_snake(commands: &mut Commands) {
|
||||
use crate::domain::{Direction, HEAD_SIZE, Head, Inert, SNAKE_VELOCITY, Velocity};
|
||||
|
||||
commands.insert_resource(SnakePath::default());
|
||||
|
||||
let head = commands
|
||||
.spawn((
|
||||
Head,
|
||||
@@ -64,43 +65,69 @@ fn spawn_snake(commands: &mut Commands) {
|
||||
ChildOf(head),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Tail(0),
|
||||
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()
|
||||
let tail = commands
|
||||
.spawn((
|
||||
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()
|
||||
},
|
||||
))
|
||||
.id();
|
||||
|
||||
commands.entity(head).observe(
|
||||
move |event: On<GridBarier>,
|
||||
mut commands: Commands,
|
||||
mut query: Query<&mut Velocity, With<Tail>>| {
|
||||
if let Ok(mut velocity) = query.get_mut(tail) {
|
||||
velocity.0 = SNAKE_VELOCITY as f32;
|
||||
}
|
||||
commands.entity(event.0).remove::<ObservedBy>();
|
||||
},
|
||||
// ChildOf(head),
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
/// Создать координатную сетку
|
||||
fn spawn_grid(commands: &mut Commands) {
|
||||
use crate::domain::GRID_SIZE;
|
||||
|
||||
let grid = commands
|
||||
.spawn((Grid, Transform::default(), Visibility::default()))
|
||||
.id();
|
||||
|
||||
for x in -20..=20 {
|
||||
commands.spawn((
|
||||
Sprite::from_color(Color::srgb(1.0, 1.0, 0.5), vec2(1.0, 2000.0)),
|
||||
Sprite::from_color(Color::srgb(1.0, 1.0, 0.5), vec2(0.5, 2000.0)),
|
||||
Transform {
|
||||
translation: vec3(x as f32 * GRID_SIZE as f32, 0.0, -1.0),
|
||||
..Default::default()
|
||||
},
|
||||
ChildOf(grid),
|
||||
));
|
||||
}
|
||||
for y in -20..=20 {
|
||||
commands.spawn((
|
||||
Sprite::from_color(Color::srgb(1.0, 1.0, 0.5), vec2(2000.0, 1.0)),
|
||||
Sprite::from_color(Color::srgb(1.0, 1.0, 0.5), vec2(2000.0, 0.5)),
|
||||
Transform {
|
||||
translation: vec3(0.0, y as f32 * GRID_SIZE as f32, -1.0),
|
||||
..Default::default()
|
||||
},
|
||||
ChildOf(grid),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// ДЛЯ ОТЛАДКИ - вывод всех сущностей мира
|
||||
pub fn print_all_entities(mut commands: Commands, query: Query<Entity>) {
|
||||
for entity in query {
|
||||
commands.entity(entity).log_components();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user