feat: initial commit
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
This commit is contained in:
commit
0a5382187e
10 changed files with 4877 additions and 0 deletions
212
src/main.rs
Normal file
212
src/main.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue