Проектная работа - WIP
This commit is contained in:
Generated
+51
@@ -725,6 +725,7 @@ dependencies = [
|
|||||||
"bevy_text",
|
"bevy_text",
|
||||||
"bevy_time",
|
"bevy_time",
|
||||||
"bevy_transform",
|
"bevy_transform",
|
||||||
|
"bevy_ui",
|
||||||
"bevy_utils",
|
"bevy_utils",
|
||||||
"bevy_window",
|
"bevy_window",
|
||||||
"bevy_winit",
|
"bevy_winit",
|
||||||
@@ -1114,6 +1115,38 @@ dependencies = [
|
|||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_ui"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1691a411014085e0d35f8bb8208e5f973edd7ace061a4b1c41c83de21579dc70"
|
||||||
|
dependencies = [
|
||||||
|
"accesskit",
|
||||||
|
"bevy_a11y",
|
||||||
|
"bevy_app",
|
||||||
|
"bevy_asset",
|
||||||
|
"bevy_camera",
|
||||||
|
"bevy_color",
|
||||||
|
"bevy_derive",
|
||||||
|
"bevy_ecs",
|
||||||
|
"bevy_image",
|
||||||
|
"bevy_input",
|
||||||
|
"bevy_input_focus",
|
||||||
|
"bevy_math",
|
||||||
|
"bevy_platform",
|
||||||
|
"bevy_reflect",
|
||||||
|
"bevy_sprite",
|
||||||
|
"bevy_text",
|
||||||
|
"bevy_transform",
|
||||||
|
"bevy_utils",
|
||||||
|
"bevy_window",
|
||||||
|
"derive_more",
|
||||||
|
"smallvec",
|
||||||
|
"taffy",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_utils"
|
name = "bevy_utils"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
@@ -2045,6 +2078,12 @@ dependencies = [
|
|||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grid"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "guillotiere"
|
name = "guillotiere"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -3547,6 +3586,18 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "taffy"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"grid",
|
||||||
|
"serde",
|
||||||
|
"slotmap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.18", default-features = false, features = ["2d_bevy_render", "bevy_log", "bevy_winit", "debug"] }
|
bevy = { version = "0.18", default-features = false, features = ["2d_bevy_render", "bevy_log", "bevy_winit", "bevy_text", "bevy_ui", "debug"] }
|
||||||
|
# bevy = { version = "0.18" }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
use bevy::{
|
||||||
|
platform::collections::{HashMap, HashSet},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use rand::{RngExt, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.init_resource::<SpatialIndex>()
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, (draw_shapes, handle_click))
|
||||||
|
// Observers are systems that run when an event is "triggered". This observer runs whenever
|
||||||
|
// `ExplodeMines` is triggered.
|
||||||
|
.add_observer(
|
||||||
|
|explode_mines: On<ExplodeMines>,
|
||||||
|
mines: Query<&Mine>,
|
||||||
|
index: Res<SpatialIndex>,
|
||||||
|
mut commands: Commands| {
|
||||||
|
// Access resources
|
||||||
|
for entity in index.get_nearby(explode_mines.pos) {
|
||||||
|
// Run queries
|
||||||
|
let mine = mines.get(entity).unwrap();
|
||||||
|
if mine.pos.distance(explode_mines.pos) < mine.size + explode_mines.radius {
|
||||||
|
// And queue commands, including triggering additional events
|
||||||
|
// Here we trigger the `Explode` event for entity `e`
|
||||||
|
commands.trigger(Explode { entity });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index.
|
||||||
|
.add_observer(on_add_mine)
|
||||||
|
// This observer runs whenever the `Mine` component is removed from an entity (including despawning it)
|
||||||
|
// and removes it from the spatial index.
|
||||||
|
.add_observer(on_remove_mine)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Mine {
|
||||||
|
pos: Vec2,
|
||||||
|
size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mine {
|
||||||
|
fn random(rand: &mut ChaCha8Rng) -> Self {
|
||||||
|
Mine {
|
||||||
|
pos: Vec2::new(
|
||||||
|
(rand.random::<f32>() - 0.5) * 1200.0,
|
||||||
|
(rand.random::<f32>() - 0.5) * 600.0,
|
||||||
|
),
|
||||||
|
size: 4.0 + rand.random::<f32>() * 16.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a normal [`Event`]. Any observer that watches for it will run when it is triggered.
|
||||||
|
#[derive(Event)]
|
||||||
|
struct ExplodeMines {
|
||||||
|
pos: Vec2,
|
||||||
|
radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [`EntityEvent`] is a specialized type of [`Event`] that can target a specific entity. In addition to
|
||||||
|
/// running normal "top level" observers when it is triggered (which target _any_ entity that Explodes), it will
|
||||||
|
/// also run any observers that target the _specific_ entity for that event.
|
||||||
|
#[derive(EntityEvent)]
|
||||||
|
struct Explode {
|
||||||
|
entity: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn(Camera2d);
|
||||||
|
commands.spawn((
|
||||||
|
Text::new(
|
||||||
|
"Click on a \"Mine\" to trigger it.\n\
|
||||||
|
When it explodes it will trigger all overlapping mines.",
|
||||||
|
),
|
||||||
|
Node {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
top: px(12),
|
||||||
|
left: px(12),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn(Mine::random(&mut rng))
|
||||||
|
// Observers can watch for events targeting a specific entity.
|
||||||
|
// This will create a new observer that runs whenever the Explode event
|
||||||
|
// is triggered for this spawned entity.
|
||||||
|
.observe(explode_mine);
|
||||||
|
|
||||||
|
// We want to spawn a bunch of mines. We could just call the code above for each of them.
|
||||||
|
// That would create a new observer instance for every Mine entity. Having duplicate observers
|
||||||
|
// generally isn't worth worrying about as the overhead is low. But if you want to be maximally efficient,
|
||||||
|
// you can reuse observers across entities.
|
||||||
|
//
|
||||||
|
// First, observers are actually just entities with the Observer component! The `observe()` functions
|
||||||
|
// you've seen so far in this example are just shorthand for manually spawning an observer.
|
||||||
|
let mut observer = Observer::new(explode_mine);
|
||||||
|
|
||||||
|
// As we spawn entities, we can make this observer watch each of them:
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let entity = commands.spawn(Mine::random(&mut rng)).id();
|
||||||
|
observer.watch_entity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// By spawning the Observer component, it becomes active!
|
||||||
|
commands.spawn(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_add_mine(add: On<Add, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
|
||||||
|
let mine = query.get(add.entity).unwrap();
|
||||||
|
let tile = (
|
||||||
|
(mine.pos.x / CELL_SIZE).floor() as i32,
|
||||||
|
(mine.pos.y / CELL_SIZE).floor() as i32,
|
||||||
|
);
|
||||||
|
index.map.entry(tile).or_default().insert(add.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove despawned mines from our index
|
||||||
|
fn on_remove_mine(remove: On<Remove, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
|
||||||
|
let mine = query.get(remove.entity).unwrap();
|
||||||
|
let tile = (
|
||||||
|
(mine.pos.x / CELL_SIZE).floor() as i32,
|
||||||
|
(mine.pos.y / CELL_SIZE).floor() as i32,
|
||||||
|
);
|
||||||
|
index.map.entry(tile).and_modify(|set| {
|
||||||
|
set.remove(&remove.entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn explode_mine(explode: On<Explode>, query: Query<&Mine>, mut commands: Commands) {
|
||||||
|
// Explode is an EntityEvent. `explode.entity` is the entity that Explode was triggered for.
|
||||||
|
let Ok(mut entity) = commands.get_entity(explode.entity) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
info!("Boom! {} exploded.", explode.entity);
|
||||||
|
entity.despawn();
|
||||||
|
let mine = query.get(explode.entity).unwrap();
|
||||||
|
// Trigger another explosion cascade.
|
||||||
|
commands.trigger(ExplodeMines {
|
||||||
|
pos: mine.pos,
|
||||||
|
radius: mine.size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a circle for each mine using `Gizmos`
|
||||||
|
fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) {
|
||||||
|
for mine in &mines {
|
||||||
|
gizmos.circle_2d(
|
||||||
|
mine.pos,
|
||||||
|
mine.size,
|
||||||
|
Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger `ExplodeMines` at the position of a given click
|
||||||
|
fn handle_click(
|
||||||
|
mouse_button_input: Res<ButtonInput<MouseButton>>,
|
||||||
|
camera: Single<(&Camera, &GlobalTransform)>,
|
||||||
|
windows: Query<&Window>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
let Ok(windows) = windows.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (camera, camera_transform) = *camera;
|
||||||
|
if let Some(pos) = windows
|
||||||
|
.cursor_position()
|
||||||
|
.and_then(|cursor| camera.viewport_to_world(camera_transform, cursor).ok())
|
||||||
|
.map(|ray| ray.origin.truncate())
|
||||||
|
&& mouse_button_input.just_pressed(MouseButton::Left)
|
||||||
|
{
|
||||||
|
commands.trigger(ExplodeMines { pos, radius: 1.0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct SpatialIndex {
|
||||||
|
map: HashMap<(i32, i32), HashSet<Entity>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cell size has to be bigger than any `TriggerMine::radius`
|
||||||
|
const CELL_SIZE: f32 = 64.0;
|
||||||
|
|
||||||
|
impl SpatialIndex {
|
||||||
|
// Lookup all entities within adjacent cells of our spatial index
|
||||||
|
fn get_nearby(&self, pos: Vec2) -> Vec<Entity> {
|
||||||
|
let tile = (
|
||||||
|
(pos.x / CELL_SIZE).floor() as i32,
|
||||||
|
(pos.y / CELL_SIZE).floor() as i32,
|
||||||
|
);
|
||||||
|
let mut nearby = Vec::new();
|
||||||
|
for x in -1..2 {
|
||||||
|
for y in -1..2 {
|
||||||
|
if let Some(mines) = self.map.get(&(tile.0 + x, tile.1 + y)) {
|
||||||
|
nearby.extend(mines.iter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nearby
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
pub const FRAMES_PER_SECOND: u8 = 24;
|
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 HEAD_SIZE: u8 = (GRID_SIZE as f32 * 1.1) as u8;
|
||||||
pub const SNAKE_VELOCITY: u8 = (GRID_SIZE as f32 / 1.0) as u8;
|
pub const SNAKE_VELOCITY: u8 = (GRID_SIZE as f32 / 1.0) as u8;
|
||||||
|
|
||||||
|
/// Координатная сетка
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Grid;
|
||||||
|
|
||||||
/// Голова змеи
|
/// Голова змеи
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Head;
|
pub struct Head;
|
||||||
|
|
||||||
/// Сегмент змеиного хвоста
|
/// Сегмент змеиного хвоста
|
||||||
#[derive(Component)]
|
#[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 {
|
pub enum Direction {
|
||||||
Up,
|
Up,
|
||||||
Left,
|
Left,
|
||||||
@@ -81,3 +95,51 @@ pub struct Velocity(pub f32);
|
|||||||
/// Инерция
|
/// Инерция
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
pub struct Inert(pub Direction);
|
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 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(
|
pub fn head_rotation(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
query: Single<(&mut Transform, &mut Direction), With<Head>>,
|
query: Single<(&mut Transform, &mut Direction), With<Head>>,
|
||||||
|
mut snake_path: ResMut<SnakePath>,
|
||||||
) {
|
) {
|
||||||
use core::f32::consts::PI;
|
use core::f32::consts::PI;
|
||||||
|
|
||||||
let (mut transform, mut direction) = query.into_inner();
|
let (mut transform, mut direction) = query.into_inner();
|
||||||
|
|
||||||
if input.just_pressed(KeyCode::ArrowLeft) {
|
if input.just_pressed(KeyCode::ArrowLeft) {
|
||||||
transform.rotate_z(PI / 2.);
|
transform.rotate_z(PI / 2.0);
|
||||||
direction.rotate_left();
|
direction.rotate_left();
|
||||||
|
snake_path.push(&transform, direction.clone());
|
||||||
}
|
}
|
||||||
if input.just_pressed(KeyCode::ArrowRight) {
|
if input.just_pressed(KeyCode::ArrowRight) {
|
||||||
transform.rotate_z(-PI / 2.);
|
transform.rotate_z(-PI / 2.0);
|
||||||
direction.rotate_right();
|
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 delta = velocity * time.delta_secs();
|
||||||
let src = inert.0.get_coord(&transform);
|
let src = inert.0.get_coord(&transform);
|
||||||
let sign = inert.0.get_change_sign();
|
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 sign = inert.0.get_change_sign();
|
||||||
let dst = src + sign * delta;
|
let dst = src + sign * delta;
|
||||||
inert.0.set_coord(&mut transform, dst);
|
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;
|
let mut src: f32 = src;
|
||||||
for step in old_grid_right as i32..new_grid_right as i32 {
|
for step in old_grid_right as i32..new_grid_right as i32 {
|
||||||
let step = step as f32 * grid;
|
let step = step as f32 * grid;
|
||||||
|
if step != src {
|
||||||
output.push((step, step - src));
|
output.push((step, step - src));
|
||||||
|
}
|
||||||
src = step;
|
src = step;
|
||||||
}
|
}
|
||||||
output.push((dst, dst - src));
|
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;
|
let mut dst: f32 = dst;
|
||||||
for step in new_grid_right as i32..old_grid_right as i32 {
|
for step in new_grid_right as i32..old_grid_right as i32 {
|
||||||
let step = step as f32 * grid;
|
let step = step as f32 * grid;
|
||||||
|
if step != dst {
|
||||||
output.push((dst, dst - step));
|
output.push((dst, dst - step));
|
||||||
|
}
|
||||||
dst = step;
|
dst = step;
|
||||||
}
|
}
|
||||||
output.push((dst, dst - src));
|
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(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(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;
|
mod startup;
|
||||||
|
|
||||||
pub use gameplay::{head_rotation, moving};
|
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(bevy::winit::WinitSettings::desktop_app())
|
||||||
.insert_resource(ClearColor(Color::srgb(0.3, 0.6, 0.8)))
|
.insert_resource(ClearColor(Color::srgb(0.3, 0.6, 0.8)))
|
||||||
.add_systems(Startup, (startup, setup_frame_rate))
|
.add_systems(Startup, (startup, setup_frame_rate))
|
||||||
|
// .add_systems(PostStartup, snake_game::print_all_entities)
|
||||||
.add_systems(Update, (head_rotation, moving))
|
.add_systems(Update, (head_rotation, moving))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
use bevy::{ecs::observer::ObservedBy, prelude::*, winit::WinitSettings};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use bevy::{prelude::*, winit::WinitSettings};
|
use crate::domain::{End, Grid, GridBarier, SnakePath, Tail};
|
||||||
|
|
||||||
use crate::domain::Tail;
|
|
||||||
|
|
||||||
/// Настройка частоты кадров
|
/// Настройка частоты кадров
|
||||||
pub fn setup_frame_rate(mut winit: ResMut<WinitSettings>) {
|
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) {
|
fn spawn_snake(commands: &mut Commands) {
|
||||||
use crate::domain::{Direction, HEAD_SIZE, Head, Inert, SNAKE_VELOCITY, Velocity};
|
use crate::domain::{Direction, HEAD_SIZE, Head, Inert, SNAKE_VELOCITY, Velocity};
|
||||||
|
|
||||||
|
commands.insert_resource(SnakePath::default());
|
||||||
|
|
||||||
let head = commands
|
let head = commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Head,
|
Head,
|
||||||
@@ -64,8 +65,10 @@ fn spawn_snake(commands: &mut Commands) {
|
|||||||
ChildOf(head),
|
ChildOf(head),
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn((
|
let tail = commands
|
||||||
Tail(0),
|
.spawn((
|
||||||
|
Tail,
|
||||||
|
End,
|
||||||
Direction::default(),
|
Direction::default(),
|
||||||
Inert::default(),
|
Inert::default(),
|
||||||
Velocity(0.0),
|
Velocity(0.0),
|
||||||
@@ -77,30 +80,54 @@ fn spawn_snake(commands: &mut Commands) {
|
|||||||
translation: vec3(0.0, 0.0, 0.0),
|
translation: vec3(0.0, 0.0, 0.0),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// ChildOf(head),
|
))
|
||||||
));
|
.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>();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Создать координатную сетку
|
/// Создать координатную сетку
|
||||||
fn spawn_grid(commands: &mut Commands) {
|
fn spawn_grid(commands: &mut Commands) {
|
||||||
use crate::domain::GRID_SIZE;
|
use crate::domain::GRID_SIZE;
|
||||||
|
|
||||||
|
let grid = commands
|
||||||
|
.spawn((Grid, Transform::default(), Visibility::default()))
|
||||||
|
.id();
|
||||||
|
|
||||||
for x in -20..=20 {
|
for x in -20..=20 {
|
||||||
commands.spawn((
|
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 {
|
Transform {
|
||||||
translation: vec3(x as f32 * GRID_SIZE as f32, 0.0, -1.0),
|
translation: vec3(x as f32 * GRID_SIZE as f32, 0.0, -1.0),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
ChildOf(grid),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
for y in -20..=20 {
|
for y in -20..=20 {
|
||||||
commands.spawn((
|
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 {
|
Transform {
|
||||||
translation: vec3(0.0, y as f32 * GRID_SIZE as f32, -1.0),
|
translation: vec3(0.0, y as f32 * GRID_SIZE as f32, -1.0),
|
||||||
..Default::default()
|
..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