Compare commits
No commits in common. "f297f249dfef05bc234ac2725cbfa48135fa4a0e" and "30412844b8a6887acd62eac6372f780ffa60227d" have entirely different histories.
f297f249df
...
30412844b8
12 changed files with 67 additions and 445 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,4 +2,3 @@
|
||||||
/assets
|
/assets
|
||||||
.idea
|
.idea
|
||||||
.directory
|
.directory
|
||||||
/tiled/
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ use crate::states::main_menu::state::MainMenuState;
|
||||||
use crate::states::settings_menu::state::SettingsMenuState;
|
use crate::states::settings_menu::state::SettingsMenuState;
|
||||||
use crate::states::linear::plugin::LinearPlayPlugin;
|
use crate::states::linear::plugin::LinearPlayPlugin;
|
||||||
|
|
||||||
const FACTOR: u32 = 40;
|
const FACTOR: u32 = 80;
|
||||||
const WIDTH: u32 = 32;
|
const WIDTH: u32 = 16;
|
||||||
const HEIGHT: u32 = 18;
|
const HEIGHT: u32 = 9;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,3 @@ pub struct LinearStateMarker;
|
||||||
|
|
||||||
#[derive(Component, Copy, Clone)]
|
#[derive(Component, Copy, Clone)]
|
||||||
pub struct LinearRestartMarker;
|
pub struct LinearRestartMarker;
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct LinearDebugConfig {
|
|
||||||
pub toggle_track: bool,
|
|
||||||
pub toggle_grid: bool,
|
|
||||||
pub toggle_laser: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Copy, Clone)]
|
|
||||||
pub enum DebugToggle{
|
|
||||||
Track,
|
|
||||||
Grid,
|
|
||||||
Laser,
|
|
||||||
}
|
|
||||||
|
|
@ -34,16 +34,8 @@ impl LinearCannonState {
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct RoundBallProjectile {
|
pub struct RoundBallProjectile {
|
||||||
pub velocity: Vec2,
|
pub velocity: Vec2,
|
||||||
pub direction: Vec2,
|
|
||||||
pub previous_position: Vec2,
|
pub previous_position: Vec2,
|
||||||
pub ball_type: RoundBallType,
|
pub ball_type: RoundBallType,
|
||||||
pub hit_result: Option<ProjectileHit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProjectileHit {
|
|
||||||
pub hit_progress: f32,
|
|
||||||
pub hit_position: Vec2,
|
|
||||||
pub segment_index: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,12 +44,3 @@ pub struct ShotMarker;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct SwapMarker;
|
pub struct SwapMarker;
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct IntersectMarker;
|
|
||||||
|
|
||||||
pub struct Intersection {
|
|
||||||
pub t: f32,
|
|
||||||
pub world_pos: Vec2,
|
|
||||||
pub segment_index: usize,
|
|
||||||
}
|
|
||||||
|
|
@ -6,13 +6,8 @@ pub const CENTER_Y: f32 = 0.0;
|
||||||
|
|
||||||
// pub const FACTOR_RADIUS: f32 = FACTOR as f32 / 2.0;
|
// pub const FACTOR_RADIUS: f32 = FACTOR as f32 / 2.0;
|
||||||
|
|
||||||
// основная размерность и масштаб
|
|
||||||
pub const SCALE: f32 = 1.0;
|
|
||||||
pub const STEP: f32 = FACTOR as f32 / SCALE;
|
pub const STEP: f32 = FACTOR as f32 / SCALE;
|
||||||
|
pub const SCALE: f32 = 2.0;
|
||||||
// зависимые размеры
|
|
||||||
pub const ROUND_BALL_SIZE: f32 = STEP;
|
|
||||||
pub const BALL_RADIUS: f32 = ROUND_BALL_SIZE / 2.0;
|
|
||||||
|
|
||||||
// Z-INDEXES
|
// Z-INDEXES
|
||||||
pub const ROUND_BALL_Z: f32 = 10.0;
|
pub const ROUND_BALL_Z: f32 = 10.0;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ pub mod plugin;
|
||||||
mod components;
|
mod components;
|
||||||
pub use components::*;
|
pub use components::*;
|
||||||
|
|
||||||
mod systems_debug;
|
mod systems;
|
||||||
pub use systems_debug::*;
|
pub use systems::*;
|
||||||
|
|
||||||
mod components_track;
|
mod components_track;
|
||||||
pub use components_track::*;
|
pub use components_track::*;
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,9 @@ impl Plugin for LinearPlayPlugin {
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
toggle_debug_systems,
|
|
||||||
draw_track_gizmos,
|
draw_track_gizmos,
|
||||||
draw_grid,
|
draw_grid,
|
||||||
// draw_cannon_laser,
|
|
||||||
//несколько систем с соблюдением порядка
|
//несколько систем с соблюдением порядка
|
||||||
calculate_projectile_hits,
|
|
||||||
linear_move_projectiles,
|
linear_move_projectiles,
|
||||||
cycle_cannon_balls,
|
cycle_cannon_balls,
|
||||||
update_linear_cannon_preview,
|
update_linear_cannon_preview,
|
||||||
|
|
@ -51,13 +48,6 @@ fn setup(mut commands: Commands) {
|
||||||
commands.insert_resource(build_track);
|
commands.insert_resource(build_track);
|
||||||
commands.insert_resource(precalculated_track);
|
commands.insert_resource(precalculated_track);
|
||||||
commands.insert_resource(build_linear_cannon_state());
|
commands.insert_resource(build_linear_cannon_state());
|
||||||
|
|
||||||
let debug_config = LinearDebugConfig{
|
|
||||||
toggle_track: true,
|
|
||||||
toggle_grid: true,
|
|
||||||
toggle_laser: false,
|
|
||||||
};
|
|
||||||
commands.insert_resource(debug_config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup(mut commands: Commands, query: Query<Entity, With<LinearStateMarker>>) {
|
fn cleanup(mut commands: Commands, query: Query<Entity, With<LinearStateMarker>>) {
|
||||||
|
|
@ -70,7 +60,6 @@ fn cleanup(mut commands: Commands, query: Query<Entity, With<LinearStateMarker>>
|
||||||
commands.remove_resource::<Track>(); //зачистить сразу после калькуляции
|
commands.remove_resource::<Track>(); //зачистить сразу после калькуляции
|
||||||
commands.remove_resource::<PrecalculatedTrack>();
|
commands.remove_resource::<PrecalculatedTrack>();
|
||||||
commands.remove_resource::<LinearCannonState>();
|
commands.remove_resource::<LinearCannonState>();
|
||||||
commands.remove_resource::<LinearDebugConfig>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linear_restart(mut next_state: ResMut<NextState<AppState>>) {
|
fn linear_restart(mut next_state: ResMut<NextState<AppState>>) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub fn spawn_round_ball(
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Sprite {
|
Sprite {
|
||||||
image,
|
image,
|
||||||
custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)),
|
custom_size: Some(Vec2::splat(STEP)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Transform::from_translation((track.segments[0].start_pos).extend(ROUND_BALL_Z)),
|
Transform::from_translation((track.segments[0].start_pos).extend(ROUND_BALL_Z)),
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
use crate::{FACTOR, HEIGHT, WIDTH, states};
|
use crate::{HEIGHT, WIDTH, states, FACTOR};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use states::linear::*;
|
use states::linear::*;
|
||||||
use std::f32::consts::FRAC_PI_2;
|
use std::f32::consts::FRAC_PI_2;
|
||||||
|
|
||||||
pub fn draw_track_gizmos(track: Res<Track>, mut gizmos: Gizmos, config: ResMut<LinearDebugConfig>) {
|
pub fn draw_track_gizmos(track: Res<Track>, mut gizmos: Gizmos) {
|
||||||
if !config.toggle_track {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut current_pos = track.start_point;
|
let mut current_pos = track.start_point;
|
||||||
let mut current_dir = track.start_direction.normalize();
|
let mut current_dir = track.start_direction.normalize();
|
||||||
|
|
||||||
|
|
@ -57,51 +53,23 @@ pub fn draw_track_gizmos(track: Res<Track>, mut gizmos: Gizmos, config: ResMut<L
|
||||||
gizmos.circle_2d(current_pos, 5.0, Color::WHITE);
|
gizmos.circle_2d(current_pos, 5.0, Color::WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_grid(mut gizmos: Gizmos, config: ResMut<LinearDebugConfig>) {
|
pub fn draw_grid(mut gizmos: Gizmos) {
|
||||||
if !config.toggle_grid {
|
let half_x = WIDTH as f32 / 2.0 * FACTOR as f32;
|
||||||
return;
|
let half_y = HEIGHT as f32 / 2.0 * FACTOR as f32;
|
||||||
};
|
|
||||||
const FF: f32 = FACTOR as f32;
|
|
||||||
|
|
||||||
const HALF_X: f32 = WIDTH as f32 / 2.0;
|
|
||||||
const HALF_Y: f32 = HEIGHT as f32 / 2.0;
|
|
||||||
const STEP_X: f32 = HALF_X * FF;
|
|
||||||
const STEP_Y: f32 = HALF_Y * FF;
|
|
||||||
|
|
||||||
for i in 0..WIDTH {
|
for i in 0..WIDTH {
|
||||||
gizmos.line_2d(
|
gizmos.line_2d(
|
||||||
Vec2::new((i as f32 - HALF_X) * FF, STEP_Y),
|
Vec2::new((i as f32 - 8.0) * FACTOR as f32, half_y),
|
||||||
Vec2::new((i as f32 - HALF_X) * FF, -STEP_Y),
|
Vec2::new((i as f32 - 8.0) * FACTOR as f32, -half_y),
|
||||||
DARK_GREEN,
|
DARK_GREEN,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..HEIGHT {
|
for i in 0..HEIGHT {
|
||||||
gizmos.line_2d(
|
gizmos.line_2d(
|
||||||
Vec2::new(-STEP_X, (i as f32 - HALF_Y) * FF),
|
Vec2::new(-half_x, (i as f32 - 4.0) * FACTOR as f32),
|
||||||
Vec2::new(STEP_X, (i as f32 - HALF_Y) * FF),
|
Vec2::new(half_x, (i as f32 - 4.0) * FACTOR as f32),
|
||||||
DARK_GREEN,
|
DARK_GREEN,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn draw_cannon_laser(mut gizmos: Gizmos, config: ResMut<LinearDebugConfig>){
|
|
||||||
// if !config.toggle_laser {
|
|
||||||
// return;
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn toggle_debug_systems(
|
|
||||||
buttons: Query<(&Interaction, &DebugToggle), Changed<Interaction>>,
|
|
||||||
mut config: ResMut<LinearDebugConfig>,
|
|
||||||
) {
|
|
||||||
for (interaction, toggle_type) in &buttons {
|
|
||||||
if matches!(interaction, Interaction::Pressed) {
|
|
||||||
match toggle_type {
|
|
||||||
DebugToggle::Track => config.toggle_track = !config.toggle_track,
|
|
||||||
DebugToggle::Grid => config.toggle_grid = !config.toggle_grid,
|
|
||||||
DebugToggle::Laser => config.toggle_laser = !config.toggle_laser,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::states::linear::*;
|
use crate::states::linear::*;
|
||||||
use crate::{FACTOR, HEIGHT, WIDTH};
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::window::PrimaryWindow;
|
use bevy::window::PrimaryWindow;
|
||||||
use std::f32::consts::{FRAC_PI_2, PI};
|
use std::f32::consts::FRAC_PI_2;
|
||||||
|
use bevy::asset::ErasedAssetLoader;
|
||||||
|
use crate::{FACTOR, HEIGHT, WIDTH};
|
||||||
|
use crate::states::level::{BallProjectile, BallType, Cannon, CannonState, CurrentPreviewMarker, NextPreviewMarker, CURRENT_SHOT_Z_INDEX, NEXT_SHOT_Z_INDEX, SLOT_SIZE};
|
||||||
|
|
||||||
pub fn spawn_linear_cannon(mut commands: Commands, asset_server: Res<AssetServer>) {
|
pub fn spawn_linear_cannon(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
let image: Handle<Image> = asset_server.load("sprites/cannon/cannon.png");
|
let image: Handle<Image> = asset_server.load("sprites/cannon/cannon.png");
|
||||||
|
|
@ -23,8 +25,6 @@ pub fn rotate_linear_cannon(
|
||||||
mut query: Query<&mut Transform, With<LinearCannon>>,
|
mut query: Query<&mut Transform, With<LinearCannon>>,
|
||||||
window_query: Query<&Window, With<PrimaryWindow>>,
|
window_query: Query<&Window, With<PrimaryWindow>>,
|
||||||
camera_query: Query<(&Camera, &GlobalTransform)>,
|
camera_query: Query<(&Camera, &GlobalTransform)>,
|
||||||
config: ResMut<LinearDebugConfig>, //FIXME debug
|
|
||||||
mut gizmos: Gizmos,
|
|
||||||
) {
|
) {
|
||||||
let Ok(mut cannon_tf) = query.single_mut() else {
|
let Ok(mut cannon_tf) = query.single_mut() else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -51,13 +51,6 @@ pub fn rotate_linear_cannon(
|
||||||
|
|
||||||
let angle = direction.to_angle() - FRAC_PI_2;
|
let angle = direction.to_angle() - FRAC_PI_2;
|
||||||
cannon_tf.rotation = Quat::from_rotation_z(angle);
|
cannon_tf.rotation = Quat::from_rotation_z(angle);
|
||||||
|
|
||||||
if !config.toggle_laser {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const OUT_OF_SCREEN: f32 = (WIDTH * FACTOR) as f32 * 1.5;
|
|
||||||
gizmos.line_2d(cannon_pos, direction * OUT_OF_SCREEN, PINK);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_projectile_from_cannon(
|
pub fn spawn_projectile_from_cannon(
|
||||||
|
|
@ -65,7 +58,7 @@ pub fn spawn_projectile_from_cannon(
|
||||||
cannon_query: Query<&Transform, With<LinearCannon>>,
|
cannon_query: Query<&Transform, With<LinearCannon>>,
|
||||||
mouse_input: Res<ButtonInput<MouseButton>>,
|
mouse_input: Res<ButtonInput<MouseButton>>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
mut cannon_state: ResMut<LinearCannonState>,
|
mut cannon_state: ResMut<LinearCannonState>
|
||||||
) {
|
) {
|
||||||
if !mouse_input.just_pressed(MouseButton::Left) {
|
if !mouse_input.just_pressed(MouseButton::Left) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -86,18 +79,15 @@ pub fn spawn_projectile_from_cannon(
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Sprite {
|
Sprite {
|
||||||
image,
|
image,
|
||||||
custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)),
|
custom_size: Some(Vec2::splat(STEP)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Transform::from_translation(spawn_pos),
|
Transform::from_translation(spawn_pos),
|
||||||
RoundBallProjectile {
|
RoundBallProjectile {
|
||||||
velocity: direction * 800.0,
|
velocity: direction * 800.0,
|
||||||
direction, // задаем отдельным полем, чтоб сэкономить на вычислениях в рейкастинге
|
|
||||||
previous_position: spawn_pos_2d,
|
previous_position: spawn_pos_2d,
|
||||||
ball_type,
|
ball_type,
|
||||||
hit_result: None,
|
|
||||||
},
|
},
|
||||||
IntersectMarker, // маркер для фильтрации, что шарик-снаряд может иметь пересечения с треком
|
|
||||||
LinearStateMarker,
|
LinearStateMarker,
|
||||||
));
|
));
|
||||||
cannon_state.fire();
|
cannon_state.fire();
|
||||||
|
|
@ -118,32 +108,30 @@ pub fn update_linear_cannon_preview(
|
||||||
shot_query: Query<Entity, With<ShotMarker>>,
|
shot_query: Query<Entity, With<ShotMarker>>,
|
||||||
swap_query: Query<Entity, With<SwapMarker>>,
|
swap_query: Query<Entity, With<SwapMarker>>,
|
||||||
) {
|
) {
|
||||||
let Ok(cannon_entity) = cannon_query.single() else {
|
let Ok(cannon_entity) = cannon_query.single() else { return };
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let shot_image: Handle<Image> = asset_server.load(cannon_state.shot.asset_path());
|
let shot_image: Handle<Image> = asset_server.load(cannon_state.shot.asset_path());
|
||||||
|
|
||||||
if let Ok(shot_entity) = shot_query.single() {
|
if let Ok(shot_entity) = shot_query.single() {
|
||||||
// усли шарик у дула есть, то обновляем спрайт
|
// усли шарик у дула есть, то обновляем спрайт
|
||||||
commands.entity(shot_entity).insert(Sprite {
|
commands.entity(shot_entity).insert(
|
||||||
|
Sprite{
|
||||||
image: shot_image,
|
image: shot_image,
|
||||||
custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)),
|
custom_size: Some(Vec2::splat(STEP)),
|
||||||
..default()
|
..default()
|
||||||
});
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
//иначе создаем
|
//иначе создаем
|
||||||
let preview_entity = commands
|
let preview_entity = commands.spawn((
|
||||||
.spawn((
|
Sprite{
|
||||||
Sprite {
|
|
||||||
image: shot_image,
|
image: shot_image,
|
||||||
custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)),
|
custom_size: Some(Vec2::splat(STEP)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Transform::from_xyz(0.0, STEP * 2.2, LINEAR_CANNON_BALL_Z),
|
Transform::from_xyz(0.0, STEP * 2.2, LINEAR_CANNON_BALL_Z),
|
||||||
ShotMarker,
|
ShotMarker
|
||||||
))
|
)).id();
|
||||||
.id();
|
|
||||||
commands.entity(cannon_entity).add_child(preview_entity);
|
commands.entity(cannon_entity).add_child(preview_entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,23 +139,21 @@ pub fn update_linear_cannon_preview(
|
||||||
let swap_image = asset_server.load(cannon_state.swap.asset_path());
|
let swap_image = asset_server.load(cannon_state.swap.asset_path());
|
||||||
|
|
||||||
if let Ok(swap_entity) = swap_query.single() {
|
if let Ok(swap_entity) = swap_query.single() {
|
||||||
commands.entity(swap_entity).insert(Sprite {
|
commands.entity(swap_entity).insert(Sprite{
|
||||||
image: swap_image,
|
image: swap_image,
|
||||||
custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)),
|
custom_size: Some(Vec2::splat(STEP)),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let preview_entity = commands
|
let preview_entity = commands.spawn((
|
||||||
.spawn((
|
Sprite{
|
||||||
Sprite {
|
|
||||||
image: swap_image,
|
image: swap_image,
|
||||||
custom_size: Some(Vec2::splat(ROUND_BALL_SIZE)),
|
custom_size: Some(Vec2::splat(STEP)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Transform::from_xyz(0.0, -STEP * 1.5, LINEAR_CANNON_BALL_Z),
|
Transform::from_xyz(0.0, -STEP * 1.5, LINEAR_CANNON_BALL_Z),
|
||||||
SwapMarker,
|
SwapMarker,
|
||||||
))
|
)).id();
|
||||||
.id();
|
|
||||||
commands.entity(cannon_entity).add_child(preview_entity);
|
commands.entity(cannon_entity).add_child(preview_entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,255 +180,10 @@ pub fn linear_move_projectiles(
|
||||||
proj.previous_position = new_pos;
|
proj.previous_position = new_pos;
|
||||||
|
|
||||||
// удаляем снаряды, улетевшие за экран
|
// удаляем снаряды, улетевшие за экран
|
||||||
if new_pos.x.abs() > 2.0 * (WIDTH * FACTOR) as f32
|
if new_pos.x.abs() > 1.5 * (WIDTH * FACTOR) as f32 || new_pos.y.abs() > 1.5 * (HEIGHT * FACTOR) as f32 {
|
||||||
|| new_pos.y.abs() > 2.0 * (HEIGHT * FACTOR) as f32
|
|
||||||
{
|
|
||||||
commands.entity(entity).despawn();
|
commands.entity(entity).despawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_projectile_hits(
|
// pub fn detect_track_hit(){} //TODO
|
||||||
mut commands: Commands,
|
|
||||||
mut projectiles: Query<
|
|
||||||
(Entity, &mut RoundBallProjectile, &mut Transform),
|
|
||||||
With<IntersectMarker>,
|
|
||||||
>,
|
|
||||||
track: Res<PrecalculatedTrack>,
|
|
||||||
balls_on_track: Query<&RoundBall>,
|
|
||||||
) {
|
|
||||||
for (entity, mut proj, mut transform) in projectiles.iter_mut() {
|
|
||||||
// если уже посчитано место попадания, пропускаем
|
|
||||||
if proj.hit_result.is_some() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ищем все точки пересечений с треком
|
|
||||||
let mut intersections = raycast_track(proj.previous_position, proj.direction, &track);
|
|
||||||
|
|
||||||
// сортируем от ближайшего t к дальнему
|
|
||||||
intersections.sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap());
|
|
||||||
|
|
||||||
// если есть точки пересечения
|
|
||||||
if let Some(closest_hit) = intersections.first() {
|
|
||||||
// собираем шарики
|
|
||||||
let mut occupied: Vec<f32> = balls_on_track.iter().map(|b| b.track_progress).collect();
|
|
||||||
// сортируем прогресс для дальнейших вычислений
|
|
||||||
occupied.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
|
||||||
|
|
||||||
//считаем прогресс для точки попадания через мировые координаты и индекс сегмента на треке
|
|
||||||
let hit_progress = calculate_progress(closest_hit, &track);
|
|
||||||
|
|
||||||
let radius_norm = BALL_RADIUS / track.total_length;
|
|
||||||
if is_occupied(hit_progress, &occupied, radius_norm) {
|
|
||||||
proj.hit_result = Some(ProjectileHit {
|
|
||||||
hit_progress,
|
|
||||||
hit_position: closest_hit.world_pos,
|
|
||||||
segment_index: closest_hit.segment_index,
|
|
||||||
});
|
|
||||||
println!("Попадание в трек: {}", hit_progress);
|
|
||||||
transform.translation = closest_hit.world_pos.extend(transform.translation.z);
|
|
||||||
// убираем маркер поиска
|
|
||||||
commands.entity(entity).remove::<IntersectMarker>();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Пересечений нет -> убираем маркер, снаряд летит дальше
|
|
||||||
commands.entity(entity).remove::<IntersectMarker>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn raycast_track(origin: Vec2, direction: Vec2, track: &PrecalculatedTrack) -> Vec<Intersection> {
|
|
||||||
let mut intersections = vec![];
|
|
||||||
|
|
||||||
for (i, seg) in track.segments.iter().enumerate() {
|
|
||||||
match seg.segment_type {
|
|
||||||
SegementType::Line => {
|
|
||||||
// ищем пересечение
|
|
||||||
if let Some(t) =
|
|
||||||
intersect_line(origin, direction, seg.start_pos, seg.direction, seg.length)
|
|
||||||
{
|
|
||||||
let pos = origin + direction * t;
|
|
||||||
intersections.push(Intersection {
|
|
||||||
t,
|
|
||||||
world_pos: pos,
|
|
||||||
segment_index: i,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SegementType::Turn => {
|
|
||||||
let hits = intersect_arc(
|
|
||||||
origin,
|
|
||||||
direction,
|
|
||||||
seg.center,
|
|
||||||
seg.radius,
|
|
||||||
seg.start_angle,
|
|
||||||
seg.sweep_sign,
|
|
||||||
);
|
|
||||||
|
|
||||||
for t in hits {
|
|
||||||
let pos = origin + direction * t;
|
|
||||||
intersections.push(Intersection {
|
|
||||||
t,
|
|
||||||
world_pos: pos,
|
|
||||||
segment_index: i,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
intersections
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intersect_line(
|
|
||||||
origin: Vec2,
|
|
||||||
direction: Vec2,
|
|
||||||
seg_start: Vec2,
|
|
||||||
seg_dir: Vec2,
|
|
||||||
seg_length: f32,
|
|
||||||
) -> Option<f32> {
|
|
||||||
// вектор самого отрезка трека
|
|
||||||
let seg_vec = seg_dir * seg_length;
|
|
||||||
|
|
||||||
// знаменатель: векторное произведение направления луча и вектора отрезка
|
|
||||||
// x1*y2 - x2*y1
|
|
||||||
let denom = direction.x * seg_vec.y - direction.y * seg_vec.x;
|
|
||||||
|
|
||||||
// если знаменатель близок к нулю - луч параллелен отрезку, пересечения нет
|
|
||||||
if denom.abs() < 1e-6 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let w = seg_start - origin;
|
|
||||||
|
|
||||||
// вычисляем параметры t и u
|
|
||||||
// t - расстояние по лучу
|
|
||||||
// u - позиция на отрезке (0.0 = начало, 1.0 = конец)
|
|
||||||
let t = (w.x * seg_vec.y - w.y * seg_vec.x) / denom;
|
|
||||||
let u = (w.x * direction.y - w.y * direction.x) / denom;
|
|
||||||
|
|
||||||
// проверяем условия попадания
|
|
||||||
// t > 0 - точка пересечения находится перед пушкой (а не за спиной)
|
|
||||||
// u >= 0 && u <= 1 - точка пересечения находится внутри отрезка (не на бесконечной линии)
|
|
||||||
if t > 0.0 && u >= 0.0 && u <= 1.0 {
|
|
||||||
return Some(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intersect_arc(
|
|
||||||
origin: Vec2,
|
|
||||||
direction: Vec2,
|
|
||||||
center: Vec2,
|
|
||||||
radius: f32,
|
|
||||||
start_angle: f32,
|
|
||||||
sweep_sign: f32,
|
|
||||||
) -> Vec<f32> {
|
|
||||||
// возвращает список дистанций t до точек пересечения с дугой.
|
|
||||||
// может вернуть 0, 1 или 2 точки (вход и выход), если луч проходит сквозь дугу.
|
|
||||||
|
|
||||||
let mut hits = Vec::new();
|
|
||||||
|
|
||||||
// считаем пересечения луча и полной окружности через квадратное уравнение
|
|
||||||
|
|
||||||
let l = origin - center;
|
|
||||||
let b = 2.0 * l.dot(direction); // поскольку направление нормализовано, a = 1
|
|
||||||
let c = l.dot(l) - radius * radius;
|
|
||||||
|
|
||||||
let discriminant = b * b - 4.0 * c;
|
|
||||||
|
|
||||||
// если дискриминант меньше нуля, луч пролетает мимо окружности
|
|
||||||
if discriminant < 0.0 {
|
|
||||||
return hits;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sqrt_discriminant = discriminant.sqrt();
|
|
||||||
|
|
||||||
// находим корни
|
|
||||||
let roots = vec![
|
|
||||||
(-b - sqrt_discriminant) / 2.0,
|
|
||||||
(-b + sqrt_discriminant) / 2.0,
|
|
||||||
];
|
|
||||||
|
|
||||||
for t in roots {
|
|
||||||
// ищем только положительные точки, потому что они будут перед пушкой
|
|
||||||
if t > 0.0 {
|
|
||||||
let hit_point = origin + direction * t;
|
|
||||||
|
|
||||||
// попали ли в сектор дуги
|
|
||||||
let hit_angle = (hit_point - center).to_angle();
|
|
||||||
|
|
||||||
// находим разницу между углом попадания и стартовым углом
|
|
||||||
let mut angle_diff = hit_angle - start_angle;
|
|
||||||
|
|
||||||
// нормализуем разницу в диапазон [-PI, PI]
|
|
||||||
// это нужно, чтобы корректно обрабатывать переход через плюс/минус пи
|
|
||||||
while angle_diff > PI {
|
|
||||||
angle_diff -= 2.0 * PI;
|
|
||||||
}
|
|
||||||
while angle_diff < -PI {
|
|
||||||
angle_diff += 2.0 * PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
// проверяем, лежит ли разница в пределах 90 градусов (пи пополам) с учетом направления
|
|
||||||
let is_valid_angle = if sweep_sign > 0.0 {
|
|
||||||
// против часовой
|
|
||||||
angle_diff >= 0.0 && angle_diff <= FRAC_PI_2
|
|
||||||
} else {
|
|
||||||
// по часовой
|
|
||||||
angle_diff >= -FRAC_PI_2 && angle_diff <= 0.0
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_valid_angle {
|
|
||||||
hits.push(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hits
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_progress(inter: &Intersection, track: &PrecalculatedTrack) -> f32 {
|
|
||||||
// берем только нужный сегмент трека, где есть пересечение
|
|
||||||
let track_seg = &track.segments[inter.segment_index];
|
|
||||||
|
|
||||||
// по типу сегмента переводим точку из мировых координат Vec2 в прогресс трека 0.0 ... 1.0
|
|
||||||
match track_seg.segment_type {
|
|
||||||
SegementType::Line => {
|
|
||||||
let diff = inter.world_pos - track_seg.start_pos;
|
|
||||||
let local_t = (diff.x * track_seg.direction.x + diff.y * track_seg.direction.y)
|
|
||||||
/ track_seg.length;
|
|
||||||
// возвращаем прогресс
|
|
||||||
track_seg.t_start + (track_seg.t_end - track_seg.t_start) * local_t
|
|
||||||
}
|
|
||||||
|
|
||||||
SegementType::Turn => {
|
|
||||||
let hit_angle = (inter.world_pos - track_seg.center).to_angle();
|
|
||||||
let mut angle_diff = hit_angle - track_seg.start_angle;
|
|
||||||
|
|
||||||
// критичный момент - важно нормализовать разницу в диапазон от -пи до +пи, чтоб он не улетел
|
|
||||||
while angle_diff > PI {
|
|
||||||
angle_diff -= 2.0 * PI
|
|
||||||
}
|
|
||||||
while angle_diff < -PI {
|
|
||||||
angle_diff += 2.0 * PI
|
|
||||||
}
|
|
||||||
|
|
||||||
// учитываем направление обхода
|
|
||||||
let local_t = angle_diff / (track_seg.sweep_sign * FRAC_PI_2);
|
|
||||||
|
|
||||||
// возвращаем прогресс
|
|
||||||
track_seg.t_start + (track_seg.t_end - track_seg.t_start) * local_t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_occupied(target: f32, occupied: &[f32], radius_norm: f32) -> bool {
|
|
||||||
let min = target - radius_norm;
|
|
||||||
let max = target + radius_norm;
|
|
||||||
|
|
||||||
occupied.iter().any(|&p| p >= min && p <= max)
|
|
||||||
}
|
|
||||||
|
|
@ -7,8 +7,8 @@ pub fn setup_linear_track() -> Track {
|
||||||
println!("Построение трека начато");
|
println!("Построение трека начато");
|
||||||
Track {
|
Track {
|
||||||
start_point: Vec2 {
|
start_point: Vec2 {
|
||||||
x: CENTER_X - STEP * 7.0 * 2.0,
|
x: CENTER_X - FACTOR as f32 * 7.0,
|
||||||
y: CENTER_Y - STEP * 4.0 * 2.0,
|
y: CENTER_Y - FACTOR as f32 * 4.0,
|
||||||
},
|
},
|
||||||
start_direction: Vec2::X,
|
start_direction: Vec2::X,
|
||||||
segments: vec![
|
segments: vec![
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
use crate::states::AppState;
|
use crate::states::AppState;
|
||||||
use crate::states::AppState::LinearPlayState;
|
use crate::states::AppState::LinearPlayState;
|
||||||
use crate::states::linear::plugin::LinearUpdateSet;
|
use crate::states::linear::plugin::LinearUpdateSet;
|
||||||
use crate::states::linear::{DebugToggle, LinearRestartMarker, LinearStateMarker};
|
use crate::states::linear::{LinearRestartMarker, LinearStateMarker};
|
||||||
use crate::ui::button_click::ButtonClickMessage;
|
use crate::ui::button_click::ButtonClickMessage;
|
||||||
use crate::ui::click::handle_click_system;
|
use crate::ui::click::handle_click_system;
|
||||||
use crate::ui::{ButtonStyle, spawn_button, spawn_background};
|
use crate::ui::{ButtonStyle, spawn_button};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use crate::{FACTOR, HEIGHT, WIDTH};
|
|
||||||
|
|
||||||
pub struct LinearUIPlugin;
|
pub struct LinearUIPlugin;
|
||||||
|
|
||||||
|
|
@ -45,63 +44,25 @@ fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
))
|
))
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
let button_style = ButtonStyle {
|
let restart_button_style = ButtonStyle {
|
||||||
font: asset_server.load("fonts/QR Ames Beta.otf"),
|
font: asset_server.load("fonts/QR Ames Beta.otf"),
|
||||||
font_size: 16.0,
|
font_size: 24.0,
|
||||||
text_color: Color::WHITE,
|
text_color: Color::WHITE,
|
||||||
normal_bg: Color::linear_rgba(0.15, 0.15, 0.15, 1.0),
|
normal_bg: Color::linear_rgba(0.15, 0.15, 0.15, 1.0),
|
||||||
hovered_bg: Color::linear_rgb(0.25, 0.25, 0.25),
|
hovered_bg: Color::linear_rgb(0.25, 0.25, 0.25),
|
||||||
pressed_bg: Color::linear_rgb(0.3, 0.3, 0.3),
|
pressed_bg: Color::linear_rgb(0.3, 0.3, 0.3),
|
||||||
width: Val::Px(100.0),
|
width: Val::Px(200.0),
|
||||||
height: Val::Px(25.0),
|
height: Val::Px(50.0),
|
||||||
margin: UiRect::all(Val::Px(10.0)),
|
margin: UiRect::all(Val::Px(10.0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let bg_image: Handle<Image> = asset_server.load("sprites/bg/linear_bg.png");
|
|
||||||
commands.spawn((
|
|
||||||
Sprite{
|
|
||||||
image: bg_image,
|
|
||||||
custom_size: Some(Vec2::new((WIDTH*FACTOR) as f32, (HEIGHT*FACTOR) as f32)),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Transform::from_xyz(0.0, 0.0, -10.0),
|
|
||||||
LinearStateMarker,
|
|
||||||
));
|
|
||||||
// spawn_background(&mut commands, bg_image, LinearStateMarker);
|
|
||||||
|
|
||||||
spawn_button(
|
spawn_button(
|
||||||
&mut commands,
|
&mut commands,
|
||||||
root,
|
root,
|
||||||
"Restart",
|
"Restart",
|
||||||
&button_style,
|
&restart_button_style,
|
||||||
LinearRestartMarker,
|
LinearRestartMarker,
|
||||||
);
|
);
|
||||||
|
|
||||||
spawn_button(
|
|
||||||
&mut commands,
|
|
||||||
root,
|
|
||||||
"Track",
|
|
||||||
&button_style,
|
|
||||||
DebugToggle::Track,
|
|
||||||
);
|
|
||||||
|
|
||||||
spawn_button(
|
|
||||||
&mut commands,
|
|
||||||
root,
|
|
||||||
"Grid",
|
|
||||||
&button_style,
|
|
||||||
DebugToggle::Grid,
|
|
||||||
);
|
|
||||||
|
|
||||||
spawn_button(
|
|
||||||
&mut commands,
|
|
||||||
root,
|
|
||||||
"Laser",
|
|
||||||
&button_style,
|
|
||||||
DebugToggle::Laser,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restart_game_button_system(
|
pub fn restart_game_button_system(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue