Compare commits

..

2 commits

Author SHA1 Message Date
f02158833c Asteroids 2025-11-16 13:36:11 +05:00
fba4745162 Faction check is now optional 2025-11-15 23:06:41 +05:00
10 changed files with 155 additions and 5 deletions

1
Cargo.lock generated
View file

@ -4092,6 +4092,7 @@ name = "spacorium"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bevy", "bevy",
"rand",
] ]
[[package]] [[package]]

View file

@ -5,3 +5,4 @@ edition = "2024"
[dependencies] [dependencies]
bevy = "0.17.2" bevy = "0.17.2"
rand = "0.9.2"

BIN
assets/asteroid2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

BIN
assets/enemy_projectile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 653 B

Before After
Before After

96
src/asteroid.rs Normal file
View file

@ -0,0 +1,96 @@
use bevy::prelude::*;
use rand::{prelude::*, rng};
use crate::{
FIRST_CORNER_X, FIRST_CORNER_Y, SECOND_CORNER_X, SECOND_CORNER_Y,
collision::{Collided, Collider},
damagable::Damagable,
projectile::Projectile,
velocity::Velocity,
};
const ASTEROID_BUMP_STRENGTH: f32 = 10.0;
pub struct AsteroidPlugin;
impl Plugin for AsteroidPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_asteroids)
.add_systems(FixedUpdate, rotate_asteroid);
}
}
#[derive(Component)]
pub struct Asteroid(f32);
pub fn setup_asteroids(mut commands: Commands, asset_server: Res<AssetServer>) {
let sprite1: Handle<Image> = asset_server.load("asteroid.png");
let sprite2: Handle<Image> = asset_server.load("asteroid2.png");
let mut random = rng();
for _ in 0..50 {
let position = Vec3::new(
random.random_range(FIRST_CORNER_X..SECOND_CORNER_X),
random.random_range(FIRST_CORNER_Y..SECOND_CORNER_Y),
0.,
);
let sprite = if random.random_bool(0.5) {
sprite1.clone()
} else {
sprite2.clone()
};
let tau = f64::to_radians(360.);
let rotation = random.random_range((-tau)..tau) as f32;
commands
.spawn((
Asteroid(rotation),
Sprite::from(sprite),
Transform::from_translation(position),
Collider::new(8.),
Damagable::new(20),
))
.observe(bump);
}
}
pub fn rotate_asteroid(time: Res<Time>, query: Query<(&mut Transform, &Asteroid)>) {
for (mut transform, asteroid) in query {
transform.rotate_z(asteroid.0 * time.delta_secs());
}
}
pub fn bump(
collision: On<Collided>,
asteroid_query: Query<&Transform, (With<Asteroid>, Without<Projectile>)>,
mut collision_query: Query<
(&mut Transform, Option<&Velocity>),
(Without<Asteroid>, Without<Projectile>),
>,
time: Res<Time>,
) {
let Ok(asteroid_transform) = asteroid_query.get(collision.entity) else {
return;
};
let Ok((mut collided_transform, velocity_option)) = collision_query.get_mut(collision.with)
else {
return;
};
let identity = Velocity::identity();
let velocity = velocity_option.unwrap_or(&identity);
let dir_to_asteroid = asteroid_transform.translation - collided_transform.translation;
let collided_direction = collided_transform.rotation * Vec3::X;
let collided_side = collided_transform.rotation * Vec3::Y;
let angle_to_direction = collided_direction.angle_between(-dir_to_asteroid);
let proporiton = velocity.linear_speed / velocity.max_linear_speed;
collided_transform.rotate_z(
angle_to_direction
* time.delta_secs()
* ASTEROID_BUMP_STRENGTH
* collided_side.dot(-dir_to_asteroid).signum()
* proporiton,
);
}

View file

@ -1,24 +1,47 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy::window::WindowResolution;
use crate::asteroid::AsteroidPlugin;
use crate::collision::CollisionPlugin; use crate::collision::CollisionPlugin;
use crate::damagable::DamagablePlugin; use crate::damagable::DamagablePlugin;
use crate::projectile::ProjectilePlugin; use crate::projectile::ProjectilePlugin;
use crate::ships::ShipsPlugin; use crate::ships::ShipsPlugin;
use crate::velocity::VelocityPlugin; use crate::velocity::VelocityPlugin;
mod asteroid;
mod collision; mod collision;
mod damagable; mod damagable;
mod projectile; mod projectile;
mod ships; mod ships;
mod velocity; mod velocity;
const FIRST_CORNER_X: f32 = -512.;
const FIRST_CORNER_Y: f32 = -512.;
const SECOND_CORNER_X: f32 = 512.;
const SECOND_CORNER_Y: f32 = 512.;
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(
(FIRST_CORNER_X.abs() + SECOND_CORNER_X.abs()) as u32,
(FIRST_CORNER_Y.abs() + SECOND_CORNER_Y.abs()) as u32,
)
.with_scale_factor_override(1.0),
..default()
}),
..default()
})
.set(ImagePlugin::default_nearest()),
)
.add_plugins(VelocityPlugin) .add_plugins(VelocityPlugin)
.add_plugins(CollisionPlugin) .add_plugins(CollisionPlugin)
.add_plugins(ShipsPlugin) .add_plugins(ShipsPlugin)
.add_plugins(DamagablePlugin) .add_plugins(DamagablePlugin)
.add_plugins(ProjectilePlugin) .add_plugins(ProjectilePlugin)
.add_plugins(AsteroidPlugin)
.run(); .run();
} }

View file

@ -75,15 +75,17 @@ pub fn observe_collision(
collision: On<Collided>, collision: On<Collided>,
mut commands: Commands, mut commands: Commands,
projectile_query: Query<&Projectile>, projectile_query: Query<&Projectile>,
mut collision_query: Query<(&mut Damagable, &Factions)>, mut damagable_query: Query<(&mut Damagable, Option<&Factions>)>,
) { ) {
let Ok(projectile) = projectile_query.get(collision.entity) else { let Ok(projectile) = projectile_query.get(collision.entity) else {
return; return;
}; };
let Ok((mut collided, faction)) = collision_query.get_mut(collision.with) else { let Ok((mut collided, faction)) = damagable_query.get_mut(collision.with) else {
return; return;
}; };
if projectile.faction.can_damage(faction) == false { if let Some(f) = faction
&& projectile.faction.can_damage(f) == false
{
return; return;
} }

View file

@ -6,6 +6,7 @@ use crate::{
velocity::Velocity, velocity::Velocity,
}; };
use bevy::prelude::*; use bevy::prelude::*;
use rand::random_range;
#[derive(Component)] #[derive(Component)]
#[require(Velocity)] #[require(Velocity)]
@ -112,10 +113,14 @@ pub fn player_shooting_system(
return; return;
} }
let five_degrees = f32::to_radians(5.);
let mut bullet_transform = transform.clone();
bullet_transform.rotate_z(random_range(-five_degrees..five_degrees));
spawn_projectile( spawn_projectile(
&mut commands, &mut commands,
projectile_sprite, projectile_sprite,
transform.clone(), bullet_transform,
gun.damage, gun.damage,
Factions::PlayerFaction, Factions::PlayerFaction,
); );

View file

@ -1,6 +1,8 @@
use bevy::prelude::*; use bevy::prelude::*;
use std::default::Default; use std::default::Default;
use crate::{FIRST_CORNER_X, FIRST_CORNER_Y, SECOND_CORNER_X, SECOND_CORNER_Y};
pub struct VelocityPlugin; pub struct VelocityPlugin;
impl Plugin for VelocityPlugin { impl Plugin for VelocityPlugin {
@ -48,6 +50,14 @@ impl Velocity {
max_rotation_speed: max_rotation_speed, max_rotation_speed: max_rotation_speed,
} }
} }
pub fn identity() -> Velocity {
Velocity {
linear_speed: 1.,
rotation_speed: 1.,
max_linear_speed: 1.0,
max_rotation_speed: 1.0,
}
}
} }
impl Default for Velocity { impl Default for Velocity {
@ -71,5 +81,17 @@ fn movement_system(time: Res<Time>, query: Query<(&Velocity, &mut Transform)>) {
transform.translation += translation_delta; transform.translation += translation_delta;
//TODO: Loop on bounds //TODO: Loop on bounds
if transform.translation.x < FIRST_CORNER_X {
transform.translation.x += SECOND_CORNER_X * 2.;
}
if transform.translation.y < FIRST_CORNER_Y {
transform.translation.y += SECOND_CORNER_Y * 2.;
}
if transform.translation.x > SECOND_CORNER_X {
transform.translation.x += FIRST_CORNER_X * 2.;
}
if transform.translation.y > SECOND_CORNER_Y {
transform.translation.y += FIRST_CORNER_X * 2.;
}
} }
} }