feat: initial commit

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
This commit is contained in:
Harald Hoyer 2025-03-29 07:40:17 +01:00
commit 0a5382187e
10 changed files with 4877 additions and 0 deletions

212
src/main.rs Normal file
View file

@ -0,0 +1,212 @@
use bevy::prelude::*;
use std::time::Duration;
const WINDOW_WIDTH: f32 = 600.0;
const WINDOW_HEIGHT: f32 = 800.0;
const PLAYER_SPEED: f32 = 300.0;
const BULLET_SPEED: f32 = 500.0;
const ENEMY_SPEED: f32 = 100.0;
#[derive(Component)]
struct Player {
speed: f32,
shoot_cooldown: Timer,
}
#[derive(Component)]
struct Bullet;
#[derive(Component)]
struct Enemy;
#[derive(Resource)]
struct EnemySpawnTimer {
timer: Timer,
}
fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Galaga".into(),
resolution: (WINDOW_WIDTH, WINDOW_HEIGHT).into(),
..default()
}),
..default()
}))
.add_systems(Startup, setup)
.add_systems(Update, (
move_player,
player_shoot,
move_bullets,
move_enemies,
check_bullet_collisions,
spawn_enemies,
))
.insert_resource(EnemySpawnTimer {
timer: Timer::new(Duration::from_secs_f32(0.5), TimerMode::Repeating),
})
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
// Spawn player
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.0, 0.5, 1.0),
custom_size: Some(Vec2::new(30.0, 30.0)),
..default()
},
transform: Transform::from_translation(Vec3::new(0.0, -300.0, 0.0)),
..default()
},
Player {
speed: PLAYER_SPEED,
shoot_cooldown: Timer::new(Duration::from_secs_f32(0.5), TimerMode::Once),
},
));
}
fn move_player(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut query: Query<(&mut Transform, &Player)>,
time: Res<Time>,
) {
if let Ok((mut transform, player)) = query.get_single_mut() {
let mut direction = 0.0;
if keyboard_input.pressed(KeyCode::KeyA) || keyboard_input.pressed(KeyCode::ArrowLeft) {
direction -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyD) || keyboard_input.pressed(KeyCode::ArrowRight) {
direction += 1.0;
}
transform.translation.x += direction * player.speed * time.delta_seconds();
transform.translation.x = transform.translation.x.clamp(-WINDOW_WIDTH / 2.0 + 15.0, WINDOW_WIDTH / 2.0 - 15.0);
}
}
fn player_shoot(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut query: Query<(&Transform, &mut Player)>,
mut commands: Commands,
time: Res<Time>,
) {
if let Ok((transform, mut player)) = query.get_single_mut() {
player.shoot_cooldown.tick(time.delta());
if (keyboard_input.just_pressed(KeyCode::Space) || keyboard_input.just_pressed(KeyCode::ArrowUp))
&& player.shoot_cooldown.finished() {
// Reset cooldown
player.shoot_cooldown.reset();
// Spawn bullet
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: Color::rgb(1.0, 1.0, 1.0),
custom_size: Some(Vec2::new(5.0, 15.0)),
..default()
},
transform: Transform::from_translation(Vec3::new(
transform.translation.x,
transform.translation.y + 20.0,
0.0
)),
..default()
},
Bullet,
));
}
}
}
fn move_bullets(
mut query: Query<(Entity, &mut Transform), With<Bullet>>,
time: Res<Time>,
mut commands: Commands,
) {
for (entity, mut transform) in query.iter_mut() {
transform.translation.y += BULLET_SPEED * time.delta_seconds();
// Despawn bullets that go out of screen
if transform.translation.y > WINDOW_HEIGHT / 2.0 {
commands.entity(entity).despawn();
}
}
}
fn spawn_enemies(
mut commands: Commands,
time: Res<Time>,
mut timer: ResMut<EnemySpawnTimer>,
enemy_query: Query<&Enemy>,
) {
timer.timer.tick(time.delta());
// Only spawn if timer finished and we don't have too many enemies
if timer.timer.just_finished() {
let x_pos = (fastrand::f32() - 0.5) * (WINDOW_WIDTH - 40.0);
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: Color::rgb(1.0, 0.2, 0.2),
custom_size: Some(Vec2::new(40.0, 40.0)),
..default()
},
transform: Transform::from_translation(Vec3::new(
x_pos,
WINDOW_HEIGHT / 2.0 - 20.0,
0.0
)),
..default()
},
Enemy,
));
}
}
fn move_enemies(
mut query: Query<(Entity, &mut Transform), With<Enemy>>,
time: Res<Time>,
mut commands: Commands,
) {
for (entity, mut transform) in query.iter_mut() {
transform.translation.y -= ENEMY_SPEED * time.delta_seconds();
// Despawn enemies that go out of screen
if transform.translation.y < -WINDOW_HEIGHT / 2.0 {
commands.entity(entity).despawn();
}
}
}
fn check_bullet_collisions(
mut commands: Commands,
bullet_query: Query<(Entity, &Transform), With<Bullet>>,
enemy_query: Query<(Entity, &Transform), With<Enemy>>,
) {
for (bullet_entity, bullet_transform) in bullet_query.iter() {
for (enemy_entity, enemy_transform) in enemy_query.iter() {
let bullet_pos = bullet_transform.translation;
let enemy_pos = enemy_transform.translation;
// Simple collision detection using distance
let distance = bullet_pos.distance(enemy_pos);
if distance < 25.0 { // Approximate collision radius
// Despawn both bullet and enemy on collision
commands.entity(bullet_entity).despawn();
commands.entity(enemy_entity).despawn();
break;
}
}
}
}