Head select (#4)
This commit is contained in:
@@ -63,7 +63,7 @@ fn toggle_animation(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut animation_index: Local<u32>,
|
||||
) {
|
||||
if keys.just_pressed(KeyCode::KeyE) {
|
||||
if keys.just_pressed(KeyCode::KeyT) {
|
||||
for (mut transition, mut player) in &mut transitions {
|
||||
transition
|
||||
.play(
|
||||
|
||||
163
src/heads_ui.rs
Normal file
163
src/heads_ui.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
struct HeadSelector(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
struct HeadImage(pub usize);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct HeadsImages {
|
||||
heads: Vec<Handle<Image>>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct ActiveHeads {
|
||||
heads: [Option<usize>; 5],
|
||||
current_slot: usize,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct HeadChanged(pub usize);
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Startup, setup);
|
||||
app.add_systems(Update, (update, toggle_heads));
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let bg = asset_server.load("ui/head_bg.png");
|
||||
let regular = asset_server.load("ui/head_regular.png");
|
||||
let selector = asset_server.load("ui/selector.png");
|
||||
|
||||
commands
|
||||
.spawn(Node {
|
||||
position_type: PositionType::Absolute,
|
||||
bottom: Val::Px(20.0),
|
||||
right: Val::Px(100.0),
|
||||
height: Val::Px(74.0),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
spawn_head_ui(parent, bg.clone(), regular.clone(), selector.clone(), 0);
|
||||
spawn_head_ui(parent, bg.clone(), regular.clone(), selector.clone(), 1);
|
||||
spawn_head_ui(parent, bg.clone(), regular.clone(), selector.clone(), 2);
|
||||
spawn_head_ui(parent, bg.clone(), regular.clone(), selector.clone(), 3);
|
||||
spawn_head_ui(parent, bg.clone(), regular.clone(), selector.clone(), 4);
|
||||
});
|
||||
|
||||
let head_01 = asset_server.load("ui/heads/angry demonstrator.png");
|
||||
let head_02 = asset_server.load("ui/heads/commando.png");
|
||||
let head_03 = asset_server.load("ui/heads/goblin.png");
|
||||
let head_04 = asset_server.load("ui/heads/highland hammer thrower.png");
|
||||
let head_05 = asset_server.load("ui/heads/french legionaire.png");
|
||||
|
||||
commands.insert_resource(HeadsImages {
|
||||
heads: vec![head_01, head_02, head_03, head_04, head_05],
|
||||
});
|
||||
|
||||
commands.insert_resource(ActiveHeads {
|
||||
heads: [Some(0), Some(1), Some(2), Some(3), Some(4)],
|
||||
current_slot: 0,
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_head_ui(
|
||||
parent: &mut ChildBuilder,
|
||||
bg: Handle<Image>,
|
||||
regular: Handle<Image>,
|
||||
selector: Handle<Image>,
|
||||
head: usize,
|
||||
) {
|
||||
parent
|
||||
.spawn((Node {
|
||||
position_type: PositionType::Relative,
|
||||
justify_content: JustifyContent::Center,
|
||||
width: Val::Px(74.0),
|
||||
..default()
|
||||
},))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(-20.0),
|
||||
..default()
|
||||
},
|
||||
Visibility::Hidden,
|
||||
ImageNode::new(selector),
|
||||
HeadSelector(head),
|
||||
));
|
||||
parent.spawn((
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
..default()
|
||||
},
|
||||
ImageNode::new(bg),
|
||||
));
|
||||
parent.spawn((
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: Val::Px(2.0),
|
||||
right: Val::Px(2.0),
|
||||
top: Val::Px(2.0),
|
||||
bottom: Val::Px(2.0),
|
||||
..default()
|
||||
},
|
||||
ImageNode::default(),
|
||||
Visibility::Hidden,
|
||||
HeadImage(head),
|
||||
));
|
||||
parent.spawn((
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
..default()
|
||||
},
|
||||
ImageNode::new(regular),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
fn update(
|
||||
res: Res<ActiveHeads>,
|
||||
heads_images: Res<HeadsImages>,
|
||||
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
||||
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
||||
) {
|
||||
if res.is_changed() {
|
||||
for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
|
||||
if let Some(head) = res.heads[*head] {
|
||||
*vis = Visibility::Visible;
|
||||
image.image = heads_images.heads[head].clone();
|
||||
} else {
|
||||
*vis = Visibility::Hidden;
|
||||
}
|
||||
}
|
||||
for (HeadSelector(head), mut vis) in head_selector.iter_mut() {
|
||||
*vis = if *head == res.current_slot {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
Visibility::Hidden
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_heads(
|
||||
mut commands: Commands,
|
||||
mut res: ResMut<ActiveHeads>,
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
let changed = if keys.just_pressed(KeyCode::KeyE) {
|
||||
res.current_slot = (res.current_slot + 1) % 5;
|
||||
true
|
||||
} else if keys.just_pressed(KeyCode::KeyQ) {
|
||||
res.current_slot = (res.current_slot + 4) % 5;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if changed {
|
||||
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap()));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod alien;
|
||||
mod camera;
|
||||
mod cash;
|
||||
mod heads_ui;
|
||||
mod player;
|
||||
mod tb_entities;
|
||||
|
||||
@@ -22,6 +23,7 @@ struct DebugVisuals {
|
||||
pub tonemapping: Tonemapping,
|
||||
pub exposure: f32,
|
||||
pub shadows: bool,
|
||||
pub cam_follow: bool,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
@@ -36,6 +38,7 @@ fn main() {
|
||||
tonemapping: Tonemapping::None,
|
||||
exposure: 1.,
|
||||
shadows: true,
|
||||
cam_follow: true,
|
||||
});
|
||||
|
||||
app.add_plugins(DefaultPlugins.set(ImagePlugin {
|
||||
@@ -43,7 +46,7 @@ fn main() {
|
||||
}));
|
||||
|
||||
app.add_plugins(PhysicsPlugins::default());
|
||||
app.add_plugins(PhysicsDebugPlugin::default());
|
||||
// app.add_plugins(PhysicsDebugPlugin::default());
|
||||
app.add_plugins((
|
||||
TnuaControllerPlugin::new(FixedUpdate),
|
||||
TnuaAvian3dPlugin::new(FixedUpdate),
|
||||
@@ -54,6 +57,7 @@ fn main() {
|
||||
app.add_plugins(alien::plugin);
|
||||
app.add_plugins(cash::plugin);
|
||||
app.add_plugins(player::plugin);
|
||||
app.add_plugins(heads_ui::plugin);
|
||||
|
||||
app.insert_resource(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
DebugVisuals,
|
||||
alien::{ALIEN_ASSET_PATH, Animations},
|
||||
camera::GameCameraRig,
|
||||
cash::{Cash, CashCollectEvent},
|
||||
heads_ui::HeadChanged,
|
||||
tb_entities::SpawnPoint,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
@@ -22,6 +24,9 @@ pub struct Player;
|
||||
#[derive(Component, Default)]
|
||||
struct PlayerAnimations;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
struct PlayerHead;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct PlayerSpawned {
|
||||
spawned: bool,
|
||||
@@ -45,6 +50,8 @@ pub fn plugin(app: &mut App) {
|
||||
FixedUpdate,
|
||||
apply_controls.in_set(TnuaUserControlsSystemSet),
|
||||
);
|
||||
|
||||
app.add_observer(updaate_head);
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
@@ -77,10 +84,11 @@ fn spawn(
|
||||
Collider::capsule(1.2, 1.5),
|
||||
LockedAxes::ROTATION_LOCKED,
|
||||
TnuaController::default(),
|
||||
TnuaAvian3dSensorShape(Collider::cylinder(1.0, 0.0)),
|
||||
TnuaAvian3dSensorShape(Collider::cylinder(0.8, 0.0)),
|
||||
))
|
||||
.with_child((
|
||||
Name::from("head"),
|
||||
PlayerHead,
|
||||
Transform::from_translation(Vec3::new(0., -0.5, 0.))
|
||||
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
|
||||
SceneRoot(mesh),
|
||||
@@ -93,8 +101,9 @@ fn spawn(
|
||||
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ALIEN_ASSET_PATH))),
|
||||
));
|
||||
|
||||
commands.spawn(AudioPlayer::new(
|
||||
asset_server.load("sfx/heads/angry demonstrator.ogg"),
|
||||
commands.spawn((
|
||||
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
||||
PlaybackSettings::DESPAWN,
|
||||
));
|
||||
|
||||
player_spawned.spawned = true;
|
||||
@@ -164,7 +173,7 @@ fn apply_controls(
|
||||
|
||||
controller.basis(TnuaBuiltinWalk {
|
||||
// The `desired_velocity` determines how the character will move.
|
||||
desired_velocity: direction.normalize_or_zero() * 10.0,
|
||||
desired_velocity: direction.normalize_or_zero() * 8.0,
|
||||
// The `float_height` must be greater (even if by little) from the distance between the
|
||||
// character's center and the lowest point of its collider.
|
||||
float_height: 3.0,
|
||||
@@ -185,11 +194,19 @@ fn apply_controls(
|
||||
}
|
||||
}
|
||||
|
||||
fn update_camera(player: Query<&Transform, With<Player>>, mut rig: Single<&mut Rig>) {
|
||||
fn update_camera(
|
||||
player: Query<&Transform, With<Player>>,
|
||||
mut rig: Single<&mut Rig>,
|
||||
res: Res<DebugVisuals>,
|
||||
) {
|
||||
let Some(player) = player.iter().next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !res.cam_follow {
|
||||
return;
|
||||
}
|
||||
|
||||
rig.driver_mut::<GameCameraRig>()
|
||||
.set_position_target(player.translation, player.rotation);
|
||||
}
|
||||
@@ -263,3 +280,32 @@ fn toggle_animation(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn updaate_head(
|
||||
trigger: Trigger<HeadChanged>,
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
head: Query<Entity, With<PlayerHead>>,
|
||||
) {
|
||||
let Ok(head) = head.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let head_str = match trigger.0 {
|
||||
0 => "angry demonstrator",
|
||||
1 => "commando",
|
||||
2 => "goblin",
|
||||
3 => "highland hammer thrower",
|
||||
_ => "french legionaire",
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
AudioPlayer::new(asset_server.load(format!("sfx/heads/{}.ogg", head_str))),
|
||||
PlaybackSettings::DESPAWN,
|
||||
));
|
||||
|
||||
let mesh = asset_server
|
||||
.load(GltfAssetLabel::Scene(0).from_asset(format!("models/heads/{}.glb", head_str)));
|
||||
|
||||
commands.entity(head).insert(SceneRoot(mesh));
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl EnemySpawn {
|
||||
Name::from("Enemy"),
|
||||
))
|
||||
.with_child((
|
||||
Transform::from_translation(Vec3::new(0., -0.6, 0.)),
|
||||
Transform::from_translation(Vec3::new(0., -0.6, 0.)).with_scale(Vec3::splat(1.5)),
|
||||
SceneRoot(mesh),
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user