Bevy 0.17 Migration Final PR (#76)

* Get bevy 0.17 compiling and running (#72)

* get bevy 0.17 compiling and running

* try to fix CI breaking from const assertion for client/server features

* fix `bin` -> `lib` for `shared` in CI

* typo

* fix some collider issues (#73)

* Physics/controller improvements (#74)

* trying to fix physics prediction

* fixed prediction desync

* substantial controller improvements

* Finish off main bevy 0.17 migration (#75)

* fix lookdir issues
- airplane moving backwards
- player model facing backwards
- camera was technically backwards the whole time, and player models were facing the right way; camera is now facing forwards
- firing without a target now respects lookdir

* fix aim targeting

* migrate to bevy_trenchbroom 0.10 crates release

* fixed colliders not being adjusted out of worldspace

* predict platforms to stop constant rollbacks while riding them

* fix key/head drop visuals not working

* Fix key/head drop random initial force

* fixed static head drops duplicating

* fix platform velocity inheritance

* fix thrown projectiles not autorotating

* fix inconsistent explosion animations

* update avian3d to 0.4.1

* fix controller snapping to fixed angle upon switching heads

* clean up commented code

* fix broken physics positions

* Clean comments, fix warnings (#77)

* clean comments, fix warnings

* fix missing import

* steamworks 162 libs

* fix mouselook

---------

Co-authored-by: extrawurst <mail@rusticorn.com>
This commit is contained in:
PROMETHIA-27
2025-11-15 09:16:38 -05:00
committed by GitHub
parent ad1b7446e1
commit b83e506a4d
75 changed files with 2514 additions and 1831 deletions

View File

@@ -54,7 +54,7 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
..default()
},
TextColor(HEDZ_GREEN.into()),
TextLayout::new_with_justify(JustifyText::Center),
TextLayout::new_with_justify(Justify::Center),
Node {
position_type: PositionType::Absolute,
top: Val::Px(20.0),

View File

@@ -1,12 +1,13 @@
use avian3d::prelude::{
Collider, ColliderAabb, ColliderDensity, ColliderMarker, ColliderMassProperties,
CollisionEventsEnabled, CollisionLayers, Sensor,
Collider, ColliderAabb, ColliderDensity, ColliderMarker, ColliderOf, ColliderTransform,
CollisionEventsEnabled, CollisionLayers, Position, Rotation, Sensor,
};
use bevy::{
ecs::bundle::BundleFromComponents, prelude::*, scene::SceneInstance, utils::synccell::SyncCell,
ecs::bundle::BundleFromComponents, platform::cell::SyncCell, prelude::*, scene::SceneInstance,
};
use bevy_trenchbroom::geometry::Brushes;
use lightyear::{
frame_interpolation::FrameInterpolate,
link::{LinkConditioner, prelude::*},
netcode::Key,
prelude::{
@@ -48,16 +49,22 @@ pub fn plugin(app: &mut App) {
);
app.add_systems(Last, close_server_processes);
app.add_systems(Update, despawn_absent_map_entities);
app.add_systems(
PreUpdate,
(migrate_remote_entities, ApplyDeferred)
.chain()
.after(ReplicationSystems::Receive),
);
global_observer!(app, on_connecting);
global_observer!(app, on_connection_failed);
global_observer!(app, on_connection_succeeded);
global_observer!(app, temp_give_player_marker);
global_observer!(app, connect_on_local_server_started);
global_observer!(app, received_remote_map_entity);
global_observer!(app, add_visual_interpolation_components);
}
fn close_server_processes(mut app_exit: EventReader<AppExit>) {
fn close_server_processes(mut app_exit: MessageReader<AppExit>) {
if app_exit.read().next().is_some() {
let mut lock = SERVER_PROCESSES.lock();
for mut process in lock.drain(..) {
@@ -114,21 +121,22 @@ fn attempt_connection(mut commands: Commands) -> Result {
},
)?,
UdpIo::default(),
PredictionManager::default(),
InputTimeline(Timeline::from(Input::new(
sync_config,
InputDelayConfig::balanced(),
))),
))
.trigger(Connect);
.trigger(|entity| Connect { entity });
Ok(())
}
fn on_connection_succeeded(
_trigger: Trigger<OnAdd, Connected>,
_trigger: On<Add, Connected>,
state: Res<State<GameState>>,
mut change_state: ResMut<NextState<GameState>>,
mut sender: Single<&mut TriggerSender<ClientEnteredPlaying>>,
mut sender: Single<&mut EventSender<ClientEnteredPlaying>>,
) {
if *state == GameState::Connecting {
change_state.set(GameState::Playing);
@@ -141,21 +149,21 @@ fn on_connection_succeeded(
#[derive(Component)]
struct ClientActive;
fn on_connecting(trigger: Trigger<OnAdd, Connecting>, mut commands: Commands) {
commands.entity(trigger.target()).insert(ClientActive);
fn on_connecting(trigger: On<Add, Connecting>, mut commands: Commands) {
commands.entity(trigger.event().entity).insert(ClientActive);
}
#[derive(Resource)]
struct LocalServerStdout(SyncCell<mpsc::Receiver<String>>);
fn on_connection_failed(
trigger: Trigger<OnAdd, Disconnected>,
trigger: On<Add, Disconnected>,
disconnected: Query<&Disconnected>,
mut commands: Commands,
client_active: Query<&ClientActive>,
mut opened_server: Local<bool>,
) -> Result {
let disconnected = disconnected.get(trigger.target()).unwrap();
let disconnected = disconnected.get(trigger.event().entity).unwrap();
if *opened_server {
panic!(
"failed to connect to local server: {:?}",
@@ -163,7 +171,7 @@ fn on_connection_failed(
);
}
let client = trigger.target();
let client = trigger.event().entity;
if client_active.contains(client) {
commands.entity(client).remove::<ClientActive>();
@@ -223,73 +231,87 @@ fn parse_local_server_stdout(mut commands: Commands, mut stdout: ResMut<LocalSer
}
fn connect_on_local_server_started(
_trigger: Trigger<LocalServerStarted>,
_trigger: On<LocalServerStarted>,
state: Res<State<GameState>>,
mut commands: Commands,
client: Single<Entity, With<Client>>,
) {
if *state == GameState::Connecting {
commands.entity(*client).trigger(Connect);
commands
.entity(*client)
.trigger(|entity| Connect { entity });
}
}
fn temp_give_player_marker(trigger: Trigger<OnAdd, Player>, mut commands: Commands) {
fn temp_give_player_marker(trigger: On<Add, Player>, mut commands: Commands) {
commands
.entity(trigger.target())
.entity(trigger.event().entity)
.insert(InputMarker::<ControlState>::default());
}
fn received_remote_map_entity(
trigger: Trigger<OnAdd, TbMapEntityId>,
world: &mut World,
mut child_buffer: Local<Vec<Entity>>,
#[allow(clippy::type_complexity)]
fn migrate_remote_entities(
query: Query<(Entity, &TbMapEntityId), (Added<TbMapEntityId>, With<Replicated>)>,
children: Query<&Children>,
mut commands: Commands,
mut mapping: ResMut<TbMapEntityMapping>,
) {
let serverside = trigger.target();
if world.get::<Replicated>(serverside).is_none() {
return;
for (serverside, tb_id) in query.iter() {
received_remote_map_entity(serverside, tb_id.id, &children, &mut mapping, &mut commands);
}
}
let id = *world.get::<TbMapEntityId>(serverside).unwrap();
let Some(clientside) = world.resource_mut::<TbMapEntityMapping>().0.remove(&id.id) else {
warn!("received unknown MapEntity ID `{id:?}`");
fn received_remote_map_entity(
serverside: Entity,
tb_id: u64,
children: &Query<&Children>,
mapping: &mut TbMapEntityMapping,
commands: &mut Commands,
) {
let Some(clientside) = mapping.0.remove(&tb_id) else {
warn!("received unknown MapEntity ID `{tb_id:?}`");
return;
};
// cannot just use `take` directly with a bundle because then any missing component would cause
// the entire bundle to fail
move_component::<Brushes>(world, clientside, serverside);
move_component::<Brushes>(commands, clientside, serverside);
move_component::<(
Collider,
ColliderAabb,
ColliderDensity,
ColliderMarker,
ColliderMassProperties,
CollisionLayers,
)>(world, clientside, serverside);
move_component::<CollisionEventsEnabled>(world, clientside, serverside);
move_component::<Platform>(world, clientside, serverside);
move_component::<PlatformTarget>(world, clientside, serverside);
move_component::<SceneInstance>(world, clientside, serverside);
move_component::<SceneRoot>(world, clientside, serverside);
move_component::<Sensor>(world, clientside, serverside);
)>(commands, clientside, serverside);
move_component::<ColliderOf>(commands, clientside, serverside);
move_component::<ColliderTransform>(commands, clientside, serverside);
move_component::<CollisionEventsEnabled>(commands, clientside, serverside);
move_component::<Platform>(commands, clientside, serverside);
move_component::<PlatformTarget>(commands, clientside, serverside);
move_component::<SceneInstance>(commands, clientside, serverside);
move_component::<SceneRoot>(commands, clientside, serverside);
move_component::<Sensor>(commands, clientside, serverside);
if let Some(children) = world.get::<Children>(clientside) {
child_buffer.extend(children.iter());
for child in child_buffer.drain(..) {
world.entity_mut(child).insert(ChildOf(serverside));
if let Ok(children) = children.get(clientside) {
for child in children.iter() {
commands.entity(child).insert(ChildOf(serverside));
}
}
world.entity_mut(clientside).despawn();
commands.entity(clientside).despawn();
}
fn move_component<B: Bundle + BundleFromComponents>(world: &mut World, from: Entity, to: Entity) {
let comp = world.entity_mut(from).take::<B>();
if let Some(comp) = comp {
world.entity_mut(to).insert(comp);
}
fn move_component<B: Bundle + BundleFromComponents>(
commands: &mut Commands,
from: Entity,
to: Entity,
) {
commands.queue(move |world: &mut World| {
let comp = world.entity_mut(from).take::<B>();
if let Some(comp) = comp {
world.entity_mut(to).insert(comp);
}
});
}
fn despawn_absent_map_entities(
@@ -309,3 +331,22 @@ fn despawn_absent_map_entities(
}
}
}
fn add_visual_interpolation_components(trigger: On<Add, Predicted>, mut commands: Commands) {
commands.entity(trigger.entity).insert((
FrameInterpolate::<Position> {
// We must trigger change detection on visual interpolation
// to make sure that child entities (sprites, meshes, text)
// are also interpolated
trigger_change_detection: true,
..default()
},
FrameInterpolate::<Rotation> {
// We must trigger change detection on visual interpolation
// to make sure that child entities (sprites, meshes, text)
// are also interpolated
trigger_change_detection: true,
..default()
},
));
}

View File

@@ -0,0 +1,54 @@
use bevy::prelude::*;
use lightyear::prelude::input::native::ActionState;
use shared::{
GameState,
control::{ControlState, ControllerSet, LookDirMovement},
player::PlayerBodyMesh,
};
use std::f32::consts::PI;
pub fn plugin(app: &mut App) {
app.add_systems(
FixedUpdate,
rotate_rig
.before(shared::control::controller_flying::apply_controls)
.in_set(ControllerSet::ApplyControlsFly)
.run_if(in_state(GameState::Playing)),
);
}
fn rotate_rig(
actions: Query<&ActionState<ControlState>>,
look_dir: Res<LookDirMovement>,
mut player: Query<(&mut Transform, &ChildOf), With<PlayerBodyMesh>>,
) {
for (mut rig_transform, child_of) in player.iter_mut() {
let controls = actions.get(child_of.parent()).unwrap();
if controls.view_mode {
continue;
}
let look_dir = look_dir.0;
// todo: Make consistent with the running controller
let sensitivity = 0.001;
let max_pitch = 35.0 * PI / 180.0;
let min_pitch = -25.0 * PI / 180.0;
rig_transform.rotate_y(look_dir.x * -sensitivity);
let euler_rot = rig_transform.rotation.to_euler(EulerRot::YXZ);
let yaw = euler_rot.0;
let pitch = euler_rot.1 + look_dir.y * -sensitivity;
let pitch_clamped = pitch.clamp(min_pitch, max_pitch);
rig_transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch_clamped, 0.0);
// The following can be used to limit the amount of rotation per frame
// let target_rotation = rig_transform.rotation
// * Quat::from_rotation_x(controls.keyboard_state.look_dir.y * sensitivity);
// let clamped_rotation = rig_transform.rotation.rotate_towards(target_rotation, 0.01);
// rig_transform.rotation = clamped_rotation;
}
}

View File

@@ -1,4 +1,4 @@
use super::Controls;
use super::{ControlState, Controls};
use crate::{GameState, control::CharacterInputEnabled};
use bevy::{
input::{
@@ -9,10 +9,13 @@ use bevy::{
prelude::*,
};
use lightyear::{
input::client::InputSet,
input::client::InputSystems,
prelude::input::native::{ActionState, InputMarker},
};
use shared::control::{ControlState, ControllerSet};
use shared::{
control::{ControllerSet, LookDirMovement},
player::PlayerBodyMesh,
};
use std::{collections::HashMap, hash::Hash};
pub fn plugin(app: &mut App) {
@@ -26,18 +29,20 @@ pub fn plugin(app: &mut App) {
app.add_systems(
FixedPreUpdate,
(
reset_lookdir,
gamepad_controls,
keyboard_controls,
mouse_rotate,
mouse_click,
gamepad_connections.run_if(on_event::<GamepadEvent>),
gamepad_connections.run_if(on_message::<GamepadEvent>),
combine_controls,
get_lookdir,
clear_keyboard_just,
clear_gamepad_just,
)
.chain()
.in_set(ControllerSet::CollectInputs)
.before(InputSet::WriteClientInputs)
.before(InputSystems::WriteClientInputs)
.run_if(
in_state(GameState::Playing)
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
@@ -45,7 +50,7 @@ pub fn plugin(app: &mut App) {
)
.add_systems(
FixedPreUpdate,
buffer_inputs.in_set(InputSet::WriteClientInputs),
buffer_inputs.in_set(InputSystems::WriteClientInputs),
);
app.add_systems(
@@ -63,6 +68,10 @@ fn buffer_inputs(
player.0 = *controls;
}
fn reset_lookdir(mut look_dir: ResMut<LookDirMovement>) {
look_dir.0 = Vec2::ZERO;
}
/// Reset character inputs to default when character input is disabled.
fn reset_control_state_on_disable(
state: Res<CharacterInputEnabled>,
@@ -71,7 +80,10 @@ fn reset_control_state_on_disable(
) {
if state.is_changed() && matches!(*state, CharacterInputEnabled::Off) {
*controls = Controls::default();
*control_state = ControlState::default();
*control_state = ControlState {
look_dir: control_state.look_dir,
..default()
};
}
}
@@ -165,7 +177,7 @@ fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<Contr
let keyboard = controls.keyboard_state;
let gamepad = controls.gamepad_state.unwrap_or_default();
combined_controls.look_dir = gamepad.look_dir + keyboard.look_dir;
combined_controls.look_dir = Dir3::NEG_Z;
combined_controls.move_dir = gamepad.move_dir + keyboard.move_dir;
combined_controls.jump = gamepad.jump | keyboard.jump;
combined_controls.view_mode = gamepad.view_mode | keyboard.view_mode;
@@ -180,6 +192,17 @@ fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<Contr
combined_controls.cash_heal = gamepad.cash_heal | keyboard.cash_heal;
}
fn get_lookdir(
mut controls: ResMut<ControlState>,
rig_transform: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
) {
controls.look_dir = if let Some(ref rig_transform) = rig_transform {
rig_transform.forward()
} else {
Dir3::NEG_Z
};
}
/// Applies a square deadzone to a Vec2
fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
Vec2::new(
@@ -192,6 +215,7 @@ fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
fn gamepad_controls(
gamepads: Query<(Entity, &Gamepad, &InputStateCache<GamepadButton>)>,
mut controls: ResMut<Controls>,
mut look_dir: ResMut<LookDirMovement>,
) {
let Some((_e, gamepad, cache)) = gamepads.iter().next() else {
if controls.gamepad_state.is_some() {
@@ -208,7 +232,7 @@ fn gamepad_controls(
.unwrap_or_default();
// 8BitDo Ultimate wireless Controller for PC
let look_dir = if gamepad.vendor_id() == Some(11720) && gamepad.product_id() == Some(12306) {
look_dir.0 = if gamepad.vendor_id() == Some(11720) && gamepad.product_id() == Some(12306) {
const EPSILON: f32 = 0.015;
Vec2::new(
if rotate < 0.5 - EPSILON {
@@ -224,9 +248,11 @@ fn gamepad_controls(
deadzone_square(gamepad.right_stick(), deadzone_right_stick) * 40.
};
let move_dir = deadzone_square(gamepad.left_stick(), deadzone_left_stick);
let state = ControlState {
move_dir: deadzone_square(gamepad.left_stick(), deadzone_left_stick),
look_dir,
move_dir,
look_dir: Dir3::NEG_Z,
jump: gamepad.pressed(GamepadButton::South),
view_mode: gamepad.pressed(GamepadButton::LeftTrigger2),
trigger: gamepad.pressed(GamepadButton::RightTrigger2),
@@ -251,11 +277,9 @@ fn gamepad_controls(
}
/// Collect mouse movement input
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
controls.keyboard_state.look_dir = Vec2::ZERO;
fn mouse_rotate(mut mouse: MessageReader<MouseMotion>, mut look_dir: ResMut<LookDirMovement>) {
for ev in mouse.read() {
controls.keyboard_state.look_dir += ev.delta;
look_dir.0 += ev.delta;
}
}
@@ -292,7 +316,7 @@ fn keyboard_controls(
}
/// Collect mouse button input when pressed
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
fn mouse_click(mut events: MessageReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
controls.keyboard_state.just_triggered = false;
for ev in events.read() {
@@ -318,7 +342,7 @@ fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<C
}
/// Receive gamepad connections and disconnections
fn gamepad_connections(mut evr_gamepad: EventReader<GamepadEvent>) {
fn gamepad_connections(mut evr_gamepad: MessageReader<GamepadEvent>) {
for ev in evr_gamepad.read() {
if let GamepadEvent::Connection(connection) = ev {
match &connection.connection {

View File

@@ -2,6 +2,7 @@ use crate::GameState;
use bevy::prelude::*;
use shared::control::{ControlState, ControllerSet};
mod controller_flying;
pub mod controls;
#[derive(Resource, Debug, Default)]
@@ -19,7 +20,7 @@ pub enum CharacterInputEnabled {
pub fn plugin(app: &mut App) {
app.insert_resource(CharacterInputEnabled::On);
app.add_plugins(controls::plugin);
app.add_plugins((controller_flying::plugin, controls::plugin));
app.configure_sets(
FixedPreUpdate,

View File

@@ -28,7 +28,7 @@ fn setup(mut commands: Commands) {
font_size: 12.0,
..default()
},
TextLayout::new_with_justify(JustifyText::Left),
TextLayout::new_with_justify(Justify::Left),
Node {
position_type: PositionType::Absolute,
top: Val::Px(5.0),

View File

@@ -67,16 +67,16 @@ fn on_added(
}
fn on_removed(
trigger: Trigger<OnRemove, Healing>,
trigger: On<Remove, Healing>,
mut commands: Commands,
effects: Query<&HasHealingEffects>,
) {
let Ok(has_effects) = effects.get(trigger.target()) else {
let Ok(has_effects) = effects.get(trigger.event().entity) else {
return;
};
commands.entity(has_effects.effects).try_despawn();
commands
.entity(trigger.target())
.entity(trigger.event().entity)
.remove::<HasHealingEffects>();
}

View File

@@ -19,10 +19,13 @@ use bevy::{
use bevy_common_assets::ron::RonAssetPlugin;
use bevy_sprite3d::Sprite3dPlugin;
use bevy_trenchbroom::prelude::*;
use bevy_ui_gradients::UiGradientsPlugin;
use bevy_trenchbroom_avian::AvianPhysicsBackend;
use camera::MainCamera;
use heads_database::HeadDatabaseAsset;
use lightyear::prelude::client::ClientPlugins;
use lightyear::{
avian3d::plugin::LightyearAvianPlugin, frame_interpolation::FrameInterpolationPlugin,
prelude::client::ClientPlugins,
};
use loading_assets::AudioAssets;
use shared::*;
use std::time::Duration;
@@ -55,6 +58,7 @@ fn main() {
level: bevy::log::Level::INFO,
// provide custom log layer to receive logging events
custom_layer: bevy_debug_log::log_capture_layer,
..default()
}),
);
@@ -64,22 +68,37 @@ fn main() {
bevy_debug_log::LogViewerPlugin::default()
.auto_open_threshold(bevy::log::tracing::level_filters::LevelFilter::OFF),
);
app.add_plugins(PhysicsPlugins::default());
app.add_plugins(
PhysicsPlugins::default()
.build()
// TODO: This plugin is *not* disabled on the server. This is to solve a bug related to collider transform inheritance. See the
// LightyearAvianPlugin below.
// Periwink is looking into it at the moment. This **must** be disabled on the client, or positions break for NPCs and some other things.
.disable::<PhysicsTransformPlugin>()
// FrameInterpolation handles interpolating Position and Rotation
.disable::<PhysicsInterpolationPlugin>(),
);
// TODO: This plugin is *not* inserted on the server. This is to solve a bug related to collider transform inheritance. See the
// `.disable::<PhysicsTransformPlugin>()` above.
// Periwink is looking into it at the moment. This **must** be inserted on the client, or positions break for NPCs and some other things.
app.add_plugins(LightyearAvianPlugin::default());
app.add_plugins(FrameInterpolationPlugin::<Position>::default());
app.add_plugins(FrameInterpolationPlugin::<Rotation>::default());
app.add_plugins(ClientPlugins {
tick_duration: Duration::from_secs_f64(1.0 / 60.0),
});
app.add_plugins(Sprite3dPlugin);
app.add_plugins(TrenchBroomPlugins(
TrenchBroomConfig::new("hedz").icon(None),
TrenchBroomConfig::new("hedz")
.icon(None)
.default_solid_spawn_hooks(|| SpawnHooks::new().convex_collider()),
));
app.add_plugins(UiGradientsPlugin);
app.add_plugins(TrenchBroomPhysicsPlugin::new(AvianPhysicsBackend));
app.add_plugins(RonAssetPlugin::<HeadDatabaseAsset>::new(&["headsdb.ron"]));
#[cfg(feature = "dbg")]
{
app.add_plugins(bevy_inspector_egui::bevy_egui::EguiPlugin {
enable_multipass_for_primary_context: true,
});
app.add_plugins(bevy_inspector_egui::bevy_egui::EguiPlugin::default());
app.add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::new());
app.add_plugins(PhysicsDebugPlugin::default());

View File

@@ -82,7 +82,7 @@ pub enum PlayerAssignmentState {
pub struct LocalPlayer;
fn on_update_head_mesh(
trigger: Trigger<ClientHeadChanged>,
trigger: On<ClientHeadChanged>,
mut commands: Commands,
body_mesh: Single<(Entity, &Children), With<PlayerBodyMesh>>,
head_db: Res<HeadsDatabase>,

View File

@@ -7,7 +7,7 @@ pub fn plugin(app: &mut App) {
}
fn on_spawn_sounds(
trigger: Trigger<PlaySound>,
trigger: On<PlaySound>,
mut commands: Commands,
// settings: SettingsRead,
assets: Res<AudioAssets>,

View File

@@ -13,7 +13,7 @@ pub fn plugin(app: &mut App) {
);
}
fn log_steam_events(mut events: EventReader<SteamworksEvent>) {
fn log_steam_events(mut events: MessageReader<SteamworksEvent>) {
for e in events.read() {
info!("steam ev: {:?}", e);
}

View File

@@ -69,7 +69,7 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
..default()
},
BackgroundColor(Color::linear_rgba(0., 0., 0., 0.6)),
StateScoped(PauseMenuState::Open),
DespawnOnExit(PauseMenuState::Open),
children![
spawn_progress(ProgressBar::Music, 100, assets.font.clone()),
spawn_progress(ProgressBar::Sound, 80, assets.font.clone())
@@ -91,7 +91,7 @@ fn spawn_progress(bar: ProgressBar, value: u8, font: Handle<Font>) -> impl Bundl
},
BackgroundColor(BLACK.into()),
BorderRadius::all(Val::Px(100.)),
BorderColor(HEDZ_PURPLE.into()),
BorderColor::all(HEDZ_PURPLE),
BoxShadow::new(
BLACK.into(),
Val::Px(2.),
@@ -117,7 +117,7 @@ fn spawn_progress(bar: ProgressBar, value: u8, font: Handle<Font>) -> impl Bundl
..Default::default()
},
TextColor(HEDZ_GREEN.into()),
TextLayout::new_with_justify(JustifyText::Left),
TextLayout::new_with_justify(Justify::Left),
),
(
Node {
@@ -131,7 +131,7 @@ fn spawn_progress(bar: ProgressBar, value: u8, font: Handle<Font>) -> impl Bundl
..Default::default()
},
TextColor(HEDZ_GREEN.into()),
TextLayout::new_with_justify(JustifyText::Left).with_no_wrap(),
TextLayout::new_with_justify(Justify::Left).with_no_wrap(),
),
(
Text::new(format!("{value}",)),
@@ -141,7 +141,7 @@ fn spawn_progress(bar: ProgressBar, value: u8, font: Handle<Font>) -> impl Bundl
..Default::default()
},
TextColor(HEDZ_GREEN.into()),
TextLayout::new_with_justify(JustifyText::Left).with_no_wrap(),
TextLayout::new_with_justify(Justify::Left).with_no_wrap(),
),
(
Node {
@@ -155,7 +155,7 @@ fn spawn_progress(bar: ProgressBar, value: u8, font: Handle<Font>) -> impl Bundl
..Default::default()
},
TextColor(HEDZ_GREEN.into()),
TextLayout::new_with_justify(JustifyText::Left).with_no_wrap(),
TextLayout::new_with_justify(Justify::Left).with_no_wrap(),
)
],
)
@@ -176,11 +176,11 @@ fn selection_changed(
) {
if state.is_changed() {
for (mut border, bar) in query.iter_mut() {
border.0 = if *bar == state.0 {
HEDZ_GREEN.into()
*border = BorderColor::all(if *bar == state.0 {
HEDZ_GREEN
} else {
HEDZ_PURPLE.into()
}
HEDZ_PURPLE
})
}
}
}