cannon components and systems

This commit is contained in:
nquidox 2026-04-18 00:40:45 +03:00
parent 71f6b549b7
commit 30412844b8
5 changed files with 253 additions and 2 deletions

View file

@ -0,0 +1,46 @@
use crate::states::linear::RoundBallType;
use bevy::prelude::*;
use std::mem;
#[derive(Component)]
pub struct LinearCannon;
#[derive(Resource, Debug, Clone)]
pub struct LinearCannonState {
pub shot: RoundBallType,
pub swap: RoundBallType,
}
impl Default for LinearCannonState {
fn default() -> Self {
Self {
shot: RoundBallType::random_from_4(),
swap: RoundBallType::random_from_4(),
}
}
}
impl LinearCannonState {
pub fn fire(&mut self) {
self.shot = self.swap;
self.swap = RoundBallType::random_from_4();
}
pub fn cycle(&mut self) {
mem::swap(&mut self.shot, &mut self.swap);
}
}
#[derive(Component)]
pub struct RoundBallProjectile {
pub velocity: Vec2,
pub previous_position: Vec2,
pub ball_type: RoundBallType,
}
#[derive(Component)]
pub struct ShotMarker;
#[derive(Component)]
pub struct SwapMarker;

View file

@ -11,6 +11,9 @@ pub const SCALE: f32 = 2.0;
// Z-INDEXES
pub const ROUND_BALL_Z: f32 = 10.0;
pub const ROUND_SHOT_Z: f32 = 11.0;
pub const LINEAR_CANNON_Z: f32 = 20.0;
pub const LINEAR_CANNON_BALL_Z: f32 = 15.0;
// COLORS
pub const PINK: Color = Color::srgb_u8(250, 0, 155);

View file

@ -21,5 +21,10 @@ pub use constants::*;
mod ui;
pub use ui::*;
mod components_cannon;
pub use components_cannon::*;
mod systems_cannon;
pub use systems_cannon::*;

View file

@ -14,7 +14,7 @@ pub enum LinearUpdateSet {
impl Plugin for LinearPlayPlugin {
fn build(&self, app: &mut App) {
app
.add_systems(OnEnter(LinearPlayState), setup)
.add_systems(OnEnter(LinearPlayState), (setup, spawn_linear_cannon).chain())
.add_systems(OnEnter(LinearGameRestart), linear_restart)
.add_plugins(LinearUIPlugin)
.configure_sets(
@ -26,8 +26,14 @@ impl Plugin for LinearPlayPlugin {
(
draw_track_gizmos,
draw_grid,
//несколько систем с соблюдением порядка
linear_move_projectiles,
cycle_cannon_balls,
update_linear_cannon_preview,
spawn_projectile_from_cannon,
spawn_round_ball,
move_round_balls,
rotate_linear_cannon,
)
.in_set(LinearUpdateSet::Track),
)
@ -41,6 +47,7 @@ fn setup(mut commands: Commands) {
commands.insert_resource(build_track);
commands.insert_resource(precalculated_track);
commands.insert_resource(build_linear_cannon_state());
}
fn cleanup(mut commands: Commands, query: Query<Entity, With<LinearStateMarker>>) {
@ -50,8 +57,9 @@ 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::<LinearCannonState>();
}
fn linear_restart(mut next_state: ResMut<NextState<AppState>>) {

View file

@ -0,0 +1,189 @@
use crate::states::linear::*;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
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>) {
let image: Handle<Image> = asset_server.load("sprites/cannon/cannon.png");
commands.spawn((
Sprite {
image,
custom_size: Some(Vec2::new(STEP * 2.5, STEP * 4.0)),
..default()
},
Transform::from_xyz(6.0 * STEP, 3.0 * STEP, LINEAR_CANNON_Z),
LinearStateMarker,
LinearCannon,
));
}
pub fn rotate_linear_cannon(
mut query: Query<&mut Transform, With<LinearCannon>>,
window_query: Query<&Window, With<PrimaryWindow>>,
camera_query: Query<(&Camera, &GlobalTransform)>,
) {
let Ok(mut cannon_tf) = query.single_mut() 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 cannon_pos = Vec2::new(cannon_tf.translation.x, cannon_tf.translation.y);
let direction = world_cursor_pos - cannon_pos;
let angle = direction.to_angle() - FRAC_PI_2;
cannon_tf.rotation = Quat::from_rotation_z(angle);
}
pub fn spawn_projectile_from_cannon(
mut commands: Commands,
cannon_query: Query<&Transform, With<LinearCannon>>,
mouse_input: Res<ButtonInput<MouseButton>>,
asset_server: Res<AssetServer>,
mut cannon_state: ResMut<LinearCannonState>
) {
if !mouse_input.just_pressed(MouseButton::Left) {
return;
}
let Ok(cannon_tf) = cannon_query.single() else {
return;
};
let ball_type = cannon_state.shot;
let image: Handle<Image> = asset_server.load(ball_type.asset_path());
let offset = 100.0; // сдвиг от центра к дулу пушки
let direction = cannon_tf.rotation.mul_vec3(Vec3::Y).truncate().normalize();
let spawn_pos_2d = cannon_tf.translation.truncate() + direction * offset;
let spawn_pos = spawn_pos_2d.extend(ROUND_SHOT_Z);
commands.spawn((
Sprite {
image,
custom_size: Some(Vec2::splat(STEP)),
..default()
},
Transform::from_translation(spawn_pos),
RoundBallProjectile {
velocity: direction * 800.0,
previous_position: spawn_pos_2d,
ball_type,
},
LinearStateMarker,
));
cannon_state.fire();
}
pub fn build_linear_cannon_state() -> LinearCannonState {
LinearCannonState {
shot: RoundBallType::random_from_4(),
swap: RoundBallType::random_from_4(),
}
}
pub fn update_linear_cannon_preview(
mut commands: Commands,
cannon_state: Res<LinearCannonState>,
asset_server: Res<AssetServer>,
cannon_query: Query<Entity, With<LinearCannon>>,
shot_query: Query<Entity, With<ShotMarker>>,
swap_query: Query<Entity, With<SwapMarker>>,
) {
let Ok(cannon_entity) = cannon_query.single() else { return };
let shot_image: Handle<Image> = asset_server.load(cannon_state.shot.asset_path());
if let Ok(shot_entity) = shot_query.single() {
// усли шарик у дула есть, то обновляем спрайт
commands.entity(shot_entity).insert(
Sprite{
image: shot_image,
custom_size: Some(Vec2::splat(STEP)),
..default()
}
);
} else {
//иначе создаем
let preview_entity = commands.spawn((
Sprite{
image: shot_image,
custom_size: Some(Vec2::splat(STEP)),
..default()
},
Transform::from_xyz(0.0, STEP * 2.2, LINEAR_CANNON_BALL_Z),
ShotMarker
)).id();
commands.entity(cannon_entity).add_child(preview_entity);
}
// следующий шарик в хвосте
let swap_image = asset_server.load(cannon_state.swap.asset_path());
if let Ok(swap_entity) = swap_query.single() {
commands.entity(swap_entity).insert(Sprite{
image: swap_image,
custom_size: Some(Vec2::splat(STEP)),
..default()
});
} else {
let preview_entity = commands.spawn((
Sprite{
image: swap_image,
custom_size: Some(Vec2::splat(STEP)),
..default()
},
Transform::from_xyz(0.0, -STEP * 1.5, LINEAR_CANNON_BALL_Z),
SwapMarker,
)).id();
commands.entity(cannon_entity).add_child(preview_entity);
}
}
pub fn cycle_cannon_balls(
mut cannon_state: ResMut<LinearCannonState>,
mouse_input: Res<ButtonInput<MouseButton>>,
) {
if mouse_input.just_pressed(MouseButton::Right) {
cannon_state.cycle();
}
}
pub fn linear_move_projectiles(
mut commands: Commands,
mut projectiles: Query<(Entity, &mut RoundBallProjectile, &mut Transform)>,
time: Res<Time>,
) {
for (entity, mut proj, mut transform) in projectiles.iter_mut() {
let delta = proj.velocity * time.delta_secs();
let new_pos = proj.previous_position + delta;
transform.translation = new_pos.extend(transform.translation.z);
proj.previous_position = new_pos;
// удаляем снаряды, улетевшие за экран
if new_pos.x.abs() > 1.5 * (WIDTH * FACTOR) as f32 || new_pos.y.abs() > 1.5 * (HEIGHT * FACTOR) as f32 {
commands.entity(entity).despawn();
}
}
}
// pub fn detect_track_hit(){} //TODO