feat: Add enemy type differentiation and adjust movement logic for Grunt and Boss enemies
This commit is contained in:
parent
e8dceaca27
commit
ef055fe3c5
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/.direnv
|
/.direnv
|
||||||
/logs
|
/logs
|
||||||
.idea/workspace.xml
|
/.idea
|
||||||
|
|
|
@ -69,13 +69,13 @@ nix develop --command bash -c "cargo build"
|
||||||
* ~~Make enemies fire bullets (downwards or towards the player) during their dives.~~ **(DONE - Downward)**
|
* ~~Make enemies fire bullets (downwards or towards the player) during their dives.~~ **(DONE - Downward)**
|
||||||
* After an attack dive, enemies could return to their formation position or fly off-screen. **(Fly off-screen implemented)**
|
* After an attack dive, enemies could return to their formation position or fly off-screen. **(Fly off-screen implemented)**
|
||||||
* **Enemy Variety:**
|
* **Enemy Variety:**
|
||||||
* Introduce different types of enemies (e.g., using different components or an enum).
|
* ~~Introduce different types of enemies (e.g., using different components or an enum).~~ **(DONE - Added EnemyType enum and field)**
|
||||||
* Assign different behaviors, point values, and maybe sprites to each type.
|
* ~~Assign different behaviors, point values, and maybe sprites to each type.~~ **(DONE - Behaviors, points & color based on type)**
|
||||||
|
|
||||||
**3. Advanced Galaga Mechanics:**
|
**3. Advanced Galaga Mechanics:**
|
||||||
|
|
||||||
* **Boss Galaga & Capture Beam:**
|
* **Boss Galaga & Capture Beam:**
|
||||||
* Create a "Boss" enemy type.
|
* ~~Create a "Boss" enemy type.~~ **(DONE - Added to Enum, handled in matches)**
|
||||||
* Implement the tractor beam attack (visual effect, player capture logic).
|
* Implement the tractor beam attack (visual effect, player capture logic).
|
||||||
* Player needs a `Captured` state.
|
* Player needs a `Captured` state.
|
||||||
* Logic for the Boss to return captured ships to the formation.
|
* Logic for the Boss to return captured ships to the formation.
|
||||||
|
|
122
src/main.rs
122
src/main.rs
|
@ -48,6 +48,7 @@ struct Bullet;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Enemy {
|
struct Enemy {
|
||||||
|
enemy_type: EnemyType, // Add type field
|
||||||
shoot_cooldown: Timer,
|
shoot_cooldown: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +71,12 @@ enum EnemyState {
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct EnemyBullet;
|
struct EnemyBullet;
|
||||||
|
#[derive(Component, Clone, Copy, PartialEq, Eq, Debug)] // Added derive for common traits
|
||||||
|
pub enum EnemyType {
|
||||||
|
Grunt,
|
||||||
|
Boss, // Added Boss type
|
||||||
|
}
|
||||||
|
|
||||||
// --- Resources ---
|
// --- Resources ---
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct EnemySpawnTimer {
|
struct EnemySpawnTimer {
|
||||||
|
@ -625,10 +632,19 @@ fn spawn_enemies(
|
||||||
let spawn_x = (fastrand::f32() - 0.5) * (WINDOW_WIDTH - ENEMY_SIZE.x);
|
let spawn_x = (fastrand::f32() - 0.5) * (WINDOW_WIDTH - ENEMY_SIZE.x);
|
||||||
let spawn_y = WINDOW_HEIGHT / 2.0 - ENEMY_SIZE.y / 2.0;
|
let spawn_y = WINDOW_HEIGHT / 2.0 - ENEMY_SIZE.y / 2.0;
|
||||||
|
|
||||||
|
// Determine enemy type (currently always Grunt)
|
||||||
|
let enemy_type = EnemyType::Grunt;
|
||||||
|
|
||||||
|
// Determine sprite color based on type
|
||||||
|
let sprite_color = match enemy_type {
|
||||||
|
EnemyType::Grunt => Color::rgb(1.0, 0.2, 0.2), // Red for Grunts
|
||||||
|
EnemyType::Boss => Color::rgb(0.8, 0.2, 1.0), // Purple for Bosses
|
||||||
|
};
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
SpriteBundle {
|
SpriteBundle {
|
||||||
sprite: Sprite {
|
sprite: Sprite {
|
||||||
color: Color::rgb(1.0, 0.2, 0.2),
|
color: sprite_color, // Use determined color
|
||||||
custom_size: Some(ENEMY_SIZE),
|
custom_size: Some(ENEMY_SIZE),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
@ -636,6 +652,7 @@ fn spawn_enemies(
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Enemy {
|
Enemy {
|
||||||
|
enemy_type, // Use the defined type
|
||||||
// Initialize cooldown, maybe slightly randomized later
|
// Initialize cooldown, maybe slightly randomized later
|
||||||
shoot_cooldown: Timer::new(
|
shoot_cooldown: Timer::new(
|
||||||
Duration::from_secs_f32(ENEMY_SHOOT_INTERVAL),
|
Duration::from_secs_f32(ENEMY_SHOOT_INTERVAL),
|
||||||
|
@ -672,8 +689,8 @@ fn move_enemies(
|
||||||
(With<Enemy>, With<FormationTarget>),
|
(With<Enemy>, With<FormationTarget>),
|
||||||
>,
|
>,
|
||||||
mut attacking_query: Query<
|
mut attacking_query: Query<
|
||||||
(Entity, &mut Transform, &EnemyState), // Add &EnemyState here
|
(Entity, &mut Transform, &EnemyState, &Enemy), // Add &Enemy here
|
||||||
(With<Enemy>, Without<FormationTarget>), // Keep With<EnemyState> filter if desired, but we check explicitly below
|
(With<Enemy>, Without<FormationTarget>),
|
||||||
>, // Query potential attackers
|
>, // Query potential attackers
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
@ -711,56 +728,88 @@ fn move_enemies(
|
||||||
|
|
||||||
// --- Handle Attacking Enemies ---
|
// --- Handle Attacking Enemies ---
|
||||||
let attack_speed = current_speed * 1.5; // Make them dive faster
|
let attack_speed = current_speed * 1.5; // Make them dive faster
|
||||||
for (entity, mut transform, state) in attacking_query.iter_mut() { // Get state from query
|
for (entity, mut transform, state, enemy) in attacking_query.iter_mut() {
|
||||||
|
// Get state and enemy
|
||||||
// *** Explicitly check if the enemy is actually in the Attacking state ***
|
// *** Explicitly check if the enemy is actually in the Attacking state ***
|
||||||
if *state == EnemyState::Attacking {
|
if *state == EnemyState::Attacking {
|
||||||
// --- Swooping Dive Logic ---
|
// --- Apply movement based on EnemyType ---
|
||||||
let delta_seconds = time.delta_seconds();
|
match enemy.enemy_type {
|
||||||
let vertical_movement = attack_speed * delta_seconds;
|
EnemyType::Grunt => {
|
||||||
|
// Grunt: Swooping Dive Logic
|
||||||
|
let delta_seconds = time.delta_seconds();
|
||||||
|
let vertical_movement = attack_speed * delta_seconds;
|
||||||
|
|
||||||
// Horizontal movement: Move towards the center (x=0)
|
// Horizontal movement: Move towards the center (x=0)
|
||||||
let horizontal_speed_factor = 0.5; // Adjust this to control the swoop intensity
|
let horizontal_speed_factor = 0.5; // Adjust this to control the swoop intensity
|
||||||
let horizontal_movement = if transform.translation.x < 0.0 {
|
let horizontal_movement = if transform.translation.x < 0.0 {
|
||||||
attack_speed * horizontal_speed_factor * delta_seconds
|
attack_speed * horizontal_speed_factor * delta_seconds
|
||||||
} else if transform.translation.x > 0.0 {
|
} else if transform.translation.x > 0.0 {
|
||||||
-attack_speed * horizontal_speed_factor * delta_seconds
|
-attack_speed * horizontal_speed_factor * delta_seconds
|
||||||
} else {
|
} else {
|
||||||
0.0 // No horizontal movement if exactly at center
|
0.0 // No horizontal movement if exactly at center
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply movement
|
// Apply movement
|
||||||
transform.translation.y -= vertical_movement;
|
transform.translation.y -= vertical_movement;
|
||||||
transform.translation.x += horizontal_movement;
|
transform.translation.x += horizontal_movement;
|
||||||
|
|
||||||
// Ensure enemy doesn't overshoot the center horizontally if moving towards it
|
// Ensure enemy doesn't overshoot the center horizontally if moving towards it
|
||||||
if (transform.translation.x - horizontal_movement < 0.0 && transform.translation.x > 0.0)
|
if (transform.translation.x - horizontal_movement < 0.0
|
||||||
|| (transform.translation.x - horizontal_movement > 0.0 && transform.translation.x < 0.0)
|
&& transform.translation.x > 0.0)
|
||||||
{
|
|| (transform.translation.x - horizontal_movement > 0.0
|
||||||
transform.translation.x = 0.0;
|
&& transform.translation.x < 0.0)
|
||||||
}
|
{
|
||||||
}
|
transform.translation.x = 0.0;
|
||||||
// Enemies that are InFormation but Without<FormationTarget> will now be ignored by this movement logic.
|
}
|
||||||
|
} // Add cases for other enemy types later
|
||||||
|
EnemyType::Boss => {
|
||||||
|
// Boss: Same Swooping Dive Logic for now
|
||||||
|
let delta_seconds = time.delta_seconds();
|
||||||
|
let vertical_movement = attack_speed * delta_seconds;
|
||||||
|
let horizontal_speed_factor = 0.5;
|
||||||
|
let horizontal_movement = if transform.translation.x < 0.0 {
|
||||||
|
attack_speed * horizontal_speed_factor * delta_seconds
|
||||||
|
} else if transform.translation.x > 0.0 {
|
||||||
|
-attack_speed * horizontal_speed_factor * delta_seconds
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
transform.translation.y -= vertical_movement;
|
||||||
|
transform.translation.x += horizontal_movement;
|
||||||
|
if (transform.translation.x - horizontal_movement < 0.0
|
||||||
|
&& transform.translation.x > 0.0)
|
||||||
|
|| (transform.translation.x - horizontal_movement > 0.0
|
||||||
|
&& transform.translation.x < 0.0)
|
||||||
|
{
|
||||||
|
transform.translation.x = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // Closes match enemy.enemy_type
|
||||||
|
} // Closes if *state == EnemyState::Attacking
|
||||||
|
|
||||||
// Despawn if off screen
|
// Enemies that are InFormation but Without<FormationTarget> will now be ignored by the movement logic above.
|
||||||
|
|
||||||
|
// Despawn if off screen (This should be inside the loop)
|
||||||
if transform.translation.y < -WINDOW_HEIGHT / 2.0 - ENEMY_SIZE.y {
|
if transform.translation.y < -WINDOW_HEIGHT / 2.0 - ENEMY_SIZE.y {
|
||||||
println!(
|
println!(
|
||||||
"Despawning attacking enemy {:?} that went off screen.",
|
"Despawning enemy {:?} that went off screen.", // Generic message as it could be InFormation or Attacking
|
||||||
entity
|
entity
|
||||||
);
|
);
|
||||||
commands.entity(entity).despawn();
|
commands.entity(entity).despawn();
|
||||||
// TODO: Later, attacking enemies might return to formation or loop
|
// TODO: Later, attacking enemies might return to formation or loop
|
||||||
}
|
}
|
||||||
}
|
} // Closes for loop
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_bullet_collisions(
|
fn check_bullet_collisions(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
bullet_query: Query<(Entity, &Transform), With<Bullet>>,
|
bullet_query: Query<(Entity, &Transform), With<Bullet>>,
|
||||||
enemy_query: Query<(Entity, &Transform), With<Enemy>>,
|
enemy_query: Query<(Entity, &Transform, &Enemy), With<Enemy>>, // Fetch Enemy component too
|
||||||
mut score: ResMut<Score>, // Add Score resource
|
mut score: ResMut<Score>, // Add Score resource
|
||||||
) {
|
) {
|
||||||
for (bullet_entity, bullet_transform) in bullet_query.iter() {
|
for (bullet_entity, bullet_transform) in bullet_query.iter() {
|
||||||
for (enemy_entity, enemy_transform) in enemy_query.iter() {
|
for (enemy_entity, enemy_transform, enemy) in enemy_query.iter() {
|
||||||
|
// Get Enemy component
|
||||||
let distance = bullet_transform
|
let distance = bullet_transform
|
||||||
.translation
|
.translation
|
||||||
.distance(enemy_transform.translation);
|
.distance(enemy_transform.translation);
|
||||||
|
@ -768,7 +817,12 @@ fn check_bullet_collisions(
|
||||||
if distance < BULLET_ENEMY_COLLISION_THRESHOLD {
|
if distance < BULLET_ENEMY_COLLISION_THRESHOLD {
|
||||||
commands.entity(bullet_entity).despawn();
|
commands.entity(bullet_entity).despawn();
|
||||||
commands.entity(enemy_entity).despawn();
|
commands.entity(enemy_entity).despawn();
|
||||||
score.value += 100; // Increment score
|
// Increment score based on enemy type
|
||||||
|
let points = match enemy.enemy_type {
|
||||||
|
EnemyType::Grunt => 100,
|
||||||
|
EnemyType::Boss => 100, // Same points as Grunt for now
|
||||||
|
};
|
||||||
|
score.value += points;
|
||||||
println!("Enemy hit! Score: {}", score.value); // Log score update
|
println!("Enemy hit! Score: {}", score.value); // Log score update
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue