Merge branch 'physics'

This commit is contained in:
yuni 2024-03-30 19:00:09 +01:00
commit d1a8c536eb
13 changed files with 588 additions and 210 deletions

280
Cargo.lock generated
View file

@ -1020,6 +1020,34 @@ dependencies = [
"winit",
]
[[package]]
name = "bevy_xpbd_3d"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0425ea7361b9b27c2a382e0663deb42f41147eee60fb2b3d5fa7e42d363ea848"
dependencies = [
"bevy",
"bevy_math",
"bevy_xpbd_derive",
"derive_more",
"fxhash",
"indexmap",
"itertools",
"nalgebra",
"parry3d",
"parry3d-f64",
]
[[package]]
name = "bevy_xpbd_derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e1ef1d5e328abe1b76df974245f78e17fd17867583883d5e77444c6a8223a64"
dependencies = [
"quote",
"syn 2.0.52",
]
[[package]]
name = "bindgen"
version = "0.69.4"
@ -1358,6 +1386,12 @@ dependencies = [
"const_soft_float",
]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -1459,6 +1493,25 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
@ -1500,8 +1553,10 @@ version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn 1.0.109",
]
@ -1729,6 +1784,15 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "gethostname"
version = "0.4.3"
@ -2134,6 +2198,12 @@ dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libredox"
version = "0.0.2"
@ -2194,6 +2264,16 @@ dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matrixmultiply"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "memchr"
version = "2.7.1"
@ -2272,6 +2352,34 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "nalgebra"
version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea4908d4f23254adda3daa60ffef0f1ac7b8c3e9a864cf3cc154b251908a2ef"
dependencies = [
"approx",
"glam",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "ndk"
version = "0.8.0"
@ -2337,6 +2445,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-complex"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.4.2"
@ -2348,6 +2465,26 @@ dependencies = [
"syn 2.0.52",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.18"
@ -2355,6 +2492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -2498,6 +2636,7 @@ version = "0.3.0"
dependencies = [
"bevy",
"bevy_embedded_assets",
"bevy_xpbd_3d",
"regex",
]
@ -2545,6 +2684,50 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "parry3d"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ccba18a65dba56c08dadfa936e0c9efbc883b3a26dc77d2685f78be10f7667c"
dependencies = [
"approx",
"arrayvec",
"bitflags 1.3.2",
"downcast-rs",
"either",
"nalgebra",
"num-derive",
"num-traits",
"rayon",
"rustc-hash",
"simba",
"slab",
"smallvec",
"spade",
]
[[package]]
name = "parry3d-f64"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d259245abcc09379798e4d373d9019e0df4dc6f7f128ecd68700a5927dc32034"
dependencies = [
"approx",
"arrayvec",
"bitflags 1.3.2",
"downcast-rs",
"either",
"nalgebra",
"num-derive",
"num-traits",
"rayon",
"rustc-hash",
"simba",
"slab",
"smallvec",
"spade",
]
[[package]]
name = "paste"
version = "1.0.14"
@ -2683,6 +2866,32 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "rectangle-pack"
version = "0.4.2"
@ -2757,6 +2966,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "robust"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30"
[[package]]
name = "rodio"
version = "0.17.3"
@ -2785,6 +3000,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.31"
@ -2815,6 +3039,15 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "safe_arch"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354"
dependencies = [
"bytemuck",
]
[[package]]
name = "same-file"
version = "1.0.6"
@ -2830,6 +3063,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
version = "1.0.197"
@ -2876,6 +3115,19 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simba"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
"wide",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
@ -2918,6 +3170,18 @@ dependencies = [
"serde",
]
[[package]]
name = "spade"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61addf9117b11d1f5b4bf6fe94242ba25f59d2d4b2080544b771bd647024fd00"
dependencies = [
"hashbrown",
"num-traits",
"robust",
"smallvec",
]
[[package]]
name = "spirv"
version = "0.3.0+sdk-1.3.268.0"
@ -3157,6 +3421,12 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
@ -3412,6 +3682,16 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wide"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89beec544f246e679fc25490e3f8e08003bc4bf612068f325120dad4cea02c1c"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "widestring"
version = "1.0.2"

View file

@ -11,8 +11,9 @@ rust-version = "1.76.0"
[dependencies]
regex = "1"
bevy_embedded_assets = "0.10.2"
bevy = { version = "0.13.0", default-features = false, features = ["jpeg", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "multi-threaded", "png", "vorbis", "x11", "tonemapping_luts"]}
bevy_xpbd_3d = { version = "0.4", default-features = false, features = ["3d", "f32", "parry-f32", "parallel"] }
bevy_embedded_assets = "0.10.2"
[features]
dev = ["bevy/dynamic_linking"]

View file

@ -24,6 +24,7 @@ Key features:
- t: toggle music (NOTE: currently no music is included in the git repo)
- m: mute sound effects
- q: enter/exit vehicle
- f: toggle 3rd person view
- TAB: toggle augmented reality overlay (HUD, low-light amplifier)
# System Requirements
@ -113,6 +114,7 @@ cargo run --release
- https://pixabay.com/sound-effects/electricity-6353
- https://pixabay.com/sound-effects/ducati-696-monster-33217
- https://pixabay.com/sound-effects/high-energy-humming-195612
- https://pixabay.com/sound-effects/box-crash-106687
- Music: [Dead Space Style Ambient Music](https://pixabay.com/music/ambient-dead-space-style-ambient-music-184793) by [Sharvarian](https://www.fiverr.com/sharvarian)
- Star chart based on the [HYG Stellar database](https://github.com/astronexus/HYG-Database)
- Custom font Yupiter is based on:

Binary file not shown.

BIN
assets/sounds/crash.ogg Normal file

Binary file not shown.

View file

@ -1,4 +1,6 @@
use bevy::prelude::*;
use bevy::transform::TransformSystem;
use bevy_xpbd_3d::prelude::*;
use crate::{nature, settings, actor, audio, hud};
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
@ -11,7 +13,6 @@ impl Plugin for ActorPlugin {
app.register_type::<ChatBranch>();
app.add_systems(FixedUpdate, (
update_physics_lifeforms,
update_physics_actors,
));
app.add_systems(Update, (
handle_new_conversations,
@ -19,7 +20,8 @@ impl Plugin for ActorPlugin {
handle_conversations,
handle_input,
handle_chat_scripts,
handle_vehicle_enter_exit,
handle_vehicle_enter_exit.after(PhysicsSet::Sync).before(TransformSystem::TransformPropagate),
handle_collisions,
));
app.add_event::<StartConversationEvent>();
app.add_event::<SendMessageEvent>();
@ -63,6 +65,7 @@ pub struct Actor {
pub v: Vec3, // velocity
pub angular_momentum: Quat,
pub inside_entity: u32,
pub camdistance: f32,
}
impl Default for Actor {
@ -74,12 +77,14 @@ impl Default for Actor {
v: Vec3::ZERO,
inside_entity: NO_RIDE,
angular_momentum: Quat::from_euler(EulerRot::XYZ, 0.001, 0.01, 0.003),
camdistance: 15.0,
}
}
}
#[derive(Component)] pub struct Player;
#[derive(Component)] pub struct PlayerDrivesThis;
#[derive(Component)] pub struct Player; // Attached to the suit of the player
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view
#[derive(Component)] pub struct PlayerInConversation;
#[derive(Component)] pub struct InConversationWithPlayer;
#[derive(Component)] pub struct ActorEnteringVehicle;
@ -187,19 +192,6 @@ const SUIT_SIMPLE: Suit = Suit {
integrity: 1e5,
};
pub fn update_physics_actors(
time: Res<Time>,
mut q_actors: Query<(&mut Actor, &mut Transform)>,
) {
let d = time.delta_seconds();
for (actor, mut transform) in q_actors.iter_mut() {
transform.rotate(actor.angular_momentum);
// TODO: animate only a step based on time between update:
//transform.rotate(actor.angular_momentum.slerp(Quat::IDENTITY, d)); // not working
transform.translation += d * actor.v;
}
}
pub fn update_physics_lifeforms(
time: Res<Time>,
mut query: Query<(&mut LifeForm, &mut Suit, &Actor)>,
@ -294,6 +286,7 @@ pub fn handle_input(
is_entering: false,
is_player: true,
});
commands.entity(player_entity).insert(RigidBody::Dynamic);
break;
}
}
@ -304,41 +297,43 @@ pub fn handle_input(
pub fn handle_vehicle_enter_exit(
mut commands: Commands,
mut er_vehicle: EventReader<VehicleEnterExitEvent>,
mut q_drivers: Query<(Entity, &mut Actor, &mut Visibility, &mut Transform), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
mut q_vehicles: Query<(Entity, &mut Actor, &mut Visibility, &mut Transform), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut q_drivers: Query<(Entity, &mut Visibility, &mut Transform, &mut LinearVelocity, &mut AngularVelocity), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
mut q_vehicles: Query<(Entity, &mut Visibility, &mut Transform, &LinearVelocity, &AngularVelocity), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
for event in er_vehicle.read() {
for (driver, mut driver_actor, mut driver_vis, mut driver_trans) in q_drivers.iter_mut() {
for (driver, mut driver_vis, mut driver_trans, mut driver_linv, mut driver_angv) in q_drivers.iter_mut() {
if driver == event.driver {
for (vehicle, mut vehicle_actor, mut vehicle_vis, mut vehicle_trans) in q_vehicles.iter_mut() {
for (vehicle, mut vehicle_vis, vehicle_trans, vehicle_linv, vehicle_angv) in q_vehicles.iter_mut() {
if vehicle == event.vehicle {
if event.is_entering {
// Entering Vehicle
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
*driver_vis = Visibility::Hidden;
*driver_trans = vehicle_trans.clone();
commands.entity(driver).remove::<RigidBody>();
*driver_vis = Visibility::Hidden; //seems to have no effect...
if event.is_player {
//player_actor.inside_entity = entity.index();
*vehicle_vis = Visibility::Hidden;
driver_actor.v = vehicle_actor.v;
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
commands.entity(driver).remove::<PlayerCamera>();
commands.entity(vehicle).insert(PlayerCamera);
commands.entity(vehicle).insert(PlayerDrivesThis);
}
}
else {
// Exiting Vehicle
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
*driver_vis = Visibility::Inherited;
*driver_linv = vehicle_linv.clone();
*driver_angv = vehicle_angv.clone();
driver_trans.translation = vehicle_trans.translation + Vec3::new(0.0, 0.0, 10.0);
driver_trans.rotation = vehicle_trans.rotation;
// NOTE: I would rather have the following line here,
// but then, for some reason, changing driver translation
// does not work. For now, you must manually insert the RigidBody
// component from the place that adds this event.
//commands.entity(driver).insert(RigidBody::Dynamic);
if event.is_player {
*vehicle_vis = Visibility::Inherited;
vehicle_actor.v = driver_actor.v;
vehicle_actor.angular_momentum = driver_actor.angular_momentum;
*vehicle_trans = driver_trans.clone();
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
commands.entity(vehicle).remove::<PlayerCamera>();
commands.entity(driver).insert(PlayerCamera);
commands.entity(vehicle).remove::<PlayerDrivesThis>();
}
else {
*driver_trans = vehicle_trans.clone();
*driver_trans = vehicle_trans.clone();
*vehicle_vis = Visibility::Inherited;
}
}
}
@ -566,3 +561,17 @@ pub fn handle_chat_scripts(
}
}
}
fn handle_collisions(
mut collision_event_reader: EventReader<Collision>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_player: Query<Entity, With<PlayerCamera>>,
) {
if let Ok(player) = q_player.get_single() {
for Collision(contacts) in collision_event_reader.read() {
if contacts.entity1 == player || contacts.entity2 == player {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash));
}
}
}
}

View file

@ -13,6 +13,7 @@ const ASSET_ROCKET: &str = "sounds/rocket.ogg";
const ASSET_ION: &str = "sounds/ion.ogg";
//const ASSET_WAKEUP: &str = "sounds/wakeup.ogg";
const ASSET_BIKESTART: &str = "sounds/bikestart.ogg";
const ASSET_CRASH: &str = "sounds/crash.ogg";
pub struct AudioPlugin;
impl Plugin for AudioPlugin {
@ -33,6 +34,7 @@ pub enum Sfx {
Ping,
Connect,
EnterVehicle,
Crash,
None,
}
@ -49,6 +51,7 @@ pub enum Sfx {
#[derive(Resource)] pub struct SoundPing(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundConnect(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundBikeStart(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundCrash(Handle<AudioSource>);
pub fn setup(
mut commands: Commands,
@ -113,6 +116,7 @@ pub fn setup(
commands.insert_resource(SoundPing(asset_server.load(ASSET_PING)));
commands.insert_resource(SoundConnect(asset_server.load(ASSET_CONNECT)));
commands.insert_resource(SoundBikeStart(asset_server.load(ASSET_BIKESTART)));
commands.insert_resource(SoundCrash(asset_server.load(ASSET_CRASH)));
}
pub fn toggle_bgm(
@ -143,6 +147,7 @@ pub fn play_sfx(
sound_ping: Res<SoundPing>,
sound_connect: Res<SoundConnect>,
sound_bikestart: Res<SoundBikeStart>,
sound_crash: Res<SoundCrash>,
) {
if settings.mute_sfx && !events_sfx.is_empty() {
events_sfx.clear();
@ -160,6 +165,7 @@ pub fn play_sfx(
Sfx::Ping => sound_ping.0.clone(),
Sfx::Connect => sound_connect.0.clone(),
Sfx::EnterVehicle => sound_bikestart.0.clone(),
Sfx::Crash => sound_crash.0.clone(),
Sfx::None => sound_ping.0.clone(),
},
settings: PlaybackSettings::DESPAWN,
@ -175,6 +181,7 @@ pub fn str2sfx(sfx_label: &str) -> Sfx {
"ping" => Sfx::Ping,
"connect" => Sfx::Connect,
"entervehicle" => Sfx::EnterVehicle,
"crash" => Sfx::Crash,
_ => Sfx::None,
};
}

View file

@ -1,6 +1,10 @@
use bevy::prelude::*;
use bevy::input::mouse::MouseMotion;
use bevy::window::PrimaryWindow;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::transform::TransformSystem;
use bevy_xpbd_3d::prelude::*;
use std::f32::consts::*;
use crate::{settings, audio, actor};
@ -8,7 +12,14 @@ pub struct CameraControllerPlugin;
impl Plugin for CameraControllerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, run_camera_controller);
app.add_systems(Startup, setup_camera);
app.add_systems(Update, handle_input);
app.add_systems(Update, manage_player_actor.after(handle_input));
app.add_systems(PostUpdate, sync_camera_to_player
.after(PhysicsSet::Sync)
.before(TransformSystem::TransformPropagate));
app.add_systems(Update, update_fov);
app.add_systems(Update, apply_input_to_player);
}
}
@ -17,29 +28,90 @@ impl Plugin for CameraControllerPlugin {
// it because it felt nice.
pub const RADIANS_PER_DOT: f32 = 1.0 / 180.0;
#[derive(Component)]
pub struct CameraController {
pub enabled: bool,
pub initialized: bool,
pub sensitivity: f32,
pub pitch: f32,
pub yaw: f32,
fn setup_camera(
mut commands: Commands,
) {
// Add player
commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: true, // HDR is required for bloom
..default()
},
tonemapping: Tonemapping::TonyMcMapface,
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
BloomSettings {
composite_mode: BloomCompositeMode::EnergyConserving,
..default()
},
));
}
impl Default for CameraController {
fn default() -> Self {
Self {
enabled: true,
initialized: false,
sensitivity: 0.5,
pitch: 1.0, // pitch=0/yaw=0 -> face sun
yaw: 0.3,
pub fn sync_camera_to_player(
settings: Res<settings::Settings>,
mut q_camera: Query<&mut Transform, With<Camera>>,
q_playercam: Query<(&actor::Actor, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
if q_camera.is_empty() || q_playercam.is_empty() {
return;
}
let mut camera_transform = q_camera.get_single_mut().unwrap();
let (actor, player_transform) = q_playercam.get_single().unwrap();
// Rotation
camera_transform.rotation = player_transform.rotation * Quat::from_array([0.0, -1.0, 0.0, 0.0]);
// Translation
if settings.third_person {
camera_transform.translation = player_transform.translation + camera_transform.rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
}
else {
camera_transform.translation = player_transform.translation;
}
}
pub fn update_fov(
q_player: Query<&actor::LifeForm, With<actor::Player>>,
mut q_camera: Query<&mut Projection, With<Camera>>,
) {
if let (Ok(lifeform), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut())
{
let fov = (lifeform.adrenaline.powf(3.0) * 45.0 + 45.0).to_radians();
*projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() });
}
}
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<settings::Settings>,
) {
if keyboard_input.just_pressed(settings.key_camera) {
settings.third_person ^= true;
}
}
fn manage_player_actor(
settings: Res<settings::Settings>,
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>,
mut q_hiddenplayer: Query<&mut Visibility, (With<actor::Player>, Without<actor::PlayerCamera>)>,
) {
for mut vis in &mut q_playercam {
if settings.third_person {
*vis = Visibility::Inherited;
}
else {
*vis = Visibility::Hidden;
}
}
for mut vis in &mut q_hiddenplayer {
*vis = Visibility::Hidden;
}
}
#[allow(clippy::too_many_arguments)]
fn run_camera_controller(
fn apply_input_to_player(
time: Res<Time>,
settings: Res<settings::Settings>,
mut windows: Query<&mut Window, With<PrimaryWindow>>,
@ -48,15 +120,12 @@ fn run_camera_controller(
thruster_sound_controller: Query<&AudioSink, With<audio::ComponentThrusterSound>>,
rocket_sound_controller: Query<&AudioSink, With<audio::ComponentRocketSound>>,
ion_sound_controller: Query<&AudioSink, With<audio::ComponentIonSound>>,
mut q_engine: Query<&mut actor::Engine, With<actor::PlayerDrivesThis>>,
mut query: Query<(
mut q_playercam: Query<(
&mut Transform,
&mut CameraController,
&mut Projection,
&mut actor::Actor,
&actor::LifeForm,
&mut actor::Engine
), (With<Camera>, Without<actor::PlayerDrivesThis>)>,
&mut actor::Engine,
&mut AngularVelocity,
&mut LinearVelocity,
), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
let dt = time.delta_seconds();
let mut play_thruster_sound = false;
@ -67,31 +136,31 @@ fn run_camera_controller(
focused = window_result.unwrap().focused;
}
if let Ok((mut transform, mut controller, mut projection, mut actor, lifeform, player_engine)) = query.get_single_mut() {
if !controller.initialized {
controller.initialized = true;
transform.rotation =
Quat::from_euler(EulerRot::ZYX, 0.0, controller.yaw, controller.pitch);
if let Ok((mut player_transform, mut engine, mut angularvelocity, mut v)) = q_playercam.get_single_mut() {
if angularvelocity.length_squared() > 0.0001 {
angularvelocity.x *= 0.98;
angularvelocity.y *= 0.98;
angularvelocity.z *= 0.98;
}
if !controller.enabled {
mouse_events.clear();
return;
else {
angularvelocity.0 = Vec3::splat(0.0);
}
// Handle key input
let mut axis_input = Vec3::ZERO;
if focused {
if key_input.pressed(settings.key_forward) {
axis_input.z -= 1.2;
}
if key_input.pressed(settings.key_back) {
axis_input.z += 1.2;
}
if key_input.pressed(settings.key_back) {
axis_input.z -= 1.2;
}
if key_input.pressed(settings.key_right) {
axis_input.x += 1.2;
axis_input.x -= 1.2;
}
if key_input.pressed(settings.key_left) {
axis_input.x -= 1.2;
axis_input.x += 1.2;
}
if key_input.pressed(settings.key_up) {
axis_input.y += 1.2;
@ -100,9 +169,9 @@ fn run_camera_controller(
axis_input.y -= 1.2;
}
if key_input.pressed(settings.key_stop) {
let stop_direction = -actor.v.normalize();
let stop_direction = -v.normalize();
if stop_direction.length_squared() > 0.3 {
axis_input += 1.0 * (transform.rotation.inverse() * stop_direction);
axis_input += 1.0 * (player_transform.rotation.inverse() * stop_direction);
}
}
}
@ -113,10 +182,8 @@ fn run_camera_controller(
// total diagonal acceleration is faster than the forward acceleration alone.
axis_input = axis_input.clamp(Vec3::splat(-1.0), Vec3::splat(1.0));
let mut engine = if let Ok(engine) = q_engine.get_single_mut() { engine } else { player_engine };
// Apply movement update
let forward_factor = engine.current_warmup * (if axis_input.z < 0.0 {
let forward_factor = engine.current_warmup * (if axis_input.z > 0.0 {
engine.thrust_forward
} else {
engine.thrust_back
@ -126,22 +193,22 @@ fn run_camera_controller(
let factor = Vec3::new(right_factor, up_factor, forward_factor);
if axis_input.length_squared() > 0.003 {
let acceleration_global = transform.rotation * (axis_input * factor);
let acceleration_global = player_transform.rotation * (axis_input * factor);
let mut acceleration_total = actor::ENGINE_SPEED_FACTOR * dt * acceleration_global;
let threshold = 1e-5;
if key_input.pressed(settings.key_stop) {
for i in 0..3 {
if actor.v[i].abs() < threshold {
actor.v[i] = 0.0;
if v[i].abs() < threshold {
v[i] = 0.0;
}
else if actor.v[i].signum() != (actor.v[i] + acceleration_total[i]).signum() {
else if v[i].signum() != (v[i] + acceleration_total[i]).signum() {
// Overshoot
actor.v[i] = 0.0;
v[i] = 0.0;
acceleration_total[i] = 0.0;
}
}
}
actor.v += acceleration_total;
v.0 += acceleration_total;
engine.current_warmup = (engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
play_thruster_sound = !settings.mute_sfx;
}
@ -157,17 +224,11 @@ fn run_camera_controller(
if mouse_delta != Vec2::ZERO {
// Apply look update
controller.pitch = (controller.pitch
- mouse_delta.y * RADIANS_PER_DOT * controller.sensitivity)
.clamp(-PI / 2., PI / 2.);
controller.yaw -= mouse_delta.x * RADIANS_PER_DOT * controller.sensitivity;
transform.rotation =
Quat::from_euler(EulerRot::ZYX, 0.0, controller.yaw, controller.pitch);
let pitch = (mouse_delta.y * RADIANS_PER_DOT * settings.mouse_sensitivity).clamp(-PI / 2., PI / 2.);
let yaw = mouse_delta.x * RADIANS_PER_DOT * settings.mouse_sensitivity;
player_transform.rotation *= Quat::from_euler(EulerRot::ZYX, 0.0, -yaw, pitch).normalize();
}
let fov = (lifeform.adrenaline * lifeform.adrenaline * lifeform.adrenaline * 45.0 + 45.0).to_radians();
*projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() });
if let Ok(sink) = thruster_sound_controller.get_single() {
if play_thruster_sound && engine.engine_type == actor::EngineType::Monopropellant {
sink.play()

View file

@ -1,3 +1,13 @@
actor 0 0 0 suit
player yes
mass 200.0
scale 1
oxygen 0.008
health 0.3
collider capsule 2 1
thrust 1.2 1 1 1 1.5
engine monopropellant
actor 300000 0 500000 jupiter
scale 200000
rotationy -1.40
@ -12,6 +22,11 @@ actor 1000 20 300 monolith
rotationx 0.5
angularmomentum 0.0 0.0 0.01
actor 10 20 30 monolith
scale 2
rotationx 0.5
angularmomentum 0.0 0.0 0.01
actor 10000 2000 -3500 monolith
scale 2
rotationx 0.5
@ -97,6 +112,9 @@ actor 10 -30 20 MeteorAceGT
vehicle yes
thrust 70 13.7 9.4 0.5 20
engine ion
collider sphere 1.5
camdistance 50
mass 500
actor 10 0 70 suit
name Icarus

View file

@ -1,6 +1,7 @@
use crate::{settings, actor, audio, nature};
use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy_xpbd_3d::prelude::*;
use std::collections::VecDeque;
use std::time::SystemTime;
@ -370,7 +371,8 @@ fn update(
diagnostics: Res<DiagnosticsStore>,
time: Res<Time>,
mut log: ResMut<Log>,
player: Query<(&actor::Suit, &actor::LifeForm, &actor::Actor), With<actor::Player>>,
player: Query<(&actor::Suit, &actor::LifeForm), With<actor::Player>>,
q_camera: Query<&LinearVelocity, With<actor::PlayerCamera>>,
mut timer: ResMut<FPSUpdateTimer>,
mut query: Query<&mut Text, With<GaugesText>>,
q_choices: Query<&ChoiceAvailable>,
@ -380,9 +382,11 @@ fn update(
) {
// TODO only when hud is actually on
if timer.0.tick(time.delta()).just_finished() || log.needs_rerendering {
let q_camera_result = q_camera.get_single();
let player = player.get_single();
if player.is_ok() {
let (suit, lifeform, actor) = player.unwrap();
if player.is_ok() && q_camera_result.is_ok() {
let (suit, lifeform) = player.unwrap();
let cam_v = q_camera_result.unwrap();
for mut text in &mut query {
if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
if let Some(value) = fps.smoothed() {
@ -409,7 +413,7 @@ fn update(
text.sections[10].value = format!("{all_actors:.0}");
let integrity = suit.integrity * 100.0;
text.sections[12].value = format!("{integrity:.0}%");
let speed = actor.v.length();
let speed = cam_v.length();
let kmh = speed * 60.0 * 60.0 / 1000.0;
text.sections[14].value = format!("{speed:.0}m/s | {kmh:.0}km/h");
}

View file

@ -28,10 +28,9 @@ fn main() {
}
}
if cfg!(debug_assertions) {
App::new().add_plugins((
OutFlyPlugin,
)).run();
App::new().add_plugins(OutFlyPlugin).run();
} else {
// In release builds, embed assets into the binary
App::new().add_plugins((
EmbeddedAssetPlugin { mode: PluginMode::ReplaceDefault },
OutFlyPlugin,

View file

@ -7,9 +7,11 @@ pub struct Settings {
pub mute_music: bool,
pub volume_sfx: u8,
pub volume_music: u8,
pub mouse_sensitivity: f32,
pub font_size_hud: f32,
pub font_size_conversations: f32,
pub hud_active: bool,
pub third_person: bool,
pub key_togglehud: KeyCode,
pub key_exit: KeyCode,
pub key_restart: KeyCode,
@ -24,6 +26,7 @@ pub struct Settings {
pub key_stop: KeyCode,
pub key_interact: KeyCode,
pub key_vehicle: KeyCode,
pub key_camera: KeyCode,
pub key_reply1: KeyCode,
pub key_reply2: KeyCode,
pub key_reply3: KeyCode,
@ -53,9 +56,11 @@ impl Default for Settings {
mute_music: default_mute_music,
volume_sfx: 100,
volume_music: 100,
mouse_sensitivity: 0.5,
font_size_hud: 32.0,
font_size_conversations: 32.0,
hud_active: false,
third_person: false,
key_togglehud: KeyCode::Tab,
key_exit: KeyCode::Escape,
key_restart: KeyCode::F12,
@ -70,6 +75,7 @@ impl Default for Settings {
key_stop: KeyCode::Space,
key_interact: KeyCode::KeyE,
key_vehicle: KeyCode::KeyQ,
key_camera: KeyCode::KeyF,
key_reply1: KeyCode::Digit1,
key_reply2: KeyCode::Digit2,
key_reply3: KeyCode::Digit3,

View file

@ -1,12 +1,12 @@
extern crate regex;
use crate::{actor, camera, nature};
use crate::{actor, nature};
use regex::Regex;
use bevy::prelude::*;
//use bevy::core_pipeline::Skybox;
//use bevy::asset::LoadState;
//use bevy::render::render_resource::{TextureViewDescriptor, TextureViewDimension};
use bevy::pbr::CascadeShadowConfigBuilder;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy_xpbd_3d::prelude::*;
use std::f32::consts::PI;
const ASTEROID_SIZE: f32 = 100.0;
@ -40,7 +40,10 @@ impl Plugin for WorldPlugin {
app.add_systems(Startup, (setup, load_defs));
//app.add_systems(Update, asset_loaded.after(load_cubemap_asset));
//app.add_systems(Update, swap_world_on_ar_toggle);
app.add_plugins(PhysicsPlugins::default());
//app.add_plugins(PhysicsDebugPlugin::default());
app.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)));
app.insert_resource(Gravity(Vec3::splat(0.0)));
}
}
@ -78,43 +81,6 @@ pub fn setup(
// cubemap_ar_handle: asset_server.load(ASSET_CUBEMAP_AR),
// });
// Add player
commands.spawn((
actor::Player,
actor::Actor {
angular_momentum: Quat::IDENTITY,
..default()
},
actor::LifeForm::default(),
actor::Suit {
oxygen: nature::OXY_H,
integrity: 0.3,
..default()
},
actor::Engine {
thrust_forward: 1.2,
..default()
},
Visibility::Visible,
Camera3dBundle {
camera: Camera {
hdr: true, // HDR is required for bloom
..default()
},
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
camera::CameraController::default(),
// Skybox {
// image: cubemap_handle,
// brightness: SKYBOX_BRIGHTNESS,
// },
BloomSettings {
composite_mode: BloomCompositeMode::EnergyConserving,
..default()
},
));
// Generate a bunch of asteriods
let maxdist = 10;
for i in -maxdist..maxdist {
@ -238,6 +204,7 @@ struct ParserState {
rotation: Quat,
angular_momentum: Quat,
pronoun: String,
is_player: bool,
is_lifeform: bool,
is_alive: bool,
is_suited: bool,
@ -249,6 +216,9 @@ struct ParserState {
warmup_seconds: f32,
engine_type: actor::EngineType,
oxygen: f32,
mass: f32,
collider: Collider,
camdistance: f32,
// Chat fields
delay: f64,
@ -278,6 +248,7 @@ impl Default for ParserState {
rotation: Quat::IDENTITY,
angular_momentum: default_actor.angular_momentum,
pronoun: "they/them".to_string(),
is_player: false,
is_lifeform: false,
is_alive: false,
is_suited: false,
@ -289,6 +260,9 @@ impl Default for ParserState {
warmup_seconds: default_engine.warmup_seconds,
engine_type: default_engine.engine_type,
oxygen: nature::OXY_D,
mass: 1.0,
collider: Collider::sphere(1.0),
camdistance: default_actor.camdistance,
delay: 0.0,
text: "".to_string(),
@ -345,32 +319,14 @@ impl ParserState {
self.reset_message();
}
fn spawn_actor(&mut self, commands: &mut Commands, asset_server: &Res<AssetServer>) {
let component_actor = actor::Actor {
let mut actor = commands.spawn_empty();
actor.insert(actor::Actor {
angular_momentum: self.angular_momentum,
id: self.id.clone(),
camdistance: self.camdistance,
..default()
};
let component_lifeform = actor::LifeForm::default();
let component_talker = actor::Talker {
conv_id: self.chat.clone(),
..default()
};
let component_vehicle = actor::Vehicle;
let component_engine = actor::Engine {
thrust_forward: self.thrust_forward,
thrust_back: self.thrust_back,
thrust_sideways: self.thrust_sideways,
reaction_wheels: self.reaction_wheels,
warmup_seconds: self.warmup_seconds,
engine_type: self.engine_type,
..default()
};
let component_suit = actor::Suit {
oxygen: self.oxygen,
oxygen_max: nature::OXY_D,
..default()
};
let component_model = SceneBundle {
});
actor.insert(SceneBundle {
transform: Transform {
translation: self.pos,
scale: Vec3::splat(self.model_scale),
@ -378,55 +334,50 @@ impl ParserState {
},
scene: asset_server.load(asset_name_to_path(self.model.as_str())),
..default()
};
});
// TODO: is there a more dynamic way to construct this...?
info!("Spawning actor {} with model {} at {}/{}/{}",
self.name, self.model, self.pos.x, self.pos.y, self.pos.z);
// Physics Parameters
let fix_scale = 1.0 / self.model_scale.powf(3.0);
actor.insert(RigidBody::Dynamic);
actor.insert(self.collider.clone());
actor.insert(ColliderDensity(self.mass * fix_scale));
// Optional Components
if self.is_player {
actor.insert(actor::Player);
actor.insert(actor::PlayerCamera);
}
if self.is_lifeform {
actor.insert(actor::LifeForm::default());
actor.insert(actor::Suit {
oxygen: self.oxygen,
oxygen_max: nature::OXY_D,
..default()
});
}
if !self.chat.is_empty() {
commands.spawn((
component_actor,
component_lifeform,
component_suit,
component_talker,
component_model,
));
actor.insert(actor::Talker {
conv_id: self.chat.clone(),
..default()
});
}
else {
commands.spawn((
component_actor,
component_lifeform,
component_suit,
component_model,
));
}
}
else {
if !self.chat.is_empty() {
commands.spawn((
component_actor,
component_talker,
component_model,
));
}
else {
if self.is_vehicle {
commands.spawn((
component_actor,
component_model,
component_vehicle,
component_engine,
));
}
else {
commands.spawn((
component_actor,
component_model,
));
}
actor.insert(actor::Vehicle);
}
if self.is_vehicle || self.is_suited {
actor.insert(actor::Engine {
thrust_forward: self.thrust_forward,
thrust_back: self.thrust_back,
thrust_sideways: self.thrust_sideways,
reaction_wheels: self.reaction_wheels,
warmup_seconds: self.warmup_seconds,
engine_type: self.engine_type,
..default()
});
}
//info!("Spawning actor {} with model {} at {}/{}/{}",
// self.name, self.model, self.pos.x, self.pos.y, self.pos.z);
self.reset();
}
fn spawn_entities(&mut self, commands: &mut Commands, asset_server: &Res<AssetServer>) {
@ -580,6 +531,46 @@ pub fn load_defs(
["engine", "ion"] => {
state.engine_type = actor::EngineType::Ion;
}
["mass", value] => {
if let Ok(value_float) = value.parse::<f32>() {
state.mass = value_float;
}
else {
error!("Can't parse float: {line}");
continue;
}
}
["collider", "sphere", radius] => {
if let Ok(radius_float) = radius.parse::<f32>() {
state.collider = Collider::sphere(radius_float);
}
else {
error!("Can't parse float: {line}");
continue;
}
}
["collider", "capsule", height, radius] => {
if let (Ok(height_float), Ok(radius_float)) = (height.parse::<f32>(), radius.parse::<f32>()) {
state.collider = Collider::capsule(height_float, radius_float);
}
else {
error!("Can't parse float: {line}");
continue;
}
}
["player", "yes"] => {
state.is_player = true;
state.is_alive = true;
}
["camdistance", value] => {
if let Ok(value_float) = value.parse::<f32>() {
state.camdistance = value_float;
}
else {
error!("Can't parse float: {line}");
continue;
}
}
// Parsing chats
["chat", chat_name] => {