ball insert into track queue
This commit is contained in:
parent
138c62ac34
commit
c4d547520e
3 changed files with 116 additions and 65 deletions
|
|
@ -71,6 +71,7 @@ pub struct WaveState {
|
||||||
pub spawning_allowed: bool,
|
pub spawning_allowed: bool,
|
||||||
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, //отдельно для большей явности
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WaveState {
|
impl Default for WaveState {
|
||||||
|
|
@ -79,6 +80,7 @@ impl Default for WaveState {
|
||||||
spawning_allowed: false,
|
spawning_allowed: false,
|
||||||
ball_reached_end: false,
|
ball_reached_end: false,
|
||||||
is_warming_up: false,
|
is_warming_up: false,
|
||||||
|
is_queue_locked: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,4 +98,6 @@ pub struct BallProjectile {
|
||||||
pub velocity: Vec2,
|
pub velocity: Vec2,
|
||||||
pub previous_position: Vec2,
|
pub previous_position: Vec2,
|
||||||
pub ball_type: BallType,
|
pub ball_type: BallType,
|
||||||
|
pub is_fresh: bool,
|
||||||
|
pub travel_distance: f32,
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ pub fn spawn_new_ball(
|
||||||
balls: Query<&Ball>,
|
balls: Query<&Ball>,
|
||||||
wave: Res<WaveState>,
|
wave: Res<WaveState>,
|
||||||
) {
|
) {
|
||||||
if !wave.spawning_allowed {
|
if !wave.spawning_allowed || wave.is_queue_locked {
|
||||||
timer.reset();
|
timer.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -209,3 +209,4 @@ pub fn check_wave_completion(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
|
use crate::states::level::components::{
|
||||||
|
Ball, BallProjectile, BallType, Cannon, LevelMarker, TrackPath, WaveState,
|
||||||
|
};
|
||||||
|
use crate::states::level::system::SLOT_SIZE;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::window::PrimaryWindow;
|
use bevy::window::PrimaryWindow;
|
||||||
use crate::states::level::components::{Ball, BallProjectile, BallType, Cannon, LevelMarker, TrackPath};
|
|
||||||
use crate::states::level::system::SLOT_SIZE;
|
|
||||||
|
|
||||||
pub fn setup_cannon(
|
pub fn setup_cannon(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
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");
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
LevelMarker,
|
LevelMarker,
|
||||||
Cannon,
|
Cannon,
|
||||||
Transform::from_xyz(579.0-(1280.0/2.0), (768.0/2.0)-150.0, 1.0),
|
Transform::from_xyz(579.0 - (1280.0 / 2.0), (768.0 / 2.0) - 150.0, 1.0),
|
||||||
Sprite::from_image(texture),
|
Sprite::from_image(texture),
|
||||||
Visibility::Visible,
|
Visibility::Visible,
|
||||||
));
|
));
|
||||||
|
|
@ -23,13 +22,23 @@ pub fn rotate_cannon(
|
||||||
window_query: Query<&Window, With<PrimaryWindow>>,
|
window_query: Query<&Window, With<PrimaryWindow>>,
|
||||||
camera_query: Query<(&Camera, &GlobalTransform)>,
|
camera_query: Query<(&Camera, &GlobalTransform)>,
|
||||||
) {
|
) {
|
||||||
let Ok(mut cannon_tf) = query.single_mut() else { return };
|
let Ok(mut cannon_tf) = query.single_mut() else {
|
||||||
let Ok(window) = window_query.single() else { return };
|
return;
|
||||||
let Some(cursor_pos) = window.cursor_position() else { return };
|
};
|
||||||
let Ok((camera, camera_tf)) = camera_query.single() else { return };
|
let Ok(window) = window_query.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(cursor_pos) = window.cursor_position() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok((camera, camera_tf)) = camera_query.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// Конвертируем экранные координаты курсора в мировые
|
// Конвертируем экранные координаты курсора в мировые
|
||||||
let Ok(world_cursor_pos) = camera.viewport_to_world_2d(camera_tf, cursor_pos) else { return };
|
let Ok(world_cursor_pos) = camera.viewport_to_world_2d(camera_tf, cursor_pos) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// Вектор направления от пушки к курсору
|
// Вектор направления от пушки к курсору
|
||||||
let cannon_pos = Vec2::new(cannon_tf.translation.x, cannon_tf.translation.y);
|
let cannon_pos = Vec2::new(cannon_tf.translation.x, cannon_tf.translation.y);
|
||||||
|
|
@ -46,14 +55,18 @@ pub fn spawn_projectile_from_cannon(
|
||||||
mouse_input: Res<ButtonInput<MouseButton>>,
|
mouse_input: Res<ButtonInput<MouseButton>>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
if !mouse_input.just_pressed(MouseButton::Left) { return; }
|
if !mouse_input.just_pressed(MouseButton::Left) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let Ok(cannon_tf) = cannon_query.single() else { return; };
|
let Ok(cannon_tf) = cannon_query.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let ball_type = BallType::First; // TODO: рандом/цикл
|
let ball_type = BallType::First; // TODO: рандом/цикл
|
||||||
let image = asset_server.load(ball_type.asset_path());
|
let image = asset_server.load(ball_type.asset_path());
|
||||||
|
|
||||||
let offset = 100.0;
|
let offset = 100.0; // сдвиг от центра к дулу пушки
|
||||||
let direction = cannon_tf.rotation.mul_vec3(Vec3::Y).truncate().normalize();
|
let direction = cannon_tf.rotation.mul_vec3(Vec3::Y).truncate().normalize();
|
||||||
let spawn_pos_2d = cannon_tf.translation.truncate() + direction * offset;
|
let spawn_pos_2d = cannon_tf.translation.truncate() + direction * offset;
|
||||||
let spawn_pos = spawn_pos_2d.extend(10.0); // z=10, чтобы было поверх трека
|
let spawn_pos = spawn_pos_2d.extend(10.0); // z=10, чтобы было поверх трека
|
||||||
|
|
@ -71,6 +84,8 @@ pub fn spawn_projectile_from_cannon(
|
||||||
velocity: direction * 800.0,
|
velocity: direction * 800.0,
|
||||||
previous_position: spawn_pos_2d,
|
previous_position: spawn_pos_2d,
|
||||||
ball_type,
|
ball_type,
|
||||||
|
is_fresh: true,
|
||||||
|
travel_distance: 0.0,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +97,11 @@ pub fn move_projectiles(
|
||||||
) {
|
) {
|
||||||
for (entity, mut proj, mut transform) in projectiles.iter_mut() {
|
for (entity, mut proj, mut transform) in projectiles.iter_mut() {
|
||||||
// Обновляем позицию
|
// Обновляем позицию
|
||||||
let new_pos = proj.previous_position + proj.velocity * time.delta_secs();
|
let delta = proj.velocity * time.delta_secs();
|
||||||
|
let new_pos = proj.previous_position + delta;
|
||||||
|
|
||||||
|
proj.travel_distance += delta.length();
|
||||||
|
|
||||||
transform.translation = new_pos.extend(transform.translation.z);
|
transform.translation = new_pos.extend(transform.translation.z);
|
||||||
proj.previous_position = new_pos;
|
proj.previous_position = new_pos;
|
||||||
|
|
||||||
|
|
@ -93,64 +112,91 @@ pub fn move_projectiles(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HIT_THRESHOLD: f32 = SLOT_SIZE;
|
||||||
pub fn detect_projectile_hit(
|
pub fn detect_projectile_hit(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
track: Res<TrackPath>,
|
track: Res<TrackPath>,
|
||||||
|
mut wave: ResMut<WaveState>,
|
||||||
mut projectiles: Query<(Entity, &mut BallProjectile)>,
|
mut projectiles: Query<(Entity, &mut BallProjectile)>,
|
||||||
mut balls: Query<&mut Ball>, // <-- нужен мутабельный доступ для сдвига
|
mut balls: Query<(Entity, &mut Ball)>,
|
||||||
) {
|
) {
|
||||||
for (proj_entity, proj) in projectiles.iter_mut() {
|
// пока идёт структурное изменение, спавн и другие мутации ждут
|
||||||
let mut nearest_idx = 0;
|
if wave.is_queue_locked {
|
||||||
let mut min_dist = f32::MAX;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (proj_entity, mut proj) in projectiles.iter_mut() {
|
||||||
|
if proj.is_fresh {
|
||||||
|
proj.is_fresh = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut min_dist_any = f32::MAX;
|
||||||
|
let mut nearest_idx_any = 0;
|
||||||
|
|
||||||
for (idx, &slot_pos) in track.points.iter().enumerate() {
|
for (idx, &slot_pos) in track.points.iter().enumerate() {
|
||||||
let dist = proj.previous_position.distance(slot_pos);
|
let dist = proj.previous_position.distance(slot_pos);
|
||||||
if dist < min_dist {
|
if dist < min_dist_any {
|
||||||
min_dist = dist;
|
min_dist_any = dist;
|
||||||
nearest_idx = idx;
|
nearest_idx_any = idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const HIT_THRESHOLD: f32 = SLOT_SIZE;
|
// если снаряд далеко от трека - промах
|
||||||
if min_dist < HIT_THRESHOLD {
|
if min_dist_any > HIT_THRESHOLD {
|
||||||
|
// commands.entity(proj_entity).despawn();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let occupied_slots: Vec<usize> = balls.iter()
|
let insert_idx;
|
||||||
.map(|ball| ball.slot_index)
|
let target_progress;
|
||||||
.collect();
|
|
||||||
|
|
||||||
if occupied_slots.contains(&nearest_idx) {
|
if let Some((_, target_ball)) = balls.iter().find(|(_, b)| b.slot_index == nearest_idx_any) {
|
||||||
|
// Копируем значения в локальные переменные
|
||||||
let left_progress = if nearest_idx > 0 {
|
target_progress = target_ball.slot_progress;
|
||||||
balls.iter()
|
|
||||||
.find(|b| b.slot_index == nearest_idx - 1)
|
|
||||||
.map(|b| b.slot_progress)
|
|
||||||
.unwrap_or(0.0)
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. Сдвигаем правую часть очереди вправо
|
|
||||||
for mut ball in balls.iter_mut() {
|
|
||||||
if ball.slot_index >= nearest_idx {
|
|
||||||
ball.slot_index += 1;
|
|
||||||
ball.slot_progress = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.entity(proj_entity)
|
|
||||||
.insert(Ball {
|
|
||||||
ball_type: proj.ball_type,
|
|
||||||
slot_index: nearest_idx,
|
|
||||||
slot_progress: left_progress,
|
|
||||||
})
|
|
||||||
.remove::<BallProjectile>();
|
|
||||||
|
|
||||||
println!("Hit! Inserted at slot {}", nearest_idx);
|
|
||||||
|
|
||||||
|
// Определяем индекс вставки
|
||||||
|
insert_idx = if target_progress <= 0.5 {
|
||||||
|
nearest_idx_any
|
||||||
} else {
|
} else {
|
||||||
commands.entity(proj_entity).despawn();
|
nearest_idx_any + 1
|
||||||
println!("Miss: slot {} is empty", nearest_idx);
|
};
|
||||||
|
} else {
|
||||||
|
// Слот пуст — снаряд пролетает мимо
|
||||||
|
commands.entity(proj_entity).despawn();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
wave.is_queue_locked = true;
|
||||||
|
|
||||||
|
// сдвигаем очередь
|
||||||
|
let max_idx = track.points.len() - 1;
|
||||||
|
let mut to_despawn = Vec::new();
|
||||||
|
|
||||||
|
|
||||||
|
for (entity, mut ball) in balls.iter_mut() {
|
||||||
|
if ball.slot_index >= insert_idx {
|
||||||
|
if ball.slot_index < max_idx {
|
||||||
|
ball.slot_index += 1;
|
||||||
|
} else {
|
||||||
|
to_despawn.push(entity);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for entity in to_despawn {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.entity(proj_entity).insert({
|
||||||
|
Ball {
|
||||||
|
ball_type: proj.ball_type,
|
||||||
|
slot_index: insert_idx,
|
||||||
|
slot_progress: target_progress,
|
||||||
|
}
|
||||||
|
}).remove::<BallProjectile>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Разблокировка (структурные изменения завершены)
|
||||||
|
wave.is_queue_locked = false;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue