beaming in characters

This commit is contained in:
2025-06-23 00:05:24 +02:00
parent 241b96fcc7
commit 7700d4bef9
15 changed files with 91 additions and 21 deletions

BIN
assets/models/beaming.glb Normal file

Binary file not shown.

BIN
assets/textures/beaming.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

View File

@@ -106,7 +106,7 @@ fn update(
),
)
.insert((
Billboard,
Billboard::All,
Transform::from_translation(first_hit.point1),
NotShadowCaster,
AnimationTimer::new(Timer::from_seconds(0.005, TimerMode::Repeating)),

View File

@@ -195,7 +195,7 @@ fn shot_collision(
.bundle_with_atlas(&mut sprite_params, texture_atlas),
)
.insert((
Billboard,
Billboard::All,
Transform::from_translation(shot_pos),
NotShadowCaster,
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),

View File

@@ -191,7 +191,7 @@ fn shot_collision(
.bundle_with_atlas(&mut sprite_params, texture_atlas),
)
.insert((
Billboard,
Billboard::All,
Transform::from_translation(shot_pos),
NotShadowCaster,
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),

View File

@@ -192,7 +192,7 @@ fn shot_collision(
.bundle_with_atlas(&mut sprite_params, texture_atlas),
)
.insert((
Billboard,
Billboard::All,
Transform::from_translation(shot_pos),
NotShadowCaster,
AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),

View File

@@ -174,7 +174,7 @@ fn shot_collision(
),
)
.insert((
Billboard,
Billboard::All,
Transform::from_translation(shot_pos),
NotShadowCaster,
AnimationTimer::new(Timer::from_seconds(0.02, TimerMode::Repeating)),

View File

@@ -42,7 +42,7 @@ fn marker_event(
let id = commands
.spawn((
Name::new("aim-marker"),
Billboard,
Billboard::All,
TargetMarker,
Transform::default(),
Sprite3dBuilder {

View File

@@ -134,7 +134,7 @@ fn on_head_drop(
|| drop.impulse,
)
.with_child((
Billboard,
Billboard::All,
SquishAnimation(2.6),
SceneRoot(mesh.scenes[0].clone()),
));

View File

@@ -62,7 +62,7 @@ fn update_effects(
cmds.entity(e).with_child((
Name::new("heal-particle"),
SceneRoot(assets.mesh_heal_particle.clone()),
Billboard,
Billboard::All,
Transform::from_translation(start_pos),
HealParticle {
start_scale,

View File

@@ -43,7 +43,7 @@ fn on_spawn_key(trigger: Trigger<KeySpawn>, mut commands: Commands, assets: Res<
Restitution::new(0.6),
Children::spawn((
Spawn((
Billboard,
Billboard::All,
SquishAnimation(2.6),
SceneRoot(assets.mesh_key.clone()),
)),

View File

@@ -110,6 +110,9 @@ pub struct GameAssets {
#[asset(path = "models/medic_particle.glb#Scene0")]
pub mesh_heal_particle: Handle<Scene>,
#[asset(path = "models/beaming.glb#Scene0")]
pub beaming: Handle<Scene>,
#[asset(path = "models/projectiles", collection(mapped, typed))]
pub projectiles: HashMap<AssetFileName, Handle<Gltf>>,

View File

@@ -9,10 +9,12 @@ use crate::{
heads_database::HeadsDatabase,
hitpoints::{Hitpoints, Kill},
keys::KeySpawn,
loading_assets::GameAssets,
sounds::PlaySound,
tb_entities::EnemySpawn,
utils::billboards::Billboard,
};
use bevy::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use std::collections::HashMap;
#[derive(Component, Reflect)]
@@ -25,14 +27,23 @@ struct NpcSpawning {
spawn_index: u32,
}
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct SpawningBeam(pub f32);
#[derive(Event)]
struct OnCheckSpawns;
#[derive(Event)]
pub struct SpawnCharacter(pub Vec3);
pub fn plugin(app: &mut App) {
app.init_resource::<NpcSpawning>();
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);
}
fn setup(mut commands: Commands) {
@@ -43,7 +54,7 @@ fn setup(mut commands: Commands) {
fn on_spawn_check(
_trigger: Trigger<OnCheckSpawns>,
mut commands: Commands,
query: Query<(Entity, &EnemySpawn), Without<Npc>>,
query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>,
heads_db: Res<HeadsDatabase>,
spawning: Res<NpcSpawning>,
) {
@@ -53,7 +64,7 @@ fn on_spawn_check(
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 order > spawning.spawn_index {
continue;
@@ -79,6 +90,7 @@ fn on_spawn_check(
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
.observe(on_kill);
commands.trigger(SpawnCharacter(transform.translation));
commands.trigger(PlaySound::Beaming);
}
}
@@ -107,3 +119,35 @@ fn on_kill(
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();
}
}
}

View File

@@ -11,6 +11,7 @@ use crate::{
heads_database::{HeadControls, HeadsDatabase},
hitpoints::{Hitpoints, Kill},
loading_assets::AudioAssets,
npc::SpawnCharacter,
physics_layers::GameLayer,
sounds::PlaySound,
tb_entities::SpawnPoint,
@@ -98,6 +99,8 @@ fn spawn(
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
PlaybackSettings::DESPAWN,
));
commands.trigger(SpawnCharacter(transform.translation));
}
fn on_kill(

View File

@@ -2,9 +2,13 @@ use crate::camera::MainCamera;
use bevy::prelude::*;
use bevy_sprite3d::Sprite3dPlugin;
#[derive(Component, Reflect)]
#[derive(Component, Reflect, Default, PartialEq, Eq)]
#[reflect(Component)]
pub struct Billboard;
pub enum Billboard {
#[default]
All,
XZ,
}
pub fn plugin(app: &mut App) {
if !app.is_plugin_added::<Sprite3dPlugin>() {
@@ -18,8 +22,8 @@ pub fn plugin(app: &mut App) {
fn face_camera(
cam_query: Query<&GlobalTransform, With<MainCamera>>,
mut query: Query<
(&mut Transform, &ChildOf, &InheritedVisibility),
(With<Billboard>, Without<MainCamera>),
(&mut Transform, &ChildOf, &InheritedVisibility, &Billboard),
Without<MainCamera>,
>,
parent_transform: Query<&GlobalTransform>,
) {
@@ -27,7 +31,7 @@ fn face_camera(
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) {
continue;
}
@@ -37,18 +41,34 @@ fn face_camera(
};
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(
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 {
return;
};
for mut transform in query.iter_mut() {
transform.look_at(cam_transform.translation(), Vec3::Y);
for (mut transform, billboard) in query.iter_mut() {
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);
}
}