beaming in characters
This commit is contained in:
BIN
assets/models/beaming.glb
Normal file
BIN
assets/models/beaming.glb
Normal file
Binary file not shown.
BIN
assets/textures/beaming.png
Normal file
BIN
assets/textures/beaming.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 463 B |
@@ -106,7 +106,7 @@ fn update(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.insert((
|
.insert((
|
||||||
Billboard,
|
Billboard::All,
|
||||||
Transform::from_translation(first_hit.point1),
|
Transform::from_translation(first_hit.point1),
|
||||||
NotShadowCaster,
|
NotShadowCaster,
|
||||||
AnimationTimer::new(Timer::from_seconds(0.005, TimerMode::Repeating)),
|
AnimationTimer::new(Timer::from_seconds(0.005, TimerMode::Repeating)),
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ fn shot_collision(
|
|||||||
.bundle_with_atlas(&mut sprite_params, texture_atlas),
|
.bundle_with_atlas(&mut sprite_params, texture_atlas),
|
||||||
)
|
)
|
||||||
.insert((
|
.insert((
|
||||||
Billboard,
|
Billboard::All,
|
||||||
Transform::from_translation(shot_pos),
|
Transform::from_translation(shot_pos),
|
||||||
NotShadowCaster,
|
NotShadowCaster,
|
||||||
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),
|
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ fn shot_collision(
|
|||||||
.bundle_with_atlas(&mut sprite_params, texture_atlas),
|
.bundle_with_atlas(&mut sprite_params, texture_atlas),
|
||||||
)
|
)
|
||||||
.insert((
|
.insert((
|
||||||
Billboard,
|
Billboard::All,
|
||||||
Transform::from_translation(shot_pos),
|
Transform::from_translation(shot_pos),
|
||||||
NotShadowCaster,
|
NotShadowCaster,
|
||||||
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),
|
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ fn shot_collision(
|
|||||||
.bundle_with_atlas(&mut sprite_params, texture_atlas),
|
.bundle_with_atlas(&mut sprite_params, texture_atlas),
|
||||||
)
|
)
|
||||||
.insert((
|
.insert((
|
||||||
Billboard,
|
Billboard::All,
|
||||||
Transform::from_translation(shot_pos),
|
Transform::from_translation(shot_pos),
|
||||||
NotShadowCaster,
|
NotShadowCaster,
|
||||||
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),
|
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ fn shot_collision(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.insert((
|
.insert((
|
||||||
Billboard,
|
Billboard::All,
|
||||||
Transform::from_translation(shot_pos),
|
Transform::from_translation(shot_pos),
|
||||||
NotShadowCaster,
|
NotShadowCaster,
|
||||||
AnimationTimer::new(Timer::from_seconds(0.02, TimerMode::Repeating)),
|
AnimationTimer::new(Timer::from_seconds(0.02, TimerMode::Repeating)),
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ fn marker_event(
|
|||||||
let id = commands
|
let id = commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Name::new("aim-marker"),
|
Name::new("aim-marker"),
|
||||||
Billboard,
|
Billboard::All,
|
||||||
TargetMarker,
|
TargetMarker,
|
||||||
Transform::default(),
|
Transform::default(),
|
||||||
Sprite3dBuilder {
|
Sprite3dBuilder {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ fn on_head_drop(
|
|||||||
|| drop.impulse,
|
|| drop.impulse,
|
||||||
)
|
)
|
||||||
.with_child((
|
.with_child((
|
||||||
Billboard,
|
Billboard::All,
|
||||||
SquishAnimation(2.6),
|
SquishAnimation(2.6),
|
||||||
SceneRoot(mesh.scenes[0].clone()),
|
SceneRoot(mesh.scenes[0].clone()),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ fn update_effects(
|
|||||||
cmds.entity(e).with_child((
|
cmds.entity(e).with_child((
|
||||||
Name::new("heal-particle"),
|
Name::new("heal-particle"),
|
||||||
SceneRoot(assets.mesh_heal_particle.clone()),
|
SceneRoot(assets.mesh_heal_particle.clone()),
|
||||||
Billboard,
|
Billboard::All,
|
||||||
Transform::from_translation(start_pos),
|
Transform::from_translation(start_pos),
|
||||||
HealParticle {
|
HealParticle {
|
||||||
start_scale,
|
start_scale,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ fn on_spawn_key(trigger: Trigger<KeySpawn>, mut commands: Commands, assets: Res<
|
|||||||
Restitution::new(0.6),
|
Restitution::new(0.6),
|
||||||
Children::spawn((
|
Children::spawn((
|
||||||
Spawn((
|
Spawn((
|
||||||
Billboard,
|
Billboard::All,
|
||||||
SquishAnimation(2.6),
|
SquishAnimation(2.6),
|
||||||
SceneRoot(assets.mesh_key.clone()),
|
SceneRoot(assets.mesh_key.clone()),
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ pub struct GameAssets {
|
|||||||
#[asset(path = "models/medic_particle.glb#Scene0")]
|
#[asset(path = "models/medic_particle.glb#Scene0")]
|
||||||
pub mesh_heal_particle: Handle<Scene>,
|
pub mesh_heal_particle: Handle<Scene>,
|
||||||
|
|
||||||
|
#[asset(path = "models/beaming.glb#Scene0")]
|
||||||
|
pub beaming: Handle<Scene>,
|
||||||
|
|
||||||
#[asset(path = "models/projectiles", collection(mapped, typed))]
|
#[asset(path = "models/projectiles", collection(mapped, typed))]
|
||||||
pub projectiles: HashMap<AssetFileName, Handle<Gltf>>,
|
pub projectiles: HashMap<AssetFileName, Handle<Gltf>>,
|
||||||
|
|
||||||
|
|||||||
50
src/npc.rs
50
src/npc.rs
@@ -9,10 +9,12 @@ use crate::{
|
|||||||
heads_database::HeadsDatabase,
|
heads_database::HeadsDatabase,
|
||||||
hitpoints::{Hitpoints, Kill},
|
hitpoints::{Hitpoints, Kill},
|
||||||
keys::KeySpawn,
|
keys::KeySpawn,
|
||||||
|
loading_assets::GameAssets,
|
||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
tb_entities::EnemySpawn,
|
tb_entities::EnemySpawn,
|
||||||
|
utils::billboards::Billboard,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::{pbr::NotShadowCaster, prelude::*};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Component, Reflect)]
|
#[derive(Component, Reflect)]
|
||||||
@@ -25,14 +27,23 @@ struct NpcSpawning {
|
|||||||
spawn_index: u32,
|
spawn_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct SpawningBeam(pub f32);
|
||||||
|
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
struct OnCheckSpawns;
|
struct OnCheckSpawns;
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct SpawnCharacter(pub Vec3);
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.init_resource::<NpcSpawning>();
|
app.init_resource::<NpcSpawning>();
|
||||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||||
|
app.add_systems(Update, update_beams.run_if(in_state(GameState::Playing)));
|
||||||
|
|
||||||
global_observer!(app, on_spawn_check);
|
global_observer!(app, on_spawn_check);
|
||||||
|
global_observer!(app, on_spawn);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
fn setup(mut commands: Commands) {
|
||||||
@@ -43,7 +54,7 @@ fn setup(mut commands: Commands) {
|
|||||||
fn on_spawn_check(
|
fn on_spawn_check(
|
||||||
_trigger: Trigger<OnCheckSpawns>,
|
_trigger: Trigger<OnCheckSpawns>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
query: Query<(Entity, &EnemySpawn), Without<Npc>>,
|
query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>,
|
||||||
heads_db: Res<HeadsDatabase>,
|
heads_db: Res<HeadsDatabase>,
|
||||||
spawning: Res<NpcSpawning>,
|
spawning: Res<NpcSpawning>,
|
||||||
) {
|
) {
|
||||||
@@ -53,7 +64,7 @@ fn on_spawn_check(
|
|||||||
names.insert(heads_db.head_key(i).to_string(), i);
|
names.insert(heads_db.head_key(i).to_string(), i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (e, spawn) in query.iter() {
|
for (e, spawn, transform) in query.iter() {
|
||||||
if let Some(order) = spawn.spawn_order {
|
if let Some(order) = spawn.spawn_order {
|
||||||
if order > spawning.spawn_index {
|
if order > spawning.spawn_index {
|
||||||
continue;
|
continue;
|
||||||
@@ -79,6 +90,7 @@ fn on_spawn_check(
|
|||||||
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
|
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
|
||||||
.observe(on_kill);
|
.observe(on_kill);
|
||||||
|
|
||||||
|
commands.trigger(SpawnCharacter(transform.translation));
|
||||||
commands.trigger(PlaySound::Beaming);
|
commands.trigger(PlaySound::Beaming);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,3 +119,35 @@ fn on_kill(
|
|||||||
commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
|
commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_spawn(
|
||||||
|
trigger: Trigger<SpawnCharacter>,
|
||||||
|
mut commands: Commands,
|
||||||
|
assets: Res<GameAssets>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
commands.spawn((
|
||||||
|
Transform::from_translation(trigger.event().0 + Vec3::new(0., -2., 0.))
|
||||||
|
.with_scale(Vec3::new(1., 40., 1.)),
|
||||||
|
Billboard::XZ,
|
||||||
|
NotShadowCaster,
|
||||||
|
SpawningBeam(time.elapsed_secs()),
|
||||||
|
SceneRoot(assets.beaming.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_beams(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut query: Query<(Entity, &SpawningBeam, &mut Transform)>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
for (entity, beam, mut transform) in query.iter_mut() {
|
||||||
|
let age = time.elapsed_secs() - beam.0;
|
||||||
|
|
||||||
|
transform.scale.x = age.sin() * 2.;
|
||||||
|
|
||||||
|
if age > 3. {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use crate::{
|
|||||||
heads_database::{HeadControls, HeadsDatabase},
|
heads_database::{HeadControls, HeadsDatabase},
|
||||||
hitpoints::{Hitpoints, Kill},
|
hitpoints::{Hitpoints, Kill},
|
||||||
loading_assets::AudioAssets,
|
loading_assets::AudioAssets,
|
||||||
|
npc::SpawnCharacter,
|
||||||
physics_layers::GameLayer,
|
physics_layers::GameLayer,
|
||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
tb_entities::SpawnPoint,
|
tb_entities::SpawnPoint,
|
||||||
@@ -98,6 +99,8 @@ fn spawn(
|
|||||||
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
||||||
PlaybackSettings::DESPAWN,
|
PlaybackSettings::DESPAWN,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
commands.trigger(SpawnCharacter(transform.translation));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_kill(
|
fn on_kill(
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ use crate::camera::MainCamera;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_sprite3d::Sprite3dPlugin;
|
use bevy_sprite3d::Sprite3dPlugin;
|
||||||
|
|
||||||
#[derive(Component, Reflect)]
|
#[derive(Component, Reflect, Default, PartialEq, Eq)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct Billboard;
|
pub enum Billboard {
|
||||||
|
#[default]
|
||||||
|
All,
|
||||||
|
XZ,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
if !app.is_plugin_added::<Sprite3dPlugin>() {
|
if !app.is_plugin_added::<Sprite3dPlugin>() {
|
||||||
@@ -18,8 +22,8 @@ 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, &ChildOf, &InheritedVisibility),
|
(&mut Transform, &ChildOf, &InheritedVisibility, &Billboard),
|
||||||
(With<Billboard>, Without<MainCamera>),
|
Without<MainCamera>,
|
||||||
>,
|
>,
|
||||||
parent_transform: Query<&GlobalTransform>,
|
parent_transform: Query<&GlobalTransform>,
|
||||||
) {
|
) {
|
||||||
@@ -27,7 +31,7 @@ fn face_camera(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (mut transform, parent, visible) in query.iter_mut() {
|
for (mut transform, parent, visible, billboard) in query.iter_mut() {
|
||||||
if !matches!(*visible, InheritedVisibility::VISIBLE) {
|
if !matches!(*visible, InheritedVisibility::VISIBLE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -37,18 +41,34 @@ fn face_camera(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let target = cam_transform.reparented_to(parent_global);
|
let target = cam_transform.reparented_to(parent_global);
|
||||||
transform.look_at(target.translation, Vec3::Y);
|
|
||||||
|
let target = match *billboard {
|
||||||
|
Billboard::All => target.translation,
|
||||||
|
Billboard::XZ => Vec3::new(
|
||||||
|
target.translation.x,
|
||||||
|
transform.translation.y,
|
||||||
|
target.translation.z,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
transform.look_at(target, Vec3::Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<ChildOf>)>,
|
mut query: Query<(&mut Transform, &Billboard), (Without<MainCamera>, Without<ChildOf>)>,
|
||||||
) {
|
) {
|
||||||
let Ok(cam_transform) = cam_query.single() else {
|
let Ok(cam_transform) = cam_query.single() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
for mut transform in query.iter_mut() {
|
for (mut transform, billboard) in query.iter_mut() {
|
||||||
transform.look_at(cam_transform.translation(), Vec3::Y);
|
let target = cam_transform.translation();
|
||||||
|
let target = match *billboard {
|
||||||
|
Billboard::All => cam_transform.translation(),
|
||||||
|
Billboard::XZ => Vec3::new(target.x, transform.translation.y, target.z),
|
||||||
|
};
|
||||||
|
|
||||||
|
transform.look_at(target, Vec3::Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user