chore(deps): upgrade Bevy 0.13 → 0.18
Three major versions of breaking changes: - Bundles → Required Components (SpriteBundle, NodeBundle, ButtonBundle, TextBundle, Camera2dBundle removed; spawn tuples of components instead) - Style merged into Node; TextStyle split into TextFont + TextColor - Color API: rgb/rgba → srgb/srgba; Color::Rgba pattern matching replaced with .alpha()/.set_alpha(); .r()/.g()/.b() → .to_srgba().red/green/blue - Time: delta_seconds() → delta_secs(); elapsed_seconds() → elapsed_secs() - Query: get_single()/get_single_mut() → single()/single_mut() (now Result) - Timer::finished() → Timer::is_finished() - despawn_recursive() removed (despawn() is now recursive); despawn_descendants() removed — replaced with Children query iteration - BorderColor(c) → BorderColor::all(c) - WindowResolution: From<(f32,f32)> removed → cast to (u32,u32) - flake.nix: added wayland to runtime libs (default-on in 0.18) Tests pass (8/8), clippy clean, headless render verified showing start menu, button, starfield, and player ship. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a4305677b
commit
b2b564f690
11 changed files with 2746 additions and 1028 deletions
160
src/enemy.rs
160
src/enemy.rs
|
|
@ -78,20 +78,17 @@ pub fn spawn_enemies(
|
|||
|
||||
// Determine sprite color based on type
|
||||
let sprite_color = match enemy_type {
|
||||
EnemyType::Grunt => Color::rgb(1.0, 0.2, 0.2),
|
||||
EnemyType::Boss => Color::rgb(0.8, 0.2, 1.0),
|
||||
EnemyType::Grunt => Color::srgb(1.0, 0.2, 0.2),
|
||||
EnemyType::Boss => Color::srgb(0.8, 0.2, 1.0),
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: sprite_color,
|
||||
custom_size: Some(ENEMY_SIZE),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_translation(Vec3::new(spawn_x, spawn_y, 0.0)),
|
||||
Sprite {
|
||||
color: sprite_color,
|
||||
custom_size: Some(ENEMY_SIZE),
|
||||
..default()
|
||||
},
|
||||
Transform::from_translation(Vec3::new(spawn_x, spawn_y, 0.0)),
|
||||
Enemy {
|
||||
enemy_type,
|
||||
// Use shoot interval from stage config
|
||||
|
|
@ -154,7 +151,7 @@ pub fn move_enemies(
|
|||
// Calculate speeds for this frame
|
||||
let base_speed = ENEMY_SPEED * speed_multiplier;
|
||||
let attack_speed = base_speed * 1.5; // Attackers are faster
|
||||
let arrival_threshold = base_speed * time.delta_seconds() * 1.1; // Threshold for reaching formation target
|
||||
let arrival_threshold = base_speed * time.delta_secs() * 1.1; // Threshold for reaching formation target
|
||||
|
||||
// --- Handle Entering Enemies ---
|
||||
for (entity, mut transform, target, mut state) in entering_query.iter_mut() {
|
||||
|
|
@ -183,7 +180,7 @@ pub fn move_enemies(
|
|||
);
|
||||
} else {
|
||||
// Move towards target using base_speed
|
||||
let move_delta = direction.normalize() * base_speed * time.delta_seconds();
|
||||
let move_delta = direction.normalize() * base_speed * time.delta_secs();
|
||||
transform.translation += move_delta;
|
||||
}
|
||||
}
|
||||
|
|
@ -202,12 +199,12 @@ pub fn move_enemies(
|
|||
// ... existing patterns ...
|
||||
AttackPattern::SwoopDive => {
|
||||
// ... existing code ...
|
||||
let vertical_movement = attack_speed * time.delta_seconds();
|
||||
let vertical_movement = attack_speed * time.delta_secs();
|
||||
let horizontal_speed_factor = 0.5;
|
||||
let horizontal_movement = if transform.translation.x < 0.0 {
|
||||
attack_speed * horizontal_speed_factor * time.delta_seconds()
|
||||
attack_speed * horizontal_speed_factor * time.delta_secs()
|
||||
} else if transform.translation.x > 0.0 {
|
||||
-attack_speed * horizontal_speed_factor * time.delta_seconds()
|
||||
-attack_speed * horizontal_speed_factor * time.delta_secs()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
|
@ -225,7 +222,7 @@ pub fn move_enemies(
|
|||
}
|
||||
}
|
||||
AttackPattern::DirectDive => {
|
||||
transform.translation.y -= attack_speed * time.delta_seconds();
|
||||
transform.translation.y -= attack_speed * time.delta_secs();
|
||||
}
|
||||
AttackPattern::Kamikaze(target) => {
|
||||
// Copy the target value rather than dereferencing
|
||||
|
|
@ -233,11 +230,11 @@ pub fn move_enemies(
|
|||
let target_pos = *target; // Dereference here
|
||||
let direction = target_pos - transform.translation;
|
||||
let distance = direction.length();
|
||||
let kamikaze_threshold = attack_speed * time.delta_seconds() * 1.1; // Threshold to stop near target
|
||||
let kamikaze_threshold = attack_speed * time.delta_secs() * 1.1; // Threshold to stop near target
|
||||
|
||||
if distance > kamikaze_threshold {
|
||||
let move_delta =
|
||||
direction.normalize() * attack_speed * time.delta_seconds();
|
||||
direction.normalize() * attack_speed * time.delta_secs();
|
||||
transform.translation += move_delta;
|
||||
} else {
|
||||
// Optionally stop or continue past target - for now, just stop moving towards it
|
||||
|
|
@ -247,7 +244,7 @@ pub fn move_enemies(
|
|||
// New CaptureBeam pattern - Bosses behave differently
|
||||
AttackPattern::CaptureBeam => {
|
||||
// For Grunt enemies, just do a direct dive (fallback)
|
||||
transform.translation.y -= attack_speed * time.delta_seconds();
|
||||
transform.translation.y -= attack_speed * time.delta_secs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -261,7 +258,7 @@ pub fn move_enemies(
|
|||
if transform.translation.y > target_y {
|
||||
// Move down to position
|
||||
transform.translation.y -=
|
||||
attack_speed * 0.8 * time.delta_seconds();
|
||||
attack_speed * 0.8 * time.delta_secs();
|
||||
} else {
|
||||
// Once in position, stay there briefly before activating beam
|
||||
// Check if this boss already has a TractorBeam component
|
||||
|
|
@ -293,11 +290,11 @@ pub fn move_enemies(
|
|||
if direction.length() > 0.0 {
|
||||
let normalized_dir = direction.normalize();
|
||||
transform.translation +=
|
||||
normalized_dir * attack_speed * time.delta_seconds();
|
||||
normalized_dir * attack_speed * time.delta_secs();
|
||||
}
|
||||
}
|
||||
AttackPattern::DirectDive => {
|
||||
transform.translation.y -= attack_speed * time.delta_seconds();
|
||||
transform.translation.y -= attack_speed * time.delta_secs();
|
||||
}
|
||||
AttackPattern::Kamikaze(target) => {
|
||||
// Convert the target to a value type
|
||||
|
|
@ -306,12 +303,12 @@ pub fn move_enemies(
|
|||
|
||||
// If very close to target, just move straight down
|
||||
if direction.length() < 50.0 {
|
||||
transform.translation.y -= attack_speed * time.delta_seconds();
|
||||
transform.translation.y -= attack_speed * time.delta_secs();
|
||||
} else {
|
||||
// Move toward target
|
||||
let normalized_dir = direction.normalize();
|
||||
transform.translation +=
|
||||
normalized_dir * attack_speed * time.delta_seconds();
|
||||
normalized_dir * attack_speed * time.delta_secs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -334,7 +331,7 @@ pub fn move_enemies(
|
|||
} else {
|
||||
// Move towards formation position
|
||||
let move_delta =
|
||||
direction.normalize() * return_speed * time.delta_seconds();
|
||||
direction.normalize() * return_speed * time.delta_secs();
|
||||
transform.translation += move_delta;
|
||||
}
|
||||
}
|
||||
|
|
@ -463,7 +460,7 @@ pub fn trigger_attack_dives(
|
|||
|
||||
// If Kamikaze, get player position (if player exists)
|
||||
if let AttackPattern::Kamikaze(_) = selected_pattern {
|
||||
if let Ok(player_transform) = player_query.get_single() {
|
||||
if let Ok(player_transform) = player_query.single() {
|
||||
selected_pattern = AttackPattern::Kamikaze(player_transform.translation);
|
||||
} else {
|
||||
// Fallback if player doesn't exist (e.g., just died)
|
||||
|
|
@ -496,21 +493,18 @@ pub fn enemy_shoot(
|
|||
if matches!(state, EnemyState::Attacking(_)) {
|
||||
// Use matches! macro
|
||||
enemy.shoot_cooldown.tick(time.delta());
|
||||
if enemy.shoot_cooldown.finished() {
|
||||
if enemy.shoot_cooldown.is_finished() {
|
||||
// println!("Enemy {:?} firing!", transform.translation); // Less verbose logging
|
||||
let bullet_start_pos = transform.translation
|
||||
- Vec3::Y * (ENEMY_SIZE.y / 2.0 + ENEMY_BULLET_SIZE.y / 2.0);
|
||||
|
||||
commands.spawn((
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: Color::rgb(1.0, 0.5, 0.5),
|
||||
custom_size: Some(ENEMY_BULLET_SIZE),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_translation(bullet_start_pos),
|
||||
Sprite {
|
||||
color: Color::srgb(1.0, 0.5, 0.5),
|
||||
custom_size: Some(ENEMY_BULLET_SIZE),
|
||||
..default()
|
||||
},
|
||||
Transform::from_translation(bullet_start_pos),
|
||||
EnemyBullet,
|
||||
));
|
||||
|
||||
|
|
@ -546,6 +540,7 @@ pub fn boss_capture_attack(
|
|||
mut boss_query: Query<(Entity, &Transform, &mut TractorBeam)>,
|
||||
player_query: Query<(Entity, &Transform), (With<Player>, Without<Captured>)>, // Only target non-captured players
|
||||
enemy_query: Query<&EnemyState, With<Enemy>>,
|
||||
children_query: Query<&Children>,
|
||||
) {
|
||||
for (boss_entity, boss_transform, mut tractor_beam) in boss_query.iter_mut() {
|
||||
// Tick the beam timer
|
||||
|
|
@ -553,7 +548,7 @@ pub fn boss_capture_attack(
|
|||
|
||||
// If player exists and beam is not active yet, set player as target
|
||||
if !tractor_beam.active && tractor_beam.target.is_none() {
|
||||
if let Ok((player_entity, _)) = player_query.get_single() {
|
||||
if let Ok((player_entity, _)) = player_query.single() {
|
||||
tractor_beam.target = Some(player_entity);
|
||||
tractor_beam.active = true;
|
||||
println!(
|
||||
|
|
@ -566,33 +561,27 @@ pub fn boss_capture_attack(
|
|||
commands.entity(boss_entity).with_children(|parent| {
|
||||
// Outer glow layer (wide, dim)
|
||||
parent.spawn((
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: BEAM_GLOW_COLOR,
|
||||
custom_size: Some(Vec2::new(BEAM_GLOW_WIDTH, beam_height)),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(0.0, -beam_height / 2.0, -0.5),
|
||||
Sprite {
|
||||
color: BEAM_GLOW_COLOR,
|
||||
custom_size: Some(Vec2::new(BEAM_GLOW_WIDTH, beam_height)),
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(0.0, -beam_height / 2.0, -0.5),
|
||||
TractorBeamSprite,
|
||||
));
|
||||
// Inner core layer (narrow, bright, shorter)
|
||||
let core_height = beam_height * 0.6;
|
||||
parent.spawn((
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: BEAM_CORE_COLOR,
|
||||
custom_size: Some(Vec2::new(TRACTOR_BEAM_WIDTH, core_height)),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(
|
||||
0.0,
|
||||
-core_height / 2.0 - beam_height * 0.2,
|
||||
-0.3,
|
||||
),
|
||||
Sprite {
|
||||
color: BEAM_CORE_COLOR,
|
||||
custom_size: Some(Vec2::new(TRACTOR_BEAM_WIDTH, core_height)),
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(
|
||||
0.0,
|
||||
-core_height / 2.0 - beam_height * 0.2,
|
||||
-0.3,
|
||||
),
|
||||
TractorBeamSprite,
|
||||
));
|
||||
});
|
||||
|
|
@ -601,7 +590,7 @@ pub fn boss_capture_attack(
|
|||
|
||||
// If beam is active, check if player is in beam's path
|
||||
if tractor_beam.active {
|
||||
if let Ok((player_entity, player_transform)) = player_query.get_single() {
|
||||
if let Ok((player_entity, player_transform)) = player_query.single() {
|
||||
// Check if player is roughly under the boss
|
||||
if (player_transform.translation.x - boss_transform.translation.x).abs()
|
||||
< tractor_beam.width / 2.0
|
||||
|
|
@ -622,14 +611,16 @@ pub fn boss_capture_attack(
|
|||
commands.entity(boss_entity).remove::<TractorBeam>();
|
||||
|
||||
// Change boss state to returning with captive
|
||||
if let Ok(enemy) = enemy_query.get(boss_entity) {
|
||||
if let EnemyState::Attacking(_) = enemy {
|
||||
commands
|
||||
.entity(boss_entity)
|
||||
.insert(EnemyState::ReturningWithCaptive);
|
||||
if let Ok(EnemyState::Attacking(_)) = enemy_query.get(boss_entity) {
|
||||
commands
|
||||
.entity(boss_entity)
|
||||
.insert(EnemyState::ReturningWithCaptive);
|
||||
|
||||
// Clean up the beam visual
|
||||
commands.entity(boss_entity).despawn_descendants();
|
||||
// Clean up the beam visual
|
||||
if let Ok(children) = children_query.get(boss_entity) {
|
||||
for child in children.iter() {
|
||||
commands.entity(child).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -638,20 +629,22 @@ pub fn boss_capture_attack(
|
|||
}
|
||||
|
||||
// If beam timer finishes and player wasn't captured, end the beam attack
|
||||
if tractor_beam.timer.finished() {
|
||||
if tractor_beam.timer.is_finished() {
|
||||
println!("Boss {:?} tractor beam expired", boss_entity);
|
||||
commands.entity(boss_entity).remove::<TractorBeam>();
|
||||
|
||||
// Clean up the beam visual
|
||||
commands.entity(boss_entity).despawn_descendants();
|
||||
if let Ok(children) = children_query.get(boss_entity) {
|
||||
for child in children.iter() {
|
||||
commands.entity(child).despawn();
|
||||
}
|
||||
}
|
||||
|
||||
// Boss returns to formation after failed capture
|
||||
if let Ok(enemy_state) = enemy_query.get(boss_entity) {
|
||||
if let EnemyState::Attacking(_) = enemy_state {
|
||||
commands
|
||||
.entity(boss_entity)
|
||||
.insert(EnemyState::ReturningWithCaptive);
|
||||
}
|
||||
if let Ok(EnemyState::Attacking(_)) = enemy_query.get(boss_entity) {
|
||||
commands
|
||||
.entity(boss_entity)
|
||||
.insert(EnemyState::ReturningWithCaptive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -666,33 +659,20 @@ pub fn update_tractor_beam_visual(
|
|||
) {
|
||||
for (boss_entity, boss_transform, _tractor_beam) in boss_query.iter() {
|
||||
let current_beam_height = calculate_beam_height(boss_transform.translation.y);
|
||||
let time_secs = time.elapsed_seconds();
|
||||
let time_secs = time.elapsed_secs();
|
||||
|
||||
if let Ok(child_entities) = children.get(boss_entity) {
|
||||
for child in child_entities {
|
||||
if let Ok((mut sprite, mut transform)) = sprite_query.get_mut(*child) {
|
||||
// Update color pulse
|
||||
if let Color::Rgba {
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
mut alpha,
|
||||
} = sprite.color
|
||||
{
|
||||
let base_alpha = alpha;
|
||||
alpha = beam_pulse_alpha(
|
||||
base_alpha,
|
||||
time_secs,
|
||||
BEAM_PULSE_FREQ,
|
||||
BEAM_PULSE_AMPLITUDE,
|
||||
);
|
||||
sprite.color = Color::Rgba {
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
alpha,
|
||||
};
|
||||
}
|
||||
let base_alpha = sprite.color.alpha();
|
||||
let new_alpha = beam_pulse_alpha(
|
||||
base_alpha,
|
||||
time_secs,
|
||||
BEAM_PULSE_FREQ,
|
||||
BEAM_PULSE_AMPLITUDE,
|
||||
);
|
||||
sprite.color.set_alpha(new_alpha);
|
||||
|
||||
// Update size and position
|
||||
let current_width = sprite.custom_size.map(|s| s.x).unwrap_or(0.0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue