Compare commits

...

7 commits

11 changed files with 179 additions and 25 deletions

View file

@ -1,3 +1,7 @@
# v0.11.1
- Fixed radio stations
# v0.11.0
- Upgrade to Bevy 0.14.2, Rust 1.81

BIN
assets/textures/exhaust.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -46,6 +46,8 @@ We are not quite there yet, but this is what I'm aiming for:
- No anxiety-causing features
- No DRM
- No paid features
- All source code and assets are ["free" as defined by the FSF](https://www.gnu.org/licenses/license-list.html)
- Obviously, space as a whole is not exactly wholesome. Space is a death trap. This should still be felt by the player, it's a space simulation after all.
- Non-addictiveness
- No features that manipulate the player into neglecting their life responsibilities in favor of the game
- No artificially long game progression

View file

@ -599,8 +599,9 @@ fn handle_wants_maxvelocity(
}
fn handle_wants_lookat(
mut query: Query<(&Position, &mut Rotation, &Transform, &WantsToLookAt)>,
mut query: Query<(&Position, &mut Rotation, &Transform, &WantsToLookAt), Without<Camera>>,
q_playercam: Query<&Position, With<PlayerCamera>>,
q_cam: Query<&Transform, With<Camera>>,
id2pos: Res<game::Id2Pos>,
) {
let player_pos = if let Ok(player_pos) = q_playercam.get_single() {
@ -608,17 +609,22 @@ fn handle_wants_lookat(
} else {
return;
};
let cam_pos = if let Ok(cam_trans) = q_cam.get_single() {
cam_trans.translation.as_dvec3() + player_pos.0
} else {
return;
};
// TODO: use ExternalTorque rather than hard-resetting the rotation
for (pos, mut rot, trans, target_id) in &mut query {
let target_pos = if target_id.0 == cmd::ID_SPECIAL_PLAYERCAM {
player_pos
let target_pos: DVec3 = if target_id.0 == cmd::ID_SPECIAL_PLAYERCAM {
cam_pos
} else if let Some(target_pos) = id2pos.0.get(&target_id.0) {
target_pos
*target_pos
} else {
continue;
};
rot.0 = look_at_quat(**pos, *target_pos, trans.up().as_dvec3());
rot.0 = look_at_quat(**pos, target_pos, trans.up().as_dvec3());
}
}

View file

@ -430,6 +430,7 @@ pub fn apply_input_to_player(
(&Position, &LinearVelocity),
(
Without<hud::IsTargeted>,
Without<visual::IsEffect>,
Without<actor::PlayerCamera>,
Without<actor::Player>,
Without<actor::PlayerDrivesThis>,
@ -447,6 +448,7 @@ pub fn apply_input_to_player(
),
(With<actor::PlayerCamera>, Without<Camera>),
>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
) {
if settings.map_active || !settings.in_control() {
return;
@ -564,6 +566,16 @@ pub fn apply_input_to_player(
engine.current_warmup =
(engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
play_thruster_sound = true;
// Visual effect
if acceleration_total.length_squared() > 1e-4 {
let thruster_direction = acceleration_total.normalize();
let thruster_v = v.0 - 10.0 * thruster_direction;
ew_effect.send(visual::SpawnEffectEvent {
duration: 2.0,
class: visual::Effects::ThrusterParticle(pos.clone(), LinearVelocity::from(thruster_v)),
});
}
} else {
engine.current_warmup =
(engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);

View file

@ -110,6 +110,10 @@ pub fn in_shadow(
pub fn look_at_quat(from: DVec3, to: DVec3, up: DVec3) -> DQuat {
let direction = (to - from).normalize();
let direction_len = direction.length_squared();
if direction_len < 1e-4 || direction_len.is_nan() {
return DQuat::IDENTITY;
}
let right = up.cross(direction).normalize();
let corrected_up = direction.cross(right);

View file

@ -12,7 +12,6 @@
use crate::prelude::*;
use bevy::color::palettes::css;
use bevy::pbr::ExtendedMaterial;
use bevy::prelude::*;
use bevy::scene::SceneInstance;
use bevy::window::{PrimaryWindow, Window, WindowMode};
@ -471,22 +470,23 @@ fn update_id2v(mut id2v: ResMut<Id2V>, q_id: Query<(&LinearVelocity, &actor::Ide
fn debug(
settings: Res<var::Settings>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut extended_materials: ResMut<
Assets<ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>,
>,
// mut commands: Commands,
// mut extended_materials: ResMut<
// Assets<bevy::pbr::ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>,
// >,
mut achievement_tracker: ResMut<var::AchievementTracker>,
vars: Res<var::GameVars>,
materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>,
// materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>,
) {
if settings.dev_mode && keyboard_input.just_pressed(KeyCode::KeyP) {
for (entity, _name, mesh) in &materials {
dbg!(mesh);
let mut entity = commands.entity(entity);
entity.remove::<Handle<StandardMaterial>>();
let material = extended_materials.add(load::AsteroidSurface::material());
entity.insert(material);
}
// for (entity, _name, mesh) in &materials {
// dbg!(mesh);
// let mut entity = commands.entity(entity);
// entity.remove::<Handle<StandardMaterial>>();
// let material = extended_materials.add(load::AsteroidSurface::material());
// entity.insert(material);
// }
}
if settings.dev_mode && keyboard_input.just_pressed(KeyCode::KeyN) {
achievement_tracker.achieve_all();

View file

@ -475,7 +475,7 @@ pub fn update_menu(
} else {
&settings.radio_modes[0]
};
text.sections[i].value = format!("Radio: {station}\n");
text.sections[i].value = format!("Speakers: {station}\n");
}
MenuAction::ToggleAR => {
let onoff = bool2string(settings.hud_active);

View file

@ -177,6 +177,7 @@ impl Default for Settings {
radio_modes: vec![
// see also: settings.is_radio_playing()
"Off".to_string(),
"Space Wave Radio".to_string(),
"Amplify outside recordings".to_string(),
],
volume_sfx: 100,

View file

@ -12,6 +12,7 @@
use crate::prelude::*;
use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*;
use std::time::Duration;
pub struct VisualPlugin;
@ -26,6 +27,8 @@ impl Plugin for VisualPlugin {
app.add_systems(Update, spawn_effects);
app.add_systems(Update, update_fadein);
app.add_systems(Update, update_fadeout);
app.add_systems(Update, update_fade_material);
app.add_systems(Update, update_grow);
app.add_systems(Update, play_animations);
// Blackout disabled for now
//app.add_systems(Update, update_blackout);
@ -37,15 +40,32 @@ impl Plugin for VisualPlugin {
pub enum Effects {
FadeIn(Color),
FadeOut(Color),
ThrusterParticle(Position, LinearVelocity),
}
// Blackout disabled for now
//#[derive(Component)] pub struct BlackOutOverlay;
#[derive(Component)]
pub struct FadeIn;
pub struct IsEffect;
#[derive(Component)]
pub struct FadeOut;
pub struct FadeInSprite;
#[derive(Component)]
pub struct FadeOutSprite;
#[derive(Component)]
pub struct FadeMaterial {
pub start_time: f64,
pub duration: f64,
pub value_start: f32,
pub value_end: f32,
}
#[derive(Component)]
pub struct Grow3DObject {
pub start_time: f64,
pub duration: f64,
pub scale_start: f32,
pub scale_end: f32,
}
#[derive(Component)]
pub struct Effect {
pub class: Effects,
@ -109,6 +129,9 @@ pub fn setup(
pub fn spawn_effects(
mut commands: Commands,
mut er_effect: EventReader<SpawnEffectEvent>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
asset_server: Res<AssetServer>,
time: Res<Time>,
) {
let now = time.elapsed_seconds_f64();
@ -116,12 +139,13 @@ pub fn spawn_effects(
match effect.class {
Effects::FadeIn(color) => {
commands.spawn((
IsEffect,
Effect {
class: effect.class.clone(),
duration: effect.duration,
start_time: now,
},
FadeIn,
FadeInSprite,
NodeBundle {
style: style_fullscreen(),
background_color: color.into(),
@ -131,12 +155,13 @@ pub fn spawn_effects(
}
Effects::FadeOut(color) => {
commands.spawn((
IsEffect,
Effect {
class: effect.class.clone(),
duration: effect.duration,
start_time: now,
},
FadeOut,
FadeOutSprite,
NodeBundle {
style: style_fullscreen(),
background_color: color.with_alpha(0.0).into(),
@ -144,13 +169,49 @@ pub fn spawn_effects(
},
));
}
Effects::ThrusterParticle(pos, v) => {
let texture = asset_server.load("textures/exhaust.png");
commands.spawn((
IsEffect,
RigidBody::Kinematic,
bevy::pbr::NotShadowCaster,
pos,
v,
Grow3DObject {
start_time: now,
duration: effect.duration,
scale_start: 1.0,
scale_end: 20.0,
},
FadeMaterial {
start_time: now,
duration: effect.duration,
value_start: 0.1,
value_end: 0.0,
},
world::DespawnAt(now + effect.duration),
world::DespawnOnPlayerDeath,
actor::WantsToLookAt(cmd::ID_SPECIAL_PLAYERCAM.into()),
PbrBundle {
mesh: meshes.add(Mesh::from(Rectangle::new(0.2, 0.2))),
material: materials.add(StandardMaterial {
base_color_texture: Some(texture),
perceptual_roughness: 1.0,
metallic: 0.5,
alpha_mode: AlphaMode::Blend,
..Default::default()
}),
..default()
},
));
}
}
}
}
pub fn update_fadein(
mut commands: Commands,
mut q_effect: Query<(Entity, &Effect, &mut BackgroundColor), With<FadeIn>>,
mut q_effect: Query<(Entity, &Effect, &mut BackgroundColor), With<FadeInSprite>>,
time: Res<Time>,
) {
for (entity, effect, mut bgcolor) in &mut q_effect {
@ -166,7 +227,7 @@ pub fn update_fadein(
pub fn update_fadeout(
mut commands: Commands,
mut q_effect: Query<(Entity, &Effect, &mut BackgroundColor), With<FadeOut>>,
mut q_effect: Query<(Entity, &Effect, &mut BackgroundColor), With<FadeOutSprite>>,
time: Res<Time>,
) {
for (entity, effect, mut bgcolor) in &mut q_effect {
@ -180,6 +241,54 @@ pub fn update_fadeout(
}
}
pub fn update_fade_material(
mut commands: Commands,
mut q_object: Query<(Entity, &Handle<StandardMaterial>, &FadeMaterial)>,
time: Res<Time>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let now = time.elapsed_seconds_f64();
for (entity, material_handle, data) in q_object.iter_mut() {
let end_time = data.start_time + data.duration;
let material = materials.get_mut(material_handle).unwrap();
if now > end_time {
material.base_color.set_alpha(data.value_end);
commands.entity(entity).remove::<FadeMaterial>();
}
else if now < data.start_time {
material.base_color.set_alpha(data.value_start);
}
else {
let progress = ((now - data.start_time) / data.duration) as f32;
let value = data.value_start + progress * (data.value_end - data.value_start);
material.base_color.set_alpha(value);
}
}
}
pub fn update_grow(
mut commands: Commands,
mut q_object: Query<(Entity, &mut Transform, &Grow3DObject)>,
time: Res<Time>,
) {
let now = time.elapsed_seconds_f64();
for (entity, mut trans, data) in q_object.iter_mut() {
let end_time = data.start_time + data.duration;
if now > end_time {
trans.scale = Vec3::splat(data.scale_end);
commands.entity(entity).remove::<Grow3DObject>();
}
else if now < data.start_time {
trans.scale = Vec3::splat(data.scale_start);
}
else {
let progress = ((now - data.start_time) / data.duration) as f32;
let scale = data.scale_start + progress * (data.scale_end - data.scale_start);
trans.scale = Vec3::splat(scale);
}
}
}
fn play_animations(
mut commands: Commands,
mut players: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,

View file

@ -38,6 +38,7 @@ impl Plugin for WorldPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, handle_respawn.run_if(on_event::<RespawnEvent>()));
app.add_systems(Update, handle_despawn_at.run_if(any_with_component::<DespawnAt>));
app.add_plugins(PhysicsPlugins::default());
//app.add_plugins(PhysicsDebugPlugin::default());
app.insert_resource(Gravity(DVec3::splat(0.0)));
@ -65,6 +66,8 @@ struct Asteroid;
pub struct Star;
#[derive(Component)]
pub struct DespawnOnPlayerDeath;
#[derive(Component)]
pub struct DespawnAt(pub f64);
#[derive(Event)]
pub struct RespawnEvent;
@ -360,6 +363,19 @@ fn handle_despawn_asteroids(
}
}
fn handle_despawn_at(
mut commands: Commands,
q_entity: Query<(Entity, &DespawnAt)>,
time: Res<Time>,
) {
let now = time.elapsed_seconds_f64();
for (entity, despawn_at) in &q_entity {
if despawn_at.0 < now {
commands.entity(entity).despawn();
}
}
}
pub fn handle_respawn(
ew_spawn: EventWriter<cmd::SpawnEvent>,
mut achievement_tracker: ResMut<var::AchievementTracker>,