Bevy 0.16 upgrade (#24)

This commit is contained in:
extrawurst
2025-04-29 00:14:25 +02:00
committed by GitHub
parent b4bfa53df4
commit 11568d57ed
40 changed files with 932 additions and 699 deletions

1212
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ opt-level = 3
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"] dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"]
[dependencies] [dependencies]
avian3d = { version = "0.2.1", default-features = false, features = [ avian3d = { version = "0.2", default-features = false, features = [
"3d", "3d",
"f32", "f32",
"parry-f32", "parry-f32",
@@ -20,23 +20,22 @@ avian3d = { version = "0.2.1", default-features = false, features = [
"bevy_picking", # todo: Consider if this one is necessary "bevy_picking", # todo: Consider if this one is necessary
"parallel", "parallel",
] } ] }
bevy = "0.15.3" bevy = { version = "0.16.0", features = ["track_location"] }
bevy_trenchbroom = { version = "0.7", features = [ bevy_trenchbroom = { version = "0.7", features = [
"auto_register", "auto_register",
"avian", "avian",
"client", "client",
] } ] }
nil = "0.14.0" nil = "0.14.0"
bevy_asset_loader = "0.22.0" bevy_asset_loader = "0.23.0-rc.3"
bevy_sprite3d = "4.0.0" bevy_sprite3d = "5.0.0"
rand = "=0.8.5" rand = "=0.8.5"
bevy-inspector-egui = { version = "0.30", optional = true } bevy-inspector-egui = { version = "0.31", optional = true }
bevy_polyline = "0.11.0"
bevy-steamworks = "0.13.0" bevy-steamworks = "0.13.0"
bevy_ballistic = "0.1.0" bevy_ballistic = "0.3.0"
bevy-ui-gradients = "0.3.0" bevy-ui-gradients = "0.4.0"
bevy_debug_log = "0.5.0" bevy_debug_log = "0.6.0"
bevy_common_assets = { version = "0.12.0", features = ["ron"] } bevy_common_assets = { version = "0.13.0", features = ["ron"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
ron = "0.8" ron = "0.8"
@@ -48,4 +47,7 @@ too_many_arguments = "allow"
type_complexity = "allow" type_complexity = "allow"
[patch.crates-io] [patch.crates-io]
bevy-steamworks = { git = "https://github.com/Laocoon7/bevy_steamworks.git" } bevy-steamworks = { git = "https://github.com/HouraiTeahouse/bevy_steamworks.git", rev = "0bc62fc" }
avian3d = { git = "https://github.com/Jondolf/avian.git", rev = "9511076" }
bevy_ballistic = { git = "https://github.com/rustunit/bevy_ballistic.git", branch = "bevy-0.16" }
bevy_trenchbroom = { git = "https://github.com/extrawurst/bevy_trenchbroom.git", branch = "bevy-0.16.0" }

View File

@@ -4,11 +4,8 @@ use crate::{
hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, sounds::PlaySound, hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, sounds::PlaySound,
tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer, tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer,
}; };
use avian3d::prelude::{ use avian3d::prelude::*;
Collider, CollisionLayers, CollisionStarted, LayerMask, PhysicsLayer, Sensor,
};
use bevy::{pbr::NotShadowCaster, prelude::*}; use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_polyline::prelude::*;
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams}; use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use std::f32::consts::PI; use std::f32::consts::PI;
@@ -83,8 +80,7 @@ fn on_trigger_gun(
mut commands: Commands, mut commands: Commands,
query_transform: Query<&Transform>, query_transform: Query<&Transform>,
time: Res<Time>, time: Res<Time>,
mut polyline_materials: ResMut<Assets<PolylineMaterial>>, mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
mut polylines: ResMut<Assets<Polyline>>,
) { ) {
let state = trigger.0; let state = trigger.0;
@@ -117,19 +113,20 @@ fn on_trigger_gun(
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()), LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
), ),
Sensor, Sensor,
CollisionEventsEnabled,
Visibility::default(), Visibility::default(),
t, t,
)) ))
.with_child(PolylineBundle { .with_child(Gizmo {
polyline: PolylineHandle(polylines.add(Polyline { handle: gizmo_assets.add({
vertices: vec![Vec3::Z * 2., Vec3::Z * -2.], let mut g = GizmoAsset::default();
})), g.line(Vec3::Z * -2., Vec3::Z * 2., LinearRgba::rgb(0.9, 0.9, 0.));
material: PolylineMaterialHandle(polyline_materials.add(PolylineMaterial { g
width: 10.0, }),
color: LinearRgba::rgb(0.9, 0.9, 0.), line_config: GizmoLineConfig {
perspective: false, width: 8.,
..default() ..default()
})), },
..default() ..default()
}); });
} }
@@ -146,7 +143,7 @@ fn timeout(mut commands: Commands, query: Query<(Entity, &GunProjectile)>, time:
for (e, GunProjectile { time, .. }) in query.iter() { for (e, GunProjectile { time, .. }) in query.iter() {
if current_time > time + MAX_SHOT_AGES { if current_time > time + MAX_SHOT_AGES {
commands.entity(e).despawn_recursive(); commands.entity(e).despawn();
} }
} }
} }
@@ -167,7 +164,7 @@ fn shot_collision(
let shot_pos = query_shot.get(shot_entity).unwrap().1.translation; let shot_pos = query_shot.get(shot_entity).unwrap().1.translation;
commands.entity(shot_entity).despawn_recursive(); commands.entity(shot_entity).despawn();
let texture_atlas = TextureAtlas { let texture_atlas = TextureAtlas {
layout: assets.layout.clone(), layout: assets.layout.clone(),

View File

@@ -7,15 +7,14 @@ use crate::{
loading_assets::GameAssets, loading_assets::GameAssets,
physics_layers::GameLayer, physics_layers::GameLayer,
sounds::PlaySound, sounds::PlaySound,
utils::{global_observer, sprite_3d_animation::AnimationTimer}, utils::{global_observer, sprite_3d_animation::AnimationTimer, trail::Trail},
}; };
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*}; use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_polyline::prelude::*;
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams}; use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use std::f32::consts::PI; use std::f32::consts::PI;
const MAX_SHOT_AGES: f32 = 5.; const MAX_SHOT_AGES: f32 = 15.;
#[derive(Component)] #[derive(Component)]
struct MissileProjectile { struct MissileProjectile {
@@ -66,7 +65,7 @@ fn on_explosion(
}; };
for entity in intersections.iter() { for entity in intersections.iter() {
if let Some(mut e) = commands.get_entity(*entity) { if let Ok(mut e) = commands.get_entity(*entity) {
e.trigger(Hit { e.trigger(Hit {
damage: explosion.damage, damage: explosion.damage,
}); });
@@ -89,11 +88,10 @@ fn on_trigger_missile(
mut commands: Commands, mut commands: Commands,
query_transform: Query<&Transform>, query_transform: Query<&Transform>,
time: Res<Time>, time: Res<Time>,
mut polyline_materials: ResMut<Assets<PolylineMaterial>>,
mut polylines: ResMut<Assets<Polyline>>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,
assets: Res<GameAssets>, assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>, gltf_assets: Res<Assets<Gltf>>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) { ) {
let state = trigger.event().0; let state = trigger.event().0;
@@ -111,7 +109,7 @@ fn on_trigger_missile(
let head = heads_db.head_stats(state.head); let head = heads_db.head_stats(state.head);
let mut t = Transform::from_translation(state.pos).with_rotation(rotation); let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 3.0; t.translation += t.forward().as_vec3() * 2.0;
let mesh = assets.projectiles["missile.glb"].clone(); let mesh = assets.projectiles["missile.glb"].clone();
let asset = gltf_assets.get(&mesh).unwrap(); let asset = gltf_assets.get(&mesh).unwrap();
@@ -123,12 +121,13 @@ fn on_trigger_missile(
time: time.elapsed_secs(), time: time.elapsed_secs(),
damage: head.damage, damage: head.damage,
}, },
Collider::capsule_endpoints(0.5, Vec3::new(0., 0., 0.), Vec3::new(0., 0., -3.)), Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new( CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()), LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()), LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
), ),
Sensor, Sensor,
CollisionEventsEnabled,
Visibility::default(), Visibility::default(),
t, t,
)) ))
@@ -137,18 +136,17 @@ fn on_trigger_missile(
.with_scale(Vec3::splat(0.04)), .with_scale(Vec3::splat(0.04)),
SceneRoot(asset.scenes[0].clone()), SceneRoot(asset.scenes[0].clone()),
)) ))
.with_child(PolylineBundle { .with_child((
polyline: PolylineHandle(polylines.add(Polyline { Trail::new(t.translation, LinearRgba::rgb(0.9, 0.9, 0.)),
vertices: vec![Vec3::Z * 4., Vec3::Z * 0.], Gizmo {
})), handle: gizmo_assets.add(GizmoAsset::default()),
material: PolylineMaterialHandle(polyline_materials.add(PolylineMaterial { line_config: GizmoLineConfig {
width: 12.0, width: 10.,
color: LinearRgba::rgb(0.9, 0.9, 0.), ..default()
perspective: false, },
..default() ..default()
})), },
..default() ));
});
} }
fn update(mut query: Query<&mut Transform, With<MissileProjectile>>) { fn update(mut query: Query<&mut Transform, With<MissileProjectile>>) {
@@ -163,7 +161,7 @@ fn timeout(mut commands: Commands, query: Query<(Entity, &MissileProjectile)>, t
for (e, MissileProjectile { time, .. }) in query.iter() { for (e, MissileProjectile { time, .. }) in query.iter() {
if current_time > time + MAX_SHOT_AGES { if current_time > time + MAX_SHOT_AGES {
commands.entity(e).despawn_recursive(); commands.entity(e).despawn();
} }
} }
} }
@@ -191,7 +189,7 @@ fn shot_collision(
commands.trigger(PlaySound::MissileExplosion); commands.trigger(PlaySound::MissileExplosion);
commands.entity(shot_entity).despawn_recursive(); commands.entity(shot_entity).despawn();
commands.trigger(Explosion { commands.trigger(Explosion {
damage, damage,

View File

@@ -88,16 +88,12 @@ fn on_trigger_state(
mut commands: Commands, mut commands: Commands,
player_rot: Query<&Transform, With<PlayerBodyMesh>>, player_rot: Query<&Transform, With<PlayerBodyMesh>>,
player_query: Query<(Entity, &AimTarget), With<Player>>, player_query: Query<(Entity, &AimTarget), With<Player>>,
mut active_heads: Query<&mut ActiveHeads, With<Player>>, mut active_heads: Single<&mut ActiveHeads, With<Player>>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,
time: Res<Time>, time: Res<Time>,
character: CharacterHierarchy, character: CharacterHierarchy,
) { ) {
if matches!(trigger.event(), TriggerState::Active) { if matches!(trigger.event(), TriggerState::Active) {
let Ok(mut active_heads) = active_heads.get_single_mut() else {
return;
};
let Some(state) = active_heads.current() else { let Some(state) = active_heads.current() else {
return; return;
}; };

View File

@@ -67,7 +67,7 @@ fn on_explosion(
}; };
for entity in intersections.iter() { for entity in intersections.iter() {
if let Some(mut e) = commands.get_entity(*entity) { if let Ok(mut e) = commands.get_entity(*entity) {
e.trigger(Hit { e.trigger(Hit {
damage: explosion.damage, damage: explosion.damage,
}); });
@@ -135,6 +135,7 @@ fn on_trigger_thrown(
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()), LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
), ),
RigidBody::Dynamic, RigidBody::Dynamic,
CollisionEventsEnabled,
Mass(0.01), Mass(0.01),
LinearVelocity(vel), LinearVelocity(vel),
Visibility::default(), Visibility::default(),
@@ -171,7 +172,7 @@ fn shot_collision(
continue; continue;
}; };
commands.entity(shot_entity).despawn_recursive(); commands.entity(shot_entity).despawn();
commands.trigger(PlaySound::ThrowHit); commands.trigger(PlaySound::ThrowHit);

View File

@@ -56,7 +56,7 @@ fn wait_for_player(
for agent in agents.iter() { for agent in agents.iter() {
if let Some(player) = in_range(50., agent, &players, &transform) { if let Some(player) = in_range(50., agent, &players, &transform) {
info!("[{agent}] Engage: {player}"); info!("[{agent}] Engage: {player}");
if let Some(mut agent) = commands.get_entity(agent) { if let Ok(mut agent) = commands.get_entity(agent) {
agent.remove::<WaitForAnyPlayer>().insert(Engage(player)); agent.remove::<WaitForAnyPlayer>().insert(Engage(player));
} }
} }

View File

@@ -32,7 +32,7 @@ fn marker_event(
marker: Query<Entity, With<TargetMarker>>, marker: Query<Entity, With<TargetMarker>>,
) { ) {
for m in marker.iter() { for m in marker.iter() {
commands.entity(m).despawn_recursive(); commands.entity(m).despawn();
} }
let MarkerEvent::Spawn(target) = trigger.event() else { let MarkerEvent::Spawn(target) = trigger.event() else {

View File

@@ -110,7 +110,7 @@ fn update_player_aim(
} }
if let Some(e) = &aim_target.0 { if let Some(e) = &aim_target.0 {
if commands.get_entity(*e).is_none() { if commands.get_entity(*e).is_err() {
aim_target.0 = None; aim_target.0 = None;
return; return;
} }
@@ -162,7 +162,7 @@ fn update_npc_aim(
} }
if let Some(e) = &aim_target.0 { if let Some(e) = &aim_target.0 {
if commands.get_entity(*e).is_none() { if commands.get_entity(*e).is_err() {
aim_target.0 = None; aim_target.0 = None;
return; return;
} }

View File

@@ -53,7 +53,7 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
} }
fn spawn_head_ui( fn spawn_head_ui(
parent: &mut ChildBuilder, parent: &mut ChildSpawnerCommands,
bg: Handle<Image>, bg: Handle<Image>,
regular: Handle<Image>, regular: Handle<Image>,
damage: Handle<Image>, damage: Handle<Image>,
@@ -129,7 +129,7 @@ fn update(
mut head_damage: Query<&mut Node, (With<HeadDamage>, Without<HeadImage>)>, mut head_damage: Query<&mut Node, (With<HeadDamage>, Without<HeadImage>)>,
) { ) {
if target.is_changed() { if target.is_changed() {
if let Ok((mut vis, mut image)) = head_image.get_single_mut() { if let Ok((mut vis, mut image)) = head_image.single_mut() {
if let Some(head) = target.head { if let Some(head) = target.head {
*vis = Visibility::Visible; *vis = Visibility::Visible;
image.image = heads_images.heads[head.head].clone(); image.image = heads_images.heads[head.head].clone();
@@ -138,7 +138,7 @@ fn update(
} }
} }
if let Ok(mut node) = head_damage.get_single_mut() { if let Ok(mut node) = head_damage.single_mut() {
node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.); node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.);
} }
} }

View File

@@ -86,6 +86,7 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
commands.spawn(( commands.spawn((
Name::new("backpack-head-count-ui"), Name::new("backpack-head-count-ui"),
Text::new("0"), Text::new("0"),
TextShadow::default(),
BackpackCountText, BackpackCountText,
TextFont { TextFont {
font: assets.font.clone(), font: assets.font.clone(),
@@ -104,7 +105,7 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
} }
fn spawn_head_ui( fn spawn_head_ui(
parent: &mut ChildBuilder, parent: &mut ChildSpawnerCommands,
bg: Handle<Image>, bg: Handle<Image>,
regular: Handle<Image>, regular: Handle<Image>,
selector: Handle<Image>, selector: Handle<Image>,

View File

@@ -103,7 +103,7 @@ fn update_ui(
)); ));
} else { } else {
for entity in query.iter() { for entity in query.iter() {
commands.entity(entity).despawn_recursive(); commands.entity(entity).despawn();
} }
} }
} }
@@ -114,8 +114,8 @@ fn update(
(&MainCamera, &mut Transform, &CameraRotationInput), (&MainCamera, &mut Transform, &CameraRotationInput),
(Without<CameraTarget>, Without<CameraArmRotation>), (Without<CameraTarget>, Without<CameraArmRotation>),
>, >,
target: Query<&GlobalTransform, (With<CameraTarget>, Without<CameraArmRotation>)>, target: Single<&GlobalTransform, (With<CameraTarget>, Without<CameraArmRotation>)>,
arm_rotation: Query<&Transform, With<CameraArmRotation>>, arm_rotation: Single<&Transform, With<CameraArmRotation>>,
spatial_query: SpatialQuery, spatial_query: SpatialQuery,
cam_state: Res<CameraState>, cam_state: Res<CameraState>,
) { ) {
@@ -123,15 +123,11 @@ fn update(
return; return;
} }
let Ok(target) = target.get_single().map(|t| t.translation()) else { let target = target.translation();
return;
};
let Ok(arm_tf) = arm_rotation.get_single() else { let arm_tf = arm_rotation;
return;
};
let Ok((camera, mut cam_transform, cam_rotation_input)) = cam.get_single_mut() else { let Ok((camera, mut cam_transform, cam_rotation_input)) = cam.single_mut() else {
return; return;
}; };
@@ -159,13 +155,9 @@ fn update(
fn rotate_view( fn rotate_view(
controls: Res<ControlState>, controls: Res<ControlState>,
mut cam: Query<&mut CameraRotationInput>, mut cam: Single<&mut CameraRotationInput>,
movement: Res<PlayerMovement>, movement: Res<PlayerMovement>,
) { ) {
let Ok(mut cam) = cam.get_single_mut() else {
return;
};
if !controls.view_mode { if !controls.view_mode {
if movement.any_direction { if movement.any_direction {
cam.x = 0.; cam.x = 0.;

View File

@@ -63,6 +63,7 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
commands.spawn(( commands.spawn((
Name::new("cash-ui"), Name::new("cash-ui"),
Text::new("0"), Text::new("0"),
TextShadow::default(),
CashText, CashText,
TextFont { TextFont {
font: assets.font.clone(), font: assets.font.clone(),

View File

@@ -20,7 +20,7 @@ fn on_heal_trigger(
mut cash: ResMut<CashResource>, mut cash: ResMut<CashResource>,
mut query: Query<&mut Hitpoints, With<Player>>, mut query: Query<&mut Hitpoints, With<Player>>,
) { ) {
let Ok(mut hp) = query.get_single_mut() else { let Ok(mut hp) = query.single_mut() else {
return; return;
}; };

View File

@@ -1,5 +1,5 @@
use crate::{GameState, heads_database::HeadsDatabase, loading_assets::GameAssets}; use crate::{GameState, heads_database::HeadsDatabase, loading_assets::GameAssets};
use bevy::{ecs::system::SystemParam, prelude::*, utils::HashMap}; use bevy::{ecs::system::SystemParam, platform::collections::HashMap, prelude::*};
use std::time::Duration; use std::time::Duration;
#[derive(Component, Debug)] #[derive(Component, Debug)]
@@ -91,14 +91,14 @@ fn setup_projectile_origin(
fn setup_once_loaded( fn setup_once_loaded(
mut commands: Commands, mut commands: Commands,
mut query: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>, mut query: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
parent_query: Query<&Parent>, parent: Query<&ChildOf>,
animated_character: Query<&AnimatedCharacter>, animated_character: Query<&AnimatedCharacter>,
assets: Res<GameAssets>, assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>, gltf_assets: Res<Assets<Gltf>>,
mut graphs: ResMut<Assets<AnimationGraph>>, mut graphs: ResMut<Assets<AnimationGraph>>,
) { ) {
for (entity, mut player) in &mut query { for (entity, mut player) in &mut query {
let Some(_animated_character) = parent_query let Some(_animated_character) = parent
.iter_ancestors(entity) .iter_ancestors(entity)
.find_map(|ancestor| animated_character.get(ancestor).ok()) .find_map(|ancestor| animated_character.get(ancestor).ok())
else { else {

View File

@@ -12,16 +12,11 @@ use super::controller_common::{CharacterController, MaxSlopeAngle};
/// and predict collisions using speculative contacts. /// and predict collisions using speculative contacts.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn kinematic_controller_collisions( pub fn kinematic_controller_collisions(
collisions: Res<Collisions>, collisions: Collisions,
bodies: Query<&RigidBody>, bodies: Query<&RigidBody>,
collider_parents: Query<&ColliderParent, Without<Sensor>>, collider_rbs: Query<&ColliderOf, Without<Sensor>>,
mut character_controllers: Query< mut character_controllers: Query<
( (&mut Position, &mut LinearVelocity, Option<&MaxSlopeAngle>),
&mut Position,
&Rotation,
&mut LinearVelocity,
Option<&MaxSlopeAngle>,
),
(With<RigidBody>, With<CharacterController>), (With<RigidBody>, With<CharacterController>),
>, >,
time: Res<Time>, time: Res<Time>,
@@ -29,8 +24,12 @@ pub fn kinematic_controller_collisions(
// Iterate through collisions and move the kinematic body to resolve penetration // Iterate through collisions and move the kinematic body to resolve penetration
for contacts in collisions.iter() { for contacts in collisions.iter() {
// Get the rigid body entities of the colliders (colliders could be children) // Get the rigid body entities of the colliders (colliders could be children)
let Ok([collider_parent1, collider_parent2]) = let Ok(
collider_parents.get_many([contacts.entity1, contacts.entity2]) [
&ColliderOf { rigid_body: rb1 },
&ColliderOf { rigid_body: rb2 },
],
) = collider_rbs.get_many([contacts.entity1, contacts.entity2])
else { else {
continue; continue;
}; };
@@ -42,20 +41,16 @@ pub fn kinematic_controller_collisions(
let character_rb: RigidBody; let character_rb: RigidBody;
let is_other_dynamic: bool; let is_other_dynamic: bool;
let (mut position, rotation, mut linear_velocity, max_slope_angle) = let (mut position, mut linear_velocity, max_slope_angle) =
if let Ok(character) = character_controllers.get_mut(collider_parent1.get()) { if let Ok(character) = character_controllers.get_mut(rb1) {
is_first = true; is_first = true;
character_rb = *bodies.get(collider_parent1.get()).unwrap(); character_rb = *bodies.get(rb1).unwrap();
is_other_dynamic = bodies is_other_dynamic = bodies.get(rb2).is_ok_and(|rb| rb.is_dynamic());
.get(collider_parent2.get())
.is_ok_and(|rb| rb.is_dynamic());
character character
} else if let Ok(character) = character_controllers.get_mut(collider_parent2.get()) { } else if let Ok(character) = character_controllers.get_mut(rb2) {
is_first = false; is_first = false;
character_rb = *bodies.get(collider_parent2.get()).unwrap(); character_rb = *bodies.get(rb2).unwrap();
is_other_dynamic = bodies is_other_dynamic = bodies.get(rb1).is_ok_and(|rb| rb.is_dynamic());
.get(collider_parent1.get())
.is_ok_and(|rb| rb.is_dynamic());
character character
} else { } else {
continue; continue;
@@ -70,15 +65,15 @@ pub fn kinematic_controller_collisions(
// Each contact in a single manifold shares the same contact normal. // Each contact in a single manifold shares the same contact normal.
for manifold in contacts.manifolds.iter() { for manifold in contacts.manifolds.iter() {
let normal = if is_first { let normal = if is_first {
-manifold.global_normal1(rotation) -manifold.normal
} else { } else {
-manifold.global_normal2(rotation) manifold.normal
}; };
let mut deepest_penetration: Scalar = Scalar::MIN; let mut deepest_penetration: Scalar = Scalar::MIN;
// Solve each penetrating contact in the manifold. // Solve each penetrating contact in the manifold.
for contact in manifold.contacts.iter() { for contact in manifold.points.iter() {
if contact.penetration > 0.0 { if contact.penetration > 0.0 {
position.0 += normal * contact.penetration; position.0 += normal * contact.penetration;
} }

View File

@@ -22,8 +22,10 @@ pub fn plugin(app: &mut App) {
// //
// NOTE: The collision implementation here is very basic and a bit buggy. // NOTE: The collision implementation here is very basic and a bit buggy.
// A collide-and-slide algorithm would likely work better. // A collide-and-slide algorithm would likely work better.
PostProcessCollisions, PhysicsSchedule,
kinematic_controller_collisions.run_if(in_state(GameState::Playing)), // todo check if we can make this less viral, kinematic_controller_collisions
.in_set(NarrowPhaseSet::Last)
.run_if(in_state(GameState::Playing)),
); );
} }
@@ -122,6 +124,7 @@ pub struct CharacterControllerBundle {
ground_caster: ShapeCaster, ground_caster: ShapeCaster,
gravity: ControllerGravity, gravity: ControllerGravity,
movement: MovementBundle, movement: MovementBundle,
collision_events: CollisionEventsEnabled,
} }
/// A bundle that contains components for character movement. /// A bundle that contains components for character movement.
@@ -169,6 +172,7 @@ impl CharacterControllerBundle {
.with_max_distance(0.2), .with_max_distance(0.2),
gravity: ControllerGravity(gravity), gravity: ControllerGravity(gravity),
movement: MovementBundle::default(), movement: MovementBundle::default(),
collision_events: CollisionEventsEnabled,
} }
} }
} }

View File

@@ -85,7 +85,7 @@ fn head_change(
}; };
if selected_controller.0 != controller { if selected_controller.0 != controller {
event_controller_switch.send(ControllerSwitchEvent); event_controller_switch.write(ControllerSwitchEvent);
selected_controller.0 = controller; selected_controller.0 = controller;
} }

View File

@@ -5,7 +5,7 @@ use crate::{
tb_entities::{CameraTarget, CutsceneCamera, CutsceneCameraMovementEnd}, tb_entities::{CameraTarget, CutsceneCamera, CutsceneCameraMovementEnd},
}; };
use bevy::prelude::*; use bevy::prelude::*;
use bevy_trenchbroom::class::Target; use bevy_trenchbroom::prelude::*;
#[derive(Event, Debug)] #[derive(Event, Debug)]
pub struct StartCutscene(pub String); pub struct StartCutscene(pub String);
@@ -96,7 +96,7 @@ fn update(
.lerp(camera_end.rotation, timer.fraction()), .lerp(camera_end.rotation, timer.fraction()),
); );
*cam.single_mut() = t; let _ = cam.single_mut().map(|mut cam| *cam = t);
if timer.finished() { if timer.finished() {
cam_state.cutscene = false; cam_state.cutscene = false;

View File

@@ -2,7 +2,7 @@ use crate::{
cutscene::StartCutscene, global_observer, keys::KeyCollected, movables::TriggerMovableEvent, cutscene::StartCutscene, global_observer, keys::KeyCollected, movables::TriggerMovableEvent,
sounds::PlaySound, sounds::PlaySound,
}; };
use bevy::{prelude::*, utils::hashbrown::HashSet}; use bevy::{platform::collections::HashSet, prelude::*};
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
global_observer!(app, on_key_collected); global_observer!(app, on_key_collected);

View File

@@ -40,6 +40,7 @@ fn on_head_drop(trigger: Trigger<HeadDrops>, mut commands: Commands, assets: Res
ExternalImpulse::new(spawn_dir * 180.).with_persistence(false), ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
LockedAxes::ROTATION_LOCKED, LockedAxes::ROTATION_LOCKED,
RigidBody::Dynamic, RigidBody::Dynamic,
CollisionEventsEnabled,
Restitution::new(0.6), Restitution::new(0.6),
)) ))
.with_child(( .with_child((
@@ -68,6 +69,6 @@ fn collect_head(
commands.trigger(PlaySound::HeadCollect); commands.trigger(PlaySound::HeadCollect);
commands.trigger(HeadCollected(key.0)); commands.trigger(HeadCollected(key.0));
commands.entity(collectable).despawn_recursive(); commands.entity(collectable).despawn();
} }
} }

View File

@@ -64,7 +64,7 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
} }
fn spawn_head_ui( fn spawn_head_ui(
parent: &mut ChildBuilder, parent: &mut ChildSpawnerCommands,
bg: Handle<Image>, bg: Handle<Image>,
regular: Handle<Image>, regular: Handle<Image>,
selector: Handle<Image>, selector: Handle<Image>,
@@ -219,7 +219,7 @@ fn sync(
mut state: ResMut<UiActiveHeads>, mut state: ResMut<UiActiveHeads>,
time: Res<Time>, time: Res<Time>,
) { ) {
let Ok(active_heads) = active_heads.get_single() else { let Ok(active_heads) = active_heads.single() else {
return; return;
}; };

View File

@@ -182,7 +182,7 @@ fn on_select_active_head(
mut commands: Commands, mut commands: Commands,
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>, mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
) { ) {
let Ok((mut active_heads, mut hp)) = query.get_single_mut() else { let Ok((mut active_heads, mut hp)) = query.single_mut() else {
return; return;
}; };
@@ -213,7 +213,7 @@ fn on_swap_backpack(
let head = backpack.heads.get(backpack_slot).unwrap(); let head = backpack.heads.get(backpack_slot).unwrap();
let Ok((mut active_heads, mut hp)) = query.get_single_mut() else { let Ok((mut active_heads, mut hp)) = query.single_mut() else {
return; return;
}; };

View File

@@ -60,7 +60,7 @@ fn on_hp_added(mut commands: Commands, query: Query<Entity, Added<Hitpoints>>) {
fn on_hit(trigger: Trigger<Hit>, mut commands: Commands, mut query: Query<&mut Hitpoints>) { fn on_hit(trigger: Trigger<Hit>, mut commands: Commands, mut query: Query<&mut Hitpoints>) {
let Hit { damage } = trigger.event(); let Hit { damage } = trigger.event();
let Ok(mut hp) = query.get_mut(trigger.entity()) else { let Ok(mut hp) = query.get_mut(trigger.target()) else {
return; return;
}; };
@@ -69,6 +69,6 @@ fn on_hit(trigger: Trigger<Hit>, mut commands: Commands, mut query: Query<&mut H
hp.current = hp.current.saturating_sub(*damage); hp.current = hp.current.saturating_sub(*damage);
if hp.current == 0 { if hp.current == 0 {
commands.trigger_targets(Kill, trigger.entity()); commands.trigger_targets(Kill, trigger.target());
} }
} }

View File

@@ -38,6 +38,7 @@ fn on_spawn_key(trigger: Trigger<KeySpawn>, mut commands: Commands, assets: Res<
ExternalImpulse::new(spawn_dir * 180.).with_persistence(false), ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
LockedAxes::ROTATION_LOCKED, LockedAxes::ROTATION_LOCKED,
RigidBody::Dynamic, RigidBody::Dynamic,
CollisionEventsEnabled,
Restitution::new(0.6), Restitution::new(0.6),
)) ))
.with_child(( .with_child((
@@ -66,6 +67,6 @@ fn collect_key(
commands.trigger(PlaySound::KeyCollect); commands.trigger(PlaySound::KeyCollect);
commands.trigger(KeyCollected(key.0.clone())); commands.trigger(KeyCollected(key.0.clone()));
commands.entity(collectable).despawn_recursive(); commands.entity(collectable).despawn();
} }
} }

View File

@@ -2,7 +2,7 @@ use crate::{
GameState, GameState,
heads_database::{HeadDatabaseAsset, HeadsDatabase}, heads_database::{HeadDatabaseAsset, HeadsDatabase},
}; };
use bevy::{prelude::*, utils::HashMap}; use bevy::{platform::collections::HashMap, prelude::*};
use bevy_asset_loader::prelude::*; use bevy_asset_loader::prelude::*;
#[derive(AssetCollection, Resource)] #[derive(AssetCollection, Resource)]

View File

@@ -1,6 +1,7 @@
use crate::{GameState, physics_layers::GameLayer}; use crate::{GameState, physics_layers::GameLayer};
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::{prelude::*, scene::SceneInstanceReady}; use bevy::prelude::*;
use bevy_trenchbroom::physics::SceneCollidersReady;
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.add_systems(OnEnter(GameState::MapLoading), setup_scene); app.add_systems(OnEnter(GameState::MapLoading), setup_scene);
@@ -13,7 +14,8 @@ fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
SceneRoot(asset_server.load("maps/map1.map#Scene")), SceneRoot(asset_server.load("maps/map1.map#Scene")),
)) ))
.observe( .observe(
|_t: Trigger<SceneInstanceReady>, mut next_game_state: ResMut<NextState<GameState>>| { |_t: Trigger<SceneCollidersReady>,
mut next_game_state: ResMut<NextState<GameState>>| {
info!("map loaded"); info!("map loaded");
next_game_state.set(GameState::Playing); next_game_state.set(GameState::Playing);

View File

@@ -36,16 +36,15 @@ use bevy::{
render::view::ColorGrading, render::view::ColorGrading,
}; };
use bevy_common_assets::ron::RonAssetPlugin; use bevy_common_assets::ron::RonAssetPlugin;
use bevy_polyline::PolylinePlugin;
use bevy_sprite3d::Sprite3dPlugin; use bevy_sprite3d::Sprite3dPlugin;
use bevy_steamworks::{FriendFlags, SteamworksClient, SteamworksPlugin}; use bevy_steamworks::{Client, FriendFlags, SteamworksPlugin};
use bevy_trenchbroom::prelude::*; use bevy_trenchbroom::prelude::*;
use bevy_ui_gradients::UiGradientsPlugin; use bevy_ui_gradients::UiGradientsPlugin;
use camera::MainCamera; use camera::MainCamera;
use heads_database::HeadDatabaseAsset; use heads_database::HeadDatabaseAsset;
use loading_assets::AudioAssets; use loading_assets::AudioAssets;
use std::io::{Read, Write}; use std::io::{Read, Write};
use utils::{billboards, sprite_3d_animation, squish_animation}; use utils::{billboards, sprite_3d_animation, squish_animation, trail};
#[derive(Resource, Reflect, Debug)] #[derive(Resource, Reflect, Debug)]
#[reflect(Resource)] #[reflect(Resource)]
@@ -106,7 +105,6 @@ fn main() {
app.add_plugins(bevy_debug_log::LogViewerPlugin::default()); app.add_plugins(bevy_debug_log::LogViewerPlugin::default());
app.add_plugins(PhysicsPlugins::default()); app.add_plugins(PhysicsPlugins::default());
app.add_plugins(PolylinePlugin);
app.add_plugins(Sprite3dPlugin); app.add_plugins(Sprite3dPlugin);
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz"))); app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
app.add_plugins(UiGradientsPlugin); app.add_plugins(UiGradientsPlugin);
@@ -114,8 +112,10 @@ fn main() {
#[cfg(feature = "dbg")] #[cfg(feature = "dbg")]
{ {
use bevy_inspector_egui::quick::WorldInspectorPlugin; app.add_plugins(bevy_inspector_egui::bevy_egui::EguiPlugin {
app.add_plugins(WorldInspectorPlugin::new()); enable_multipass_for_primary_context: true,
});
app.add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::new());
app.add_plugins(PhysicsDebugPlugin::default()); app.add_plugins(PhysicsDebugPlugin::default());
// app.add_plugins(bevy::pbr::wireframe::WireframePlugin) // app.add_plugins(bevy::pbr::wireframe::WireframePlugin)
@@ -153,22 +153,24 @@ fn main() {
app.add_plugins(utils::observers::plugin); app.add_plugins(utils::observers::plugin);
app.add_plugins(water::plugin); app.add_plugins(water::plugin);
app.add_plugins(head_drop::plugin); app.add_plugins(head_drop::plugin);
app.add_plugins(trail::plugin);
app.init_state::<GameState>(); app.init_state::<GameState>();
app.insert_resource(AmbientLight { app.insert_resource(AmbientLight {
color: Color::WHITE, color: Color::WHITE,
brightness: 400., brightness: 400.,
..Default::default()
}); });
app.insert_resource(ClearColor(Color::BLACK)); app.insert_resource(ClearColor(Color::BLACK));
//TODO: let use control this //TODO: let user control this
app.insert_resource(GlobalVolume::new(0.1)); app.insert_resource(GlobalVolume::new(Volume::Linear(0.4)));
app.add_systems( app.add_systems(
Startup, Startup,
( (
write_trenchbroom_config, write_trenchbroom_config,
steam_system.run_if(resource_exists::<SteamworksClient>), steam_system.run_if(resource_exists::<Client>),
), ),
); );
app.add_systems(OnEnter(GameState::Playing), music); app.add_systems(OnEnter(GameState::Playing), music);
@@ -177,7 +179,7 @@ fn main() {
app.run(); app.run();
} }
fn steam_system(steam_client: Res<SteamworksClient>) { fn steam_system(steam_client: Res<Client>) {
for friend in steam_client.friends().get_friends(FriendFlags::IMMEDIATE) { for friend in steam_client.friends().get_friends(FriendFlags::IMMEDIATE) {
info!( info!(
"Steam Friend: {:?} - {}({:?})", "Steam Friend: {:?} - {}({:?})",
@@ -215,7 +217,7 @@ fn music(assets: Res<AudioAssets>, mut commands: Commands) {
AudioPlayer::new(assets.music.clone()), AudioPlayer::new(assets.music.clone()),
PlaybackSettings { PlaybackSettings {
mode: PlaybackMode::Loop, mode: PlaybackMode::Loop,
volume: Volume::new(0.6), volume: Volume::Linear(0.6),
..default() ..default()
}, },
)); ));
@@ -225,7 +227,7 @@ fn music(assets: Res<AudioAssets>, mut commands: Commands) {
AudioPlayer::new(assets.ambient.clone()), AudioPlayer::new(assets.ambient.clone()),
PlaybackSettings { PlaybackSettings {
mode: PlaybackMode::Loop, mode: PlaybackMode::Loop,
volume: Volume::new(0.8), volume: Volume::Linear(0.8),
..default() ..default()
}, },
)); ));

View File

@@ -2,8 +2,8 @@ use crate::{
GameState, global_observer, GameState, global_observer,
tb_entities::{Movable, MoveTarget}, tb_entities::{Movable, MoveTarget},
}; };
use bevy::{prelude::*, utils::HashSet}; use bevy::{platform::collections::HashSet, prelude::*};
use bevy_trenchbroom::class::Target; use bevy_trenchbroom::prelude::*;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]

View File

@@ -55,13 +55,13 @@ fn on_kill(
mut commands: Commands, mut commands: Commands,
query: Query<(&Transform, &EnemySpawn, &ActiveHead)>, query: Query<(&Transform, &EnemySpawn, &ActiveHead)>,
) { ) {
let Ok((transform, enemy, head)) = query.get(trigger.entity()) else { let Ok((transform, enemy, head)) = query.get(trigger.target()) else {
return; return;
}; };
commands.trigger(HeadDrops(transform.translation, head.0)); commands.trigger(HeadDrops(transform.translation, head.0));
commands.entity(trigger.entity()).despawn_recursive(); commands.entity(trigger.target()).despawn();
if !enemy.key.is_empty() { if !enemy.key.is_empty() {
commands.trigger(KeySpawn(transform.translation, enemy.key.clone())); commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));

View File

@@ -3,7 +3,7 @@ use crate::{
tb_entities::{Platform, PlatformTarget}, tb_entities::{Platform, PlatformTarget},
}; };
use bevy::{math::ops::sin, prelude::*}; use bevy::{math::ops::sin, prelude::*};
use bevy_trenchbroom::class::Target; use bevy_trenchbroom::prelude::*;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]

View File

@@ -102,9 +102,12 @@ fn spawn(
)); ));
} }
fn cursor_recenter(mut q_windows: Query<&mut Window, With<PrimaryWindow>>) { fn cursor_recenter(q_windows: Single<&mut Window, With<PrimaryWindow>>) {
let mut primary_window = q_windows.single_mut(); let mut primary_window = q_windows;
let center = Vec2::new(primary_window.width() / 2.0, primary_window.height() / 2.0); let center = Vec2::new(
primary_window.resolution.width() / 2.,
primary_window.resolution.height() / 2.,
);
primary_window.set_cursor_position(Some(center)); primary_window.set_cursor_position(Some(center));
} }
@@ -121,12 +124,8 @@ fn toggle_grab_cursor(window: &mut Window) {
} }
} }
fn toggle_cursor_system(mut primary_window: Query<&mut Window, With<PrimaryWindow>>) { fn toggle_cursor_system(mut window: Single<&mut Window, With<PrimaryWindow>>) {
if let Ok(mut window) = primary_window.get_single_mut() { toggle_grab_cursor(&mut window);
toggle_grab_cursor(&mut window);
} else {
warn!("Primary window not found for `toggle_cursor_system`!");
}
} }
fn collect_cash( fn collect_cash(
@@ -154,11 +153,11 @@ fn collect_cash(
fn setup_animations_marker_for_player( fn setup_animations_marker_for_player(
mut commands: Commands, mut commands: Commands,
animation_handles: Query<Entity, Added<AnimationGraphHandle>>, animation_handles: Query<Entity, Added<AnimationGraphHandle>>,
parent_query: Query<&Parent>, children: Query<&ChildOf>,
player: Query<&PlayerBodyMesh>, player: Query<&PlayerBodyMesh>,
) { ) {
for entity in animation_handles.iter() { for entity in animation_handles.iter() {
for ancestor in parent_query.iter_ancestors(entity) { for ancestor in children.iter_ancestors(entity) {
if player.contains(ancestor) { if player.contains(ancestor) {
commands.entity(entity).insert(PlayerAnimations); commands.entity(entity).insert(PlayerAnimations);
return; return;
@@ -196,18 +195,12 @@ fn toggle_animation(
fn on_update_head( fn on_update_head(
trigger: Trigger<HeadChanged>, trigger: Trigger<HeadChanged>,
mut commands: Commands, mut commands: Commands,
body_mesh: Query<Entity, With<PlayerBodyMesh>>, body_mesh: Single<Entity, With<PlayerBodyMesh>>,
mut player_head: Query<&mut ActiveHead, With<Player>>, mut player: Single<&mut ActiveHead, With<Player>>,
head_db: Res<HeadsDatabase>, head_db: Res<HeadsDatabase>,
audio_assets: Res<AudioAssets>, audio_assets: Res<AudioAssets>,
) { ) {
let Ok(body_mesh) = body_mesh.get_single() else { let body_mesh = *body_mesh;
return;
};
let Ok(mut player) = player_head.get_single_mut() else {
return;
};
player.0 = trigger.0; player.0 = trigger.0;
@@ -215,7 +208,7 @@ fn on_update_head(
commands.trigger(PlaySound::Head(head_str.to_string())); commands.trigger(PlaySound::Head(head_str.to_string()));
commands.entity(body_mesh).despawn_descendants(); commands.entity(body_mesh).despawn_related::<Children>();
commands commands
.entity(body_mesh) .entity(body_mesh)

View File

@@ -1,8 +1,9 @@
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::ecs::{component::ComponentId, world::DeferredWorld}; use bevy::{
use bevy::math::*; ecs::{component::HookContext, world::DeferredWorld},
use bevy::prelude::*; math::*,
use bevy_trenchbroom::class::Target; prelude::*,
};
use bevy_trenchbroom::prelude::*; use bevy_trenchbroom::prelude::*;
use crate::cash::Cash; use crate::cash::Cash;
@@ -17,7 +18,7 @@ use crate::physics_layers::GameLayer;
pub struct SpawnPoint {} pub struct SpawnPoint {}
impl SpawnPoint { impl SpawnPoint {
fn on_add(mut world: DeferredWorld, entity: Entity, _id: ComponentId) { fn on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let Some(assets) = world.get_resource::<GameAssets>() else { let Some(assets) = world.get_resource::<GameAssets>() else {
return; return;
}; };
@@ -117,7 +118,7 @@ pub struct EnemySpawn {
} }
impl EnemySpawn { impl EnemySpawn {
fn on_add(mut world: DeferredWorld, entity: Entity, _id: ComponentId) { fn on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
//TODO: figure out why this crashes if removed //TODO: figure out why this crashes if removed
let Some(_assets) = world.get_resource::<GameAssets>() else { let Some(_assets) = world.get_resource::<GameAssets>() else {
return; return;
@@ -155,7 +156,7 @@ impl EnemySpawn {
pub struct CashSpawn {} pub struct CashSpawn {}
impl CashSpawn { impl CashSpawn {
fn on_add(mut world: DeferredWorld, entity: Entity, _id: ComponentId) { fn on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let Some(assets) = world.get_resource::<GameAssets>() else { let Some(assets) = world.get_resource::<GameAssets>() else {
return; return;
}; };
@@ -167,6 +168,7 @@ impl CashSpawn {
SceneRoot(mesh), SceneRoot(mesh),
Cash, Cash,
Collider::cuboid(2., 3.0, 2.), Collider::cuboid(2., 3.0, 2.),
CollisionEventsEnabled,
Sensor, Sensor,
)); ));
} }

View File

@@ -18,12 +18,12 @@ pub fn plugin(app: &mut App) {
fn face_camera( fn face_camera(
cam_query: Query<&GlobalTransform, With<MainCamera>>, cam_query: Query<&GlobalTransform, With<MainCamera>>,
mut query: Query< mut query: Query<
(&mut Transform, &Parent, &InheritedVisibility), (&mut Transform, &ChildOf, &InheritedVisibility),
(With<Billboard>, Without<MainCamera>), (With<Billboard>, Without<MainCamera>),
>, >,
parent_transform: Query<&GlobalTransform>, parent_transform: Query<&GlobalTransform>,
) { ) {
let Ok(cam_transform) = cam_query.get_single() else { let Ok(cam_transform) = cam_query.single() else {
return; return;
}; };
@@ -32,7 +32,7 @@ fn face_camera(
continue; continue;
} }
let Ok(parent_global) = parent_transform.get(parent.get()) else { let Ok(parent_global) = parent_transform.get(parent.parent()) else {
continue; continue;
}; };
@@ -43,9 +43,9 @@ fn face_camera(
fn face_camera_no_parent( fn face_camera_no_parent(
cam_query: Query<&GlobalTransform, With<MainCamera>>, cam_query: Query<&GlobalTransform, With<MainCamera>>,
mut query: Query<&mut Transform, (With<Billboard>, Without<MainCamera>, Without<Parent>)>, mut query: Query<&mut Transform, (With<Billboard>, Without<MainCamera>, Without<ChildOf>)>,
) { ) {
let Ok(cam_transform) = cam_query.get_single() else { let Ok(cam_transform) = cam_query.single() else {
return; return;
}; };
for mut transform in query.iter_mut() { for mut transform in query.iter_mut() {

View File

@@ -2,5 +2,6 @@ pub mod billboards;
pub mod observers; pub mod observers;
pub mod sprite_3d_animation; pub mod sprite_3d_animation;
pub mod squish_animation; pub mod squish_animation;
pub mod trail;
pub(crate) use observers::global_observer; pub(crate) use observers::global_observer;

View File

@@ -17,7 +17,7 @@ pub fn plugin(app: &mut App) {
fn global_observers( fn global_observers(
mut cmds: Commands, mut cmds: Commands,
query: Query<Entity, (With<Observer>, Without<Parent>, Added<Observer>)>, query: Query<Entity, (With<Observer>, Without<Children>, Added<Observer>)>,
mut root: Local<Option<Entity>>, mut root: Local<Option<Entity>>,
) { ) {
if root.is_none() { if root.is_none() {

View File

@@ -29,7 +29,7 @@ fn animate_sprite(
if atlas.index < length - 1 { if atlas.index < length - 1 {
atlas.index = atlas.index.saturating_add(1) % length; atlas.index = atlas.index.saturating_add(1) % length;
} else { } else {
commands.entity(e).despawn_recursive(); commands.entity(e).despawn();
} }
} }
} }

63
src/utils/trail.rs Normal file
View File

@@ -0,0 +1,63 @@
use bevy::prelude::*;
use crate::GameState;
#[derive(Component)]
pub struct Trail {
points: Vec<Vec3>,
col: LinearRgba,
}
impl Trail {
pub fn new(pos: Vec3, col: LinearRgba) -> Self {
let mut v = Vec::with_capacity(12);
v.push(pos);
Self { points: v, col }
}
pub fn add(&mut self, pos: Vec3) {
if self.points.len() >= self.points.capacity() {
self.points.pop();
}
self.points.insert(0, pos);
}
}
pub fn plugin(app: &mut App) {
app.add_systems(
FixedUpdate,
update_trail.run_if(in_state(GameState::Playing)),
);
}
fn update_trail(
mut query: Query<(&mut Trail, &Gizmo, &GlobalTransform, &ChildOf)>,
global_transform: Query<&GlobalTransform>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) -> Result<(), BevyError> {
for (mut trail, gizmo, pos, parent) in query.iter_mut() {
trail.add(pos.translation());
let parent_transform = global_transform.get(parent.parent())?;
let Some(gizmo) = gizmo_assets.get_mut(gizmo.handle.id()) else {
continue;
};
for window in trail.points.windows(2) {
let [a, b] = window else {
continue;
};
let a = GlobalTransform::from_translation(*a)
.reparented_to(parent_transform)
.translation;
let b = GlobalTransform::from_translation(*b)
.reparented_to(parent_transform)
.translation;
gizmo.line(a, b, trail.col);
}
}
Ok(())
}

View File

@@ -31,10 +31,11 @@ fn setup(mut commands: Commands, query: Query<(Entity, &Children), With<Water>>)
commands.entity(e).insert(( commands.entity(e).insert((
Sensor, Sensor,
ColliderConstructorHierarchy::new(ColliderConstructor::TrimeshFromMesh), CollisionEventsEnabled,
ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh),
)); ));
commands.entity(*child).insert(WaterSensor); commands.entity(child).insert(WaterSensor);
} }
} }

View File

@@ -131,6 +131,16 @@
scale(vector) : "Scale" : "1 1 1" : "" scale(vector) : "Scale" : "1 1 1" : ""
] ]
@BaseClass = visibility
[
visibility(choices) : "Visibility" : "Inherited" : "" =
[
"Inherited" : "Uses the visibility of its parents. If its a root-level entity, it will be visible."
"Hidden" : "Always not rendered, regardless of its parent's visibility."
"Visible" : "Always rendered, regardless of its parent's visibility."
]
]
@SolidClass = water @SolidClass = water
[ [
] ]