Проектная работа - MVP
This commit is contained in:
@@ -8,9 +8,9 @@ bevy = { version = "0.18", default-features = false, features = ["2d_bevy_render
|
|||||||
# bevy = { version = "0.18" }
|
# bevy = { version = "0.18" }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
rand = "0.10"
|
||||||
|
|
||||||
# for examples
|
# for examples
|
||||||
rand = "0.10"
|
|
||||||
rand_chacha = "0.10"
|
rand_chacha = "0.10"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ pub fn print_all_entities_by_press_enter(input: Res<ButtonInput<KeyCode>>, mut c
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Замедлить время и поставить на паузу
|
/// Замедлить время и поставить на паузу
|
||||||
pub fn set_initial_time_speed_to_slow_and_pause(mut time: ResMut<Time<Virtual>>) {
|
pub fn set_initial_time_speed_to_slow(mut time: ResMut<Time<Virtual>>) {
|
||||||
time.set_relative_speed(1.0 / 8.0);
|
time.set_relative_speed(1.0 / 8.0);
|
||||||
time.pause();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// обертка над функцией [crate::tools::split_by_grid] для ее отладки
|
/// обертка над функцией [crate::tools::split_by_grid] для ее отладки
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ pub use position::Position;
|
|||||||
|
|
||||||
pub const GRID_SIZE: u8 = 32;
|
pub const GRID_SIZE: u8 = 32;
|
||||||
pub const HEAD_SIZE: u8 = (GRID_SIZE as f32 * 1.1).ceil() as u8;
|
pub const HEAD_SIZE: u8 = (GRID_SIZE as f32 * 1.1).ceil() as u8;
|
||||||
pub const SNAKE_VELOCITY: u8 = (GRID_SIZE as f32 / 0.33).ceil() as u8;
|
pub const SNAKE_VELOCITY: u8 = (GRID_SIZE as f32 * 3.0).ceil() as u8;
|
||||||
pub const FIELD_SIZE: u8 = 20;
|
pub const FIELD_SIZE: u8 = 20;
|
||||||
|
pub const MAX_MEALS_ON_FIELD: u8 = 5;
|
||||||
|
pub const MEAL_SIZE: u8 = (GRID_SIZE as f32 * 0.75).ceil() as u8;
|
||||||
|
|
||||||
/// Голова змеи
|
/// Голова змеи
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
@@ -36,3 +38,13 @@ pub struct Velocity(pub f32);
|
|||||||
/// Еда
|
/// Еда
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Meal;
|
pub struct Meal;
|
||||||
|
|
||||||
|
/// Таймер респавна еды
|
||||||
|
#[derive(Resource, Deref, DerefMut)]
|
||||||
|
pub struct MealRespawnTimer(Timer);
|
||||||
|
|
||||||
|
impl MealRespawnTimer {
|
||||||
|
pub fn new(period: f32) -> Self {
|
||||||
|
Self(Timer::from_seconds(period, TimerMode::Repeating))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
/// Направление движения
|
/// Направление движения
|
||||||
#[derive(Component, PartialEq, Clone, Copy, Debug)]
|
#[derive(Component, PartialEq, Clone, Copy, Debug, Default)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
|
#[default]
|
||||||
Up,
|
Up,
|
||||||
Left,
|
Left,
|
||||||
Down,
|
Down,
|
||||||
@@ -67,12 +68,6 @@ impl Direction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Direction {
|
|
||||||
fn default() -> Self {
|
|
||||||
Direction::Up
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Direction {
|
impl std::fmt::Display for Direction {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use bevy::prelude::*;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use bevy::{color::palettes::tailwind::*, prelude::*};
|
||||||
|
use rand::RngExt;
|
||||||
|
|
||||||
use crate::{domain::*, tools::split_by_grid};
|
use crate::{domain::*, tools::split_by_grid};
|
||||||
|
|
||||||
@@ -7,19 +10,19 @@ fn moving(
|
|||||||
velocity: &f32,
|
velocity: &f32,
|
||||||
time: &Time<Fixed>,
|
time: &Time<Fixed>,
|
||||||
direction: &mut Direction,
|
direction: &mut Direction,
|
||||||
mut transform: &mut Transform,
|
transform: &mut Transform,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
switch_direction: impl FnOnce(Position) -> Option<Direction>,
|
switch_direction: impl FnOnce(Position) -> Option<Direction>,
|
||||||
) {
|
) {
|
||||||
// первая часть движения - до границы сетки
|
// первая часть движения - до границы сетки
|
||||||
let delta = velocity * time.delta_secs();
|
let delta = velocity * time.delta_secs();
|
||||||
let src = direction.get_coord(&transform);
|
let src = direction.get_coord(transform);
|
||||||
let sign = direction.get_change_sign();
|
let sign = direction.get_change_sign();
|
||||||
let dst = src + sign * delta;
|
let dst = src + sign * delta;
|
||||||
let mut splited = split_by_grid(src, dst, GRID_SIZE as f32).into_iter();
|
let mut splited = split_by_grid(src, dst, GRID_SIZE as f32).into_iter();
|
||||||
let first = splited.next().unwrap_or((0.0, 0.0));
|
let first = splited.next().unwrap_or((0.0, 0.0));
|
||||||
direction.set_coord(&mut transform, first.0);
|
direction.set_coord(transform, first.0);
|
||||||
|
|
||||||
// вторая часть движения - после границы сетки
|
// вторая часть движения - после границы сетки
|
||||||
if let Some(second) = splited.next() {
|
if let Some(second) = splited.next() {
|
||||||
@@ -32,10 +35,10 @@ fn moving(
|
|||||||
let src = direction.get_coord(&*transform);
|
let src = direction.get_coord(&*transform);
|
||||||
let sign = direction.get_change_sign();
|
let sign = direction.get_change_sign();
|
||||||
let dst = src + sign * delta;
|
let dst = src + sign * delta;
|
||||||
direction.set_coord(&mut transform, dst);
|
direction.set_coord(transform, dst);
|
||||||
|
|
||||||
// создать событие прохождения границы сетки
|
// создать событие прохождения границы сетки
|
||||||
commands.entity(entity).trigger(|e| GridBarier::new(e, pos, (&*direction).clone()));
|
commands.entity(entity).trigger(|e| GridBarier::new(e, pos, *direction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +51,12 @@ pub fn head_moving(
|
|||||||
) {
|
) {
|
||||||
let _span = trace_span!("head_moving").entered();
|
let _span = trace_span!("head_moving").entered();
|
||||||
for (mut transform, mut direction, Velocity(velocity), head, entity) in query.into_iter() {
|
for (mut transform, mut direction, Velocity(velocity), head, entity) in query.into_iter() {
|
||||||
let next_dir = head.0.clone();
|
let next_dir = head.0;
|
||||||
let dir = direction.clone();
|
let dir = *direction;
|
||||||
let snake_path = &mut snake_path;
|
let snake_path = &mut snake_path;
|
||||||
let switch_direction = move |pos: Position| {
|
let switch_direction = move |pos: Position| {
|
||||||
if next_dir != dir {
|
if next_dir != dir {
|
||||||
snake_path.insert(pos.clone(), next_dir.clone());
|
snake_path.insert(pos, next_dir);
|
||||||
Some(next_dir)
|
Some(next_dir)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -63,6 +66,7 @@ pub fn head_moving(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
/// Система движения тела змеи
|
/// Система движения тела змеи
|
||||||
pub fn snake_moving(
|
pub fn snake_moving(
|
||||||
query: Query<(&mut Transform, &mut Direction, &Velocity, Entity), (With<Tail>, Without<Head>)>,
|
query: Query<(&mut Transform, &mut Direction, &Velocity, Entity), (With<Tail>, Without<Head>)>,
|
||||||
@@ -77,3 +81,34 @@ pub fn snake_moving(
|
|||||||
moving(velocity, &time, &mut direction, &mut transform, entity, &mut commands, switch_direction);
|
moving(velocity, &time, &mut direction, &mut transform, entity, &mut commands, switch_direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Система спавна еды при необходимости
|
||||||
|
pub fn spawn_meal_if_needed(
|
||||||
|
time: Res<Time<Fixed>>, //
|
||||||
|
mut meal_timer: ResMut<MealRespawnTimer>,
|
||||||
|
query: Query<&Transform, With<Meal>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
if meal_timer.tick(time.elapsed()).just_finished() && query.count() < MAX_MEALS_ON_FIELD as usize {
|
||||||
|
let used_positions = query.iter().map(Position::from).collect::<HashSet<Position>>();
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
let pos = loop {
|
||||||
|
const ABS: i8 = (FIELD_SIZE as f32 / 2.0) as i8;
|
||||||
|
let rand_x = rng.random::<i8>() % ABS;
|
||||||
|
let rand_y = rng.random::<i8>() % ABS;
|
||||||
|
let rand_pos = Position::new(rand_x, rand_y);
|
||||||
|
if !used_positions.contains(&rand_pos) {
|
||||||
|
break rand_pos;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
commands.spawn((
|
||||||
|
Meal,
|
||||||
|
Sprite::from_color(YELLOW_400, vec2(MEAL_SIZE as f32, MEAL_SIZE as f32)), //
|
||||||
|
Transform {
|
||||||
|
translation: pos.translation().extend(0.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
trace!("new meal spawned at {pos}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ impl Plugin for SnakeGame {
|
|||||||
|
|
||||||
app.add_systems(Startup, crate::startup::setup_window);
|
app.add_systems(Startup, crate::startup::setup_window);
|
||||||
app.add_systems(Startup, crate::startup::setup_time);
|
app.add_systems(Startup, crate::startup::setup_time);
|
||||||
|
app.add_systems(Startup, crate::startup::pause_on_startup);
|
||||||
app.add_systems(Startup, crate::startup::startup);
|
app.add_systems(Startup, crate::startup::startup);
|
||||||
|
|
||||||
app.add_systems(Update, crate::update::head_rotation);
|
app.add_systems(Update, crate::update::head_rotation);
|
||||||
@@ -31,12 +32,13 @@ impl Plugin for SnakeGame {
|
|||||||
|
|
||||||
app.add_systems(FixedUpdate, crate::fixed_update::head_moving);
|
app.add_systems(FixedUpdate, crate::fixed_update::head_moving);
|
||||||
app.add_systems(FixedUpdate, crate::fixed_update::snake_moving);
|
app.add_systems(FixedUpdate, crate::fixed_update::snake_moving);
|
||||||
|
app.add_systems(FixedUpdate, crate::fixed_update::spawn_meal_if_needed);
|
||||||
|
|
||||||
// DEBUG:
|
// DEBUG:
|
||||||
|
|
||||||
app.add_systems(Startup, crate::debug::set_initial_time_speed_to_slow_and_pause);
|
// app.add_systems(Startup, crate::debug::set_initial_time_speed_to_slow);
|
||||||
// app.add_systems(PostStartup, crate::debug::print_all_entities);
|
// app.add_systems(PostStartup, crate::debug::print_all_entities);
|
||||||
app.add_systems(Update, crate::debug::grow_up_on_tab);
|
// app.add_systems(Update, crate::debug::grow_up_on_tab);
|
||||||
// app.add_systems(Update, crate::debug::print_all_entities_by_press_enter);
|
// app.add_systems(Update, crate::debug::print_all_entities_by_press_enter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub fn clean_snake_path(event: On<GridBarier>, query: Query<&End>, mut snake_pat
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Обработка события [GrowUp]
|
/// Обработка события [GrowUp]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn grow_up_snake(_: On<GrowUp>, mut commands: Commands, query: Single<(Entity, &Direction, &Transform, &mut Sprite), (With<Tail>, With<End>)>) {
|
pub fn grow_up_snake(_: On<GrowUp>, mut commands: Commands, query: Single<(Entity, &Direction, &Transform, &mut Sprite), (With<Tail>, With<End>)>) {
|
||||||
let (entity, direction, transform, mut sprite) = query.into_inner();
|
let (entity, direction, transform, mut sprite) = query.into_inner();
|
||||||
sprite.color = GRAY_300.into();
|
sprite.color = GRAY_300.into();
|
||||||
@@ -33,7 +34,7 @@ pub fn grow_up_snake(_: On<GrowUp>, mut commands: Commands, query: Single<(Entit
|
|||||||
.spawn((
|
.spawn((
|
||||||
Tail(entity),
|
Tail(entity),
|
||||||
End,
|
End,
|
||||||
direction.clone(),
|
*direction,
|
||||||
Velocity(0.0),
|
Velocity(0.0),
|
||||||
Sprite::from_color(YELLOW_500, vec2(HEAD_SIZE as f32 * 0.8, HEAD_SIZE as f32 * 0.8)),
|
Sprite::from_color(YELLOW_500, vec2(HEAD_SIZE as f32 * 0.8, HEAD_SIZE as f32 * 0.8)),
|
||||||
Transform {
|
Transform {
|
||||||
@@ -46,6 +47,7 @@ pub fn grow_up_snake(_: On<GrowUp>, mut commands: Commands, query: Single<(Entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Обработка столкновений
|
/// Обработка столкновений
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn handle_collisions(
|
pub fn handle_collisions(
|
||||||
event: On<Collision>, //
|
event: On<Collision>, //
|
||||||
query: Query<(&mut Velocity, &mut Sprite), Or<(With<Head>, With<Tail>, With<End>)>>,
|
query: Query<(&mut Velocity, &mut Sprite), Or<(With<Head>, With<Tail>, With<End>)>>,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub fn check_bounds_colisions(
|
|||||||
let (head_transform,) = head.into_inner();
|
let (head_transform,) = head.into_inner();
|
||||||
let (x, y) = (head_transform.translation.x, head_transform.translation.y);
|
let (x, y) = (head_transform.translation.x, head_transform.translation.y);
|
||||||
const SIDE: f32 = (FIELD_SIZE as f32 / 2.0) * GRID_SIZE as f32;
|
const SIDE: f32 = (FIELD_SIZE as f32 / 2.0) * GRID_SIZE as f32;
|
||||||
if x < -SIDE || x > SIDE || y < -SIDE || y > SIDE {
|
if !(-SIDE..=SIDE).contains(&x) || !(-SIDE..=SIDE).contains(&y) {
|
||||||
commands.trigger(Collision::Bounds);
|
commands.trigger(Collision::Bounds);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use bevy::{color::palettes::tailwind::*, prelude::*};
|
use bevy::{color::palettes::tailwind::*, prelude::*};
|
||||||
|
|
||||||
use crate::domain::{FIELD_SIZE, GRID_SIZE, SnakePath};
|
use crate::domain::{FIELD_SIZE, GRID_SIZE, MealRespawnTimer, SnakePath};
|
||||||
|
|
||||||
/// Настройка игрового окна
|
/// Настройка игрового окна
|
||||||
pub fn setup_window(mut window: Single<&mut Window>) {
|
pub fn setup_window(mut window: Single<&mut Window>) {
|
||||||
@@ -15,10 +15,16 @@ pub fn setup_time(mut time: ResMut<Time<Fixed>>) {
|
|||||||
time.set_timestep_hz(32.0);
|
time.set_timestep_hz(32.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Начать с паузы
|
||||||
|
pub fn pause_on_startup(mut time: ResMut<Time<Virtual>>) {
|
||||||
|
time.pause();
|
||||||
|
}
|
||||||
|
|
||||||
/// Инициализация игрового мира
|
/// Инициализация игрового мира
|
||||||
pub fn startup(mut commands: Commands) {
|
pub fn startup(mut commands: Commands) {
|
||||||
commands.insert_resource(ClearColor(SKY_600.into()));
|
commands.insert_resource(ClearColor(SKY_600.into()));
|
||||||
commands.insert_resource(SnakePath::default());
|
commands.insert_resource(SnakePath::default());
|
||||||
|
commands.insert_resource(MealRespawnTimer::new(2.0));
|
||||||
|
|
||||||
commands.spawn(Observer::new(crate::observers::clean_snake_path));
|
commands.spawn(Observer::new(crate::observers::clean_snake_path));
|
||||||
commands.spawn(Observer::new(crate::observers::grow_up_snake));
|
commands.spawn(Observer::new(crate::observers::grow_up_snake));
|
||||||
@@ -34,6 +40,6 @@ pub fn startup(mut commands: Commands) {
|
|||||||
// commands.spawn(Observer::new(crate::debug::trace_collisions));
|
// commands.spawn(Observer::new(crate::debug::trace_collisions));
|
||||||
// commands.spawn(Observer::new(crate::debug::trace_snake_moving));
|
// commands.spawn(Observer::new(crate::debug::trace_snake_moving));
|
||||||
|
|
||||||
crate::debug::spawn_grid(&mut commands);
|
// crate::debug::spawn_grid(&mut commands);
|
||||||
crate::debug::spawn_some_meal(&mut commands);
|
// crate::debug::spawn_some_meal(&mut commands);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub fn define_log_layer() -> Option<bevy::log::BoxedFmtLayer> {
|
|||||||
.with_line_number(false)
|
.with_line_number(false)
|
||||||
.with_filter(targets.clone())
|
.with_filter(targets.clone())
|
||||||
.boxed();
|
.boxed();
|
||||||
let file = match File::options().write(true).create(true).append(true).open("snake-game.log") {
|
let file = match File::options().create(true).append(true).open("snake-game.log") {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("No access to log file: {:?}", e);
|
eprintln!("No access to log file: {:?}", e);
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ pub fn head_rotation(
|
|||||||
) {
|
) {
|
||||||
if input.just_pressed(KeyCode::ArrowLeft) {
|
if input.just_pressed(KeyCode::ArrowLeft) {
|
||||||
let (mut transform, mut head, direction) = query.into_inner();
|
let (mut transform, mut head, direction) = query.into_inner();
|
||||||
let mut new_direction = direction.clone();
|
let mut new_direction = *direction;
|
||||||
new_direction.rotate_left();
|
new_direction.rotate_left();
|
||||||
head.0 = new_direction;
|
head.0 = new_direction;
|
||||||
transform.rotation = new_direction.orientation();
|
transform.rotation = new_direction.orientation();
|
||||||
} else if input.just_pressed(KeyCode::ArrowRight) {
|
} else if input.just_pressed(KeyCode::ArrowRight) {
|
||||||
let (mut transform, mut head, direction) = query.into_inner();
|
let (mut transform, mut head, direction) = query.into_inner();
|
||||||
let mut new_direction = direction.clone();
|
let mut new_direction = *direction;
|
||||||
new_direction.rotate_right();
|
new_direction.rotate_right();
|
||||||
head.0 = new_direction;
|
head.0 = new_direction;
|
||||||
transform.rotation = new_direction.orientation();
|
transform.rotation = new_direction.orientation();
|
||||||
} else if input.just_pressed(KeyCode::ArrowUp) {
|
} else if input.just_pressed(KeyCode::ArrowUp) {
|
||||||
let (mut transform, mut head, direction) = query.into_inner();
|
let (mut transform, mut head, direction) = query.into_inner();
|
||||||
head.0 = direction.clone();
|
head.0 = *direction;
|
||||||
transform.rotation = direction.orientation();
|
transform.rotation = direction.orientation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user