Compare commits
2 commits
4da000f1c8
...
8d673fb3aa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d673fb3aa | ||
|
|
562b340469 |
10 changed files with 153 additions and 59 deletions
|
|
@ -39,6 +39,7 @@ pub struct WaveState {
|
||||||
pub ball_reached_end: bool,
|
pub ball_reached_end: bool,
|
||||||
pub is_warming_up: bool,
|
pub is_warming_up: bool,
|
||||||
pub is_queue_locked: bool, //отдельно для большей явности
|
pub is_queue_locked: bool, //отдельно для большей явности
|
||||||
|
pub last_insert_index: Option<usize>, //для системы удаления серий шариков
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WaveState {
|
impl Default for WaveState {
|
||||||
|
|
@ -48,6 +49,7 @@ impl Default for WaveState {
|
||||||
ball_reached_end: false,
|
ball_reached_end: false,
|
||||||
is_warming_up: false,
|
is_warming_up: false,
|
||||||
is_queue_locked: false,
|
is_queue_locked: false,
|
||||||
|
last_insert_index: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,4 +67,8 @@ pub enum Direction {
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default, Debug)]
|
||||||
|
pub struct Score(pub u32);
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct ScoreTextMarker;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
pub struct Ball {
|
||||||
|
pub ball_type: BallType,
|
||||||
|
pub slot_index: usize,
|
||||||
|
pub slot_progress: f32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||||
pub enum BallType {
|
pub enum BallType {
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -28,9 +35,3 @@ impl BallType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
pub struct Ball {
|
|
||||||
pub ball_type: BallType,
|
|
||||||
pub slot_index: usize,
|
|
||||||
pub slot_progress: f32,
|
|
||||||
}
|
|
||||||
|
|
@ -27,12 +27,6 @@ impl CannonState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cycle_next(&mut self) {
|
pub fn cycle_next(&mut self) {
|
||||||
// self.next_type = match self.next_type {
|
|
||||||
// BallType::First => BallType::Second,
|
|
||||||
// BallType::Second => BallType::Third,
|
|
||||||
// BallType::Third => BallType::Forth,
|
|
||||||
// BallType::Forth => BallType::First,
|
|
||||||
// };
|
|
||||||
let cur = self.current_type;
|
let cur = self.current_type;
|
||||||
self.current_type = self.next_type;
|
self.current_type = self.next_type;
|
||||||
self.next_type = cur;
|
self.next_type = cur;
|
||||||
|
|
|
||||||
14
src/states/level/constants.rs
Normal file
14
src/states/level/constants.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::FACTOR;
|
||||||
|
|
||||||
|
pub const SHIFT: f32 = 1.0; // коэффициент пересчета для слотов
|
||||||
|
pub const SLOT_SIZE: f32 = FACTOR as f32 / SHIFT;
|
||||||
|
|
||||||
|
pub const BALL_MOVEMENT_SPEED: f32 = 60.0; // пикселей в секунду
|
||||||
|
pub const INITIAL_BALLS_COUNT: usize = 10;
|
||||||
|
pub const WARMUP_BALL_MOVEMENT_MULTIPLIER: f32 = 5.0; // во сколько раз больше BALL_MOVEMENT_SPEED
|
||||||
|
|
||||||
|
// Z-индексы элементов
|
||||||
|
pub const CANNON_Z_INDEX: f32 = 10.0;
|
||||||
|
pub const PROJECTILE_Z_INDEX: f32 = 11.0;
|
||||||
|
pub const CURRENT_SHOT_Z_INDEX: f32 = 12.0;
|
||||||
|
pub const NEXT_SHOT_Z_INDEX: f32 = 12.0;
|
||||||
|
|
@ -18,3 +18,9 @@ mod components_cannon;
|
||||||
pub use components_cannon::*;
|
pub use components_cannon::*;
|
||||||
mod components_ball;
|
mod components_ball;
|
||||||
pub use components_ball::*;
|
pub use components_ball::*;
|
||||||
|
mod ui;
|
||||||
|
pub use ui::*;
|
||||||
|
mod constants;
|
||||||
|
pub use constants::*;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,40 @@ use crate::states::AppState::GameState;
|
||||||
use crate::states::level::*;
|
use crate::states::level::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
pub struct LevelPlugin;
|
pub struct LevelPlugin;
|
||||||
|
|
||||||
impl Plugin for LevelPlugin {
|
impl Plugin for LevelPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(OnEnter(GameState), (
|
app.add_systems(
|
||||||
setup_level,
|
OnEnter(GameState),
|
||||||
initialize_queue,
|
(setup_level, initialize_queue, setup_cannon).chain(),
|
||||||
setup_cannon).chain())
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
|
(
|
||||||
|
// debug_draw_grid,
|
||||||
|
debug_draw_track,
|
||||||
|
warmup_queue_movement, //до спавна остальных шариков
|
||||||
|
spawn_new_ball,
|
||||||
|
check_wave_completion,
|
||||||
|
//cannon systems
|
||||||
|
cycle_cannon_type,
|
||||||
|
update_cannon_preview,
|
||||||
|
spawn_projectile_from_cannon,
|
||||||
|
//сохранять порядок верхних трех
|
||||||
|
rotate_cannon,
|
||||||
|
move_projectiles,
|
||||||
|
//сохраняем порядок трех
|
||||||
(
|
(
|
||||||
// debug_draw_grid,
|
|
||||||
debug_draw_track,
|
|
||||||
warmup_queue_movement, //до спавна остальных шариков
|
|
||||||
spawn_new_ball,
|
|
||||||
move_queue_along_track,
|
|
||||||
check_wave_completion,
|
|
||||||
//cannon systems
|
|
||||||
cycle_cannon_type,
|
|
||||||
update_cannon_preview,
|
|
||||||
spawn_projectile_from_cannon,
|
|
||||||
//сохранять порядок верхних трех
|
|
||||||
rotate_cannon,
|
|
||||||
move_projectiles,
|
|
||||||
detect_projectile_hit,
|
detect_projectile_hit,
|
||||||
|
check_and_remove_matches,
|
||||||
|
move_queue_along_track,
|
||||||
)
|
)
|
||||||
.run_if(in_state(GameState)),
|
.chain(),
|
||||||
)
|
)
|
||||||
.add_systems(OnExit(GameState), cleanup_main_menu);
|
.run_if(in_state(GameState)),
|
||||||
|
)
|
||||||
|
.add_systems(OnExit(GameState), cleanup_main_menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,4 +44,4 @@ fn cleanup_main_menu(mut commands: Commands, query: Query<Entity, With<LevelMark
|
||||||
commands.entity(entity).despawn_children();
|
commands.entity(entity).despawn_children();
|
||||||
commands.entity(entity).despawn();
|
commands.entity(entity).despawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ use crate::states::level::*;
|
||||||
use crate::{FACTOR, HEIGHT, WIDTH};
|
use crate::{FACTOR, HEIGHT, WIDTH};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
pub const SHIFT: f32 = 1.0;
|
|
||||||
pub const SLOT_SIZE: f32 = FACTOR as f32 / SHIFT;
|
|
||||||
|
|
||||||
pub fn setup_timer(mut commands: Commands){
|
pub fn setup_timer(mut commands: Commands){
|
||||||
commands.insert_resource(SpawnTimer::default());
|
commands.insert_resource(SpawnTimer::default());
|
||||||
|
|
@ -18,5 +16,8 @@ pub fn setup_level(mut commands: Commands){
|
||||||
|
|
||||||
commands.insert_resource(build_cannon_state());
|
commands.insert_resource(build_cannon_state());
|
||||||
println!("CannonState is set up");
|
println!("CannonState is set up");
|
||||||
|
|
||||||
|
commands.insert_resource(Score::default());
|
||||||
|
println!("Score is set up");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,6 @@ pub fn spawn_new_ball(
|
||||||
timer.reset();
|
timer.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//до добавления буферных слотов
|
|
||||||
// let entry_blocked = balls
|
|
||||||
// .iter()
|
|
||||||
// .any(|ball| ball.slot_index == 0 && ball.slot_progress < 1.0);
|
|
||||||
|
|
||||||
let entry_blocked = balls.iter().any(|ball| {
|
let entry_blocked = balls.iter().any(|ball| {
|
||||||
ball.slot_index == track.spawn_index && ball.slot_progress < 1.0
|
ball.slot_index == track.spawn_index && ball.slot_progress < 1.0
|
||||||
|
|
@ -50,8 +45,7 @@ pub fn spawn_new_ball(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME перенести позже в более удобное место/объект левела
|
|
||||||
const SPEED: f32 = 60.0; // пикселей в секунду
|
|
||||||
|
|
||||||
pub fn move_queue_along_track(
|
pub fn move_queue_along_track(
|
||||||
track: Res<TrackPath>,
|
track: Res<TrackPath>,
|
||||||
|
|
@ -64,7 +58,7 @@ pub fn move_queue_along_track(
|
||||||
|
|
||||||
for (mut ball, mut transform) in balls.iter_mut() {
|
for (mut ball, mut transform) in balls.iter_mut() {
|
||||||
// Увеличиваем прогресс
|
// Увеличиваем прогресс
|
||||||
ball.slot_progress += SPEED * time.delta_secs() / SLOT_SIZE;
|
ball.slot_progress += BALL_MOVEMENT_SPEED * time.delta_secs() / SLOT_SIZE;
|
||||||
|
|
||||||
// Если шарик достиг конца текущего сегмента
|
// Если шарик достиг конца текущего сегмента
|
||||||
if ball.slot_progress >= 1.0 {
|
if ball.slot_progress >= 1.0 {
|
||||||
|
|
@ -92,8 +86,7 @@ pub fn move_queue_along_track(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME перенести позже в более удобное место/объект левела
|
|
||||||
const START_COUNT: usize = 10;
|
|
||||||
|
|
||||||
pub fn initialize_queue(
|
pub fn initialize_queue(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
|
@ -102,16 +95,16 @@ pub fn initialize_queue(
|
||||||
mut wave: ResMut<WaveState>,
|
mut wave: ResMut<WaveState>,
|
||||||
) {
|
) {
|
||||||
// заполняем слоты от -START_COUNT до -1
|
// заполняем слоты от -START_COUNT до -1
|
||||||
let base_idx = track.spawn_index.saturating_sub(START_COUNT);
|
let base_idx = track.spawn_index.saturating_sub(INITIAL_BALLS_COUNT);
|
||||||
|
|
||||||
for i in 0..START_COUNT {
|
for i in 0..INITIAL_BALLS_COUNT {
|
||||||
let slot_idx = base_idx + i; // пропускаем индекс 0 (спавн за экраном)
|
let slot_idx = base_idx + i; // пропускаем индекс 0 (спавн за экраном)
|
||||||
let Some(&pos) = track.points.get(slot_idx) else {
|
let Some(&pos) = track.points.get(slot_idx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
println!("Slot {}: {:?}", slot_idx, pos); //debug
|
println!("Slot {}: {:?}", slot_idx, pos); //debug
|
||||||
|
|
||||||
let target_idx = slot_idx + START_COUNT;
|
let target_idx = slot_idx + INITIAL_BALLS_COUNT;
|
||||||
|
|
||||||
let ball_type = BallType::random();
|
let ball_type = BallType::random();
|
||||||
let image: Handle<Image> = asset_server.load(ball_type.asset_path());
|
let image: Handle<Image> = asset_server.load(ball_type.asset_path());
|
||||||
|
|
@ -136,8 +129,7 @@ pub fn initialize_queue(
|
||||||
wave.spawning_allowed = false;
|
wave.spawning_allowed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME перенести позже в более удобное место/объект левела
|
|
||||||
const WARMUP_MULTIPLIER: f32 = 5.0;
|
|
||||||
pub fn warmup_queue_movement(
|
pub fn warmup_queue_movement(
|
||||||
track: Res<TrackPath>,
|
track: Res<TrackPath>,
|
||||||
mut wave: ResMut<WaveState>,
|
mut wave: ResMut<WaveState>,
|
||||||
|
|
@ -149,7 +141,7 @@ pub fn warmup_queue_movement(
|
||||||
) {
|
) {
|
||||||
if !wave.is_warming_up { return; }
|
if !wave.is_warming_up { return; }
|
||||||
|
|
||||||
let speed = SPEED * WARMUP_MULTIPLIER;
|
let speed = BALL_MOVEMENT_SPEED * WARMUP_BALL_MOVEMENT_MULTIPLIER;
|
||||||
|
|
||||||
|
|
||||||
let spawn_slot = track.spawn_index.saturating_sub(1);
|
let spawn_slot = track.spawn_index.saturating_sub(1);
|
||||||
|
|
@ -209,3 +201,82 @@ pub fn check_wave_completion(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_and_remove_matches(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut wave: ResMut<WaveState>,
|
||||||
|
mut balls: ParamSet<(
|
||||||
|
Query<(Entity, &Ball)>,
|
||||||
|
Query<(Entity, &mut Ball)>
|
||||||
|
)>,
|
||||||
|
mut score: ResMut<Score>,
|
||||||
|
) {
|
||||||
|
let Some(insert_idx) = wave.last_insert_index.take() else { return; };
|
||||||
|
|
||||||
|
let mut balls_sorted: Vec<(Entity, Ball)> = balls.p0()
|
||||||
|
.iter()
|
||||||
|
.map(|(e, b)| (e, *b))
|
||||||
|
.collect();
|
||||||
|
balls_sorted.sort_by_key(|(_, b)| b.slot_index);
|
||||||
|
|
||||||
|
if balls_sorted.is_empty() { return; }
|
||||||
|
|
||||||
|
let Some(pos) = balls_sorted.iter().position(|(_, b)| b.slot_index == insert_idx) else { return; };
|
||||||
|
let inserted_ball = balls_sorted[pos].1;
|
||||||
|
|
||||||
|
// Ищем группу того же типа
|
||||||
|
let mut group_start = pos;
|
||||||
|
while group_start > 0 && balls_sorted[group_start - 1].1.ball_type == inserted_ball.ball_type {
|
||||||
|
group_start -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut group_end = pos + 1;
|
||||||
|
while group_end < balls_sorted.len() && balls_sorted[group_end].1.ball_type == inserted_ball.ball_type {
|
||||||
|
group_end += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let group_size = group_end - group_start;
|
||||||
|
if group_size < 3 { return; }
|
||||||
|
|
||||||
|
// Запоминаем диапазон до удаления
|
||||||
|
let removed_min_idx = balls_sorted[group_start].1.slot_index;
|
||||||
|
let removed_max_idx = balls_sorted[group_end - 1].1.slot_index;
|
||||||
|
let removed_count = group_size;
|
||||||
|
|
||||||
|
// Проверяем соседей
|
||||||
|
let has_left_neighbors = balls_sorted.iter().any(|(_, b)| b.slot_index < removed_min_idx);
|
||||||
|
let has_right_neighbors = balls_sorted.iter().any(|(_, b)| b.slot_index > removed_max_idx);
|
||||||
|
|
||||||
|
// --- УДАЛЕНИЕ ---
|
||||||
|
let to_remove: Vec<Entity> = balls_sorted[group_start..group_end]
|
||||||
|
.iter()
|
||||||
|
.map(|(e, _)| *e)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for entity in to_remove.iter() {
|
||||||
|
commands.entity(*entity).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
let gained = removed_count * 100;
|
||||||
|
score.0 += gained as u32;
|
||||||
|
println!("Match at idx {}! +{} points | total: {}", insert_idx, gained, score.0);
|
||||||
|
|
||||||
|
// --- СХЛОПЫВАНИЕ ОЧЕРЕДИ ---
|
||||||
|
|
||||||
|
if has_left_neighbors && has_right_neighbors {
|
||||||
|
// 🔸 Удаление ВНУТРИ: сдвигаем все шарики СПРАВА влево
|
||||||
|
println!("Closing gap ({}..{}), shifting right balls left by {}",
|
||||||
|
removed_min_idx, removed_max_idx, removed_count);
|
||||||
|
|
||||||
|
// Сдвигаем индексы всех шариков с index > removed_max_idx
|
||||||
|
for (entity, mut ball) in balls.p1().iter_mut() {
|
||||||
|
if ball.slot_index > removed_max_idx {
|
||||||
|
ball.slot_index -= removed_count;
|
||||||
|
ball.slot_progress = 0.0; // "Примагничиваем" к новому слоту
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔸 Удаление в НАЧАЛЕ или КОНЦЕ: индексы не меняем
|
||||||
|
// (опционально: можно сбросить progress у правых соседей для чёткости)
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,6 @@ use bevy::prelude::*;
|
||||||
use crate::states::level::*;
|
use crate::states::level::*;
|
||||||
use bevy::window::PrimaryWindow;
|
use bevy::window::PrimaryWindow;
|
||||||
|
|
||||||
const CANNON_Z_INDEX: f32 = 10.0;
|
|
||||||
const PROJECTILE_Z_INDEX: f32 = 11.0;
|
|
||||||
const CURRENT_SHOT_Z_INDEX: f32 = 12.0;
|
|
||||||
const NEXT_SHOT_Z_INDEX: f32 = 12.0;
|
|
||||||
|
|
||||||
pub fn setup_cannon(mut commands: Commands, asset_server: Res<AssetServer>) {
|
pub fn setup_cannon(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
let texture: Handle<Image> = asset_server.load("cannon/cannon.png");
|
let texture: Handle<Image> = asset_server.load("cannon/cannon.png");
|
||||||
|
|
||||||
|
|
@ -191,6 +186,8 @@ pub fn detect_projectile_hit(
|
||||||
slot_progress: target_progress,
|
slot_progress: target_progress,
|
||||||
}
|
}
|
||||||
}).remove::<BallProjectile>();
|
}).remove::<BallProjectile>();
|
||||||
|
// запоминаем индекс слота, куда попали
|
||||||
|
wave.last_insert_index = Some(insert_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Разблокировка (структурные изменения завершены)
|
// Разблокировка (структурные изменения завершены)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ pub fn setup_track_with_buffer() -> TrackPath {
|
||||||
|
|
||||||
TrackPath {
|
TrackPath {
|
||||||
points: full_points,
|
points: full_points,
|
||||||
spawn_index: BUFFER_SLOTS,
|
spawn_index: BUFFER_SLOTS-1,
|
||||||
buffer_size: BUFFER_SLOTS,
|
buffer_size: BUFFER_SLOTS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue