Compare commits
No commits in common. "8d673fb3aad83c7973352d30e85e7918379062d0" and "4da000f1c83c3eb7be45ef89d30cd1d14137c0b7" have entirely different histories.
8d673fb3aa
...
4da000f1c8
10 changed files with 59 additions and 153 deletions
|
|
@ -39,7 +39,6 @@ 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 {
|
||||||
|
|
@ -49,7 +48,6 @@ 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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,8 +65,4 @@ pub enum Direction {
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Default, Debug)]
|
|
||||||
pub struct Score(pub u32);
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct ScoreTextMarker;
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
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]
|
||||||
|
|
@ -35,3 +28,9 @@ impl BallType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct Ball {
|
||||||
|
pub ball_type: BallType,
|
||||||
|
pub slot_index: usize,
|
||||||
|
pub slot_progress: f32,
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,12 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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,9 +18,3 @@ 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,40 +2,36 @@ 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(
|
app.add_systems(OnEnter(GameState), (
|
||||||
OnEnter(GameState),
|
setup_level,
|
||||||
(setup_level, initialize_queue, setup_cannon).chain(),
|
initialize_queue,
|
||||||
)
|
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,
|
|
||||||
//сохраняем порядок трех
|
|
||||||
(
|
(
|
||||||
detect_projectile_hit,
|
// debug_draw_grid,
|
||||||
check_and_remove_matches,
|
debug_draw_track,
|
||||||
|
warmup_queue_movement, //до спавна остальных шариков
|
||||||
|
spawn_new_ball,
|
||||||
move_queue_along_track,
|
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,
|
||||||
)
|
)
|
||||||
.chain(),
|
.run_if(in_state(GameState)),
|
||||||
)
|
)
|
||||||
.run_if(in_state(GameState)),
|
.add_systems(OnExit(GameState), cleanup_main_menu);
|
||||||
)
|
|
||||||
.add_systems(OnExit(GameState), cleanup_main_menu);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ 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());
|
||||||
|
|
@ -16,8 +18,5 @@ 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ pub fn spawn_new_ball(
|
||||||
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
|
||||||
});
|
});
|
||||||
|
|
@ -45,7 +50,8 @@ 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>,
|
||||||
|
|
@ -58,7 +64,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 += BALL_MOVEMENT_SPEED * time.delta_secs() / SLOT_SIZE;
|
ball.slot_progress += SPEED * time.delta_secs() / SLOT_SIZE;
|
||||||
|
|
||||||
// Если шарик достиг конца текущего сегмента
|
// Если шарик достиг конца текущего сегмента
|
||||||
if ball.slot_progress >= 1.0 {
|
if ball.slot_progress >= 1.0 {
|
||||||
|
|
@ -86,7 +92,8 @@ 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,
|
||||||
|
|
@ -95,16 +102,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(INITIAL_BALLS_COUNT);
|
let base_idx = track.spawn_index.saturating_sub(START_COUNT);
|
||||||
|
|
||||||
for i in 0..INITIAL_BALLS_COUNT {
|
for i in 0..START_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 + INITIAL_BALLS_COUNT;
|
let target_idx = slot_idx + START_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());
|
||||||
|
|
@ -129,7 +136,8 @@ 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>,
|
||||||
|
|
@ -141,7 +149,7 @@ pub fn warmup_queue_movement(
|
||||||
) {
|
) {
|
||||||
if !wave.is_warming_up { return; }
|
if !wave.is_warming_up { return; }
|
||||||
|
|
||||||
let speed = BALL_MOVEMENT_SPEED * WARMUP_BALL_MOVEMENT_MULTIPLIER;
|
let speed = SPEED * WARMUP_MULTIPLIER;
|
||||||
|
|
||||||
|
|
||||||
let spawn_slot = track.spawn_index.saturating_sub(1);
|
let spawn_slot = track.spawn_index.saturating_sub(1);
|
||||||
|
|
@ -201,82 +209,3 @@ 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,6 +2,11 @@ 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");
|
||||||
|
|
||||||
|
|
@ -186,8 +191,6 @@ 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-1,
|
spawn_index: BUFFER_SLOTS,
|
||||||
buffer_size: BUFFER_SLOTS,
|
buffer_size: BUFFER_SLOTS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue