fix missile trails (#81)

added replicated `SpawnTrail` that acts as a marker and gets picked up by the client to create the visuals.
This commit is contained in:
extrawurst
2025-12-09 22:16:09 +01:00
committed by GitHub
parent 5c0e78cb8a
commit 4e169c1506
4 changed files with 91 additions and 51 deletions

View File

@@ -5,7 +5,7 @@ use crate::{
heads_database::HeadsDatabase, heads_database::HeadsDatabase,
physics_layers::GameLayer, physics_layers::GameLayer,
protocol::{GltfSceneRoot, PlaySound}, protocol::{GltfSceneRoot, PlaySound},
utils::{global_observer, trail::Trail}, utils::{global_observer, trail::SpawnTrail},
}; };
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::prelude::*; use bevy::prelude::*;
@@ -15,7 +15,8 @@ use std::f32::consts::PI;
const MAX_SHOT_AGES: f32 = 15.; const MAX_SHOT_AGES: f32 = 15.;
const MISSLE_SPEED: f32 = 3.; const MISSLE_SPEED: f32 = 3.;
#[derive(Component)] #[derive(Component, Reflect)]
#[reflect(Component)]
struct MissileProjectile { struct MissileProjectile {
time: f32, time: f32,
damage: u32, damage: u32,
@@ -40,7 +41,6 @@ fn on_trigger_missile(
query_transform: Query<&Transform>, query_transform: Query<&Transform>,
time: Res<Time>, time: Res<Time>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) { ) {
let state = trigger.event().0; let state = trigger.event().0;
@@ -66,6 +66,13 @@ fn on_trigger_missile(
time: time.elapsed_secs(), time: time.elapsed_secs(),
damage: head.damage, damage: head.damage,
}, },
SpawnTrail::new(
12,
LinearRgba::rgb(1., 0.0, 0.),
LinearRgba::rgb(0.9, 0.9, 0.),
10.,
)
.init_with_pos(),
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)), 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()),
@@ -77,29 +84,11 @@ fn on_trigger_missile(
Visibility::default(), Visibility::default(),
transform, transform,
Replicated, Replicated,
children![ children![(
( Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()), GltfSceneRoot::Projectile("missile".to_string()),
GltfSceneRoot::Projectile("missile".to_string()), Replicated
Replicated, )],
),
(
Trail::new(
12,
LinearRgba::rgb(1., 0.0, 0.),
LinearRgba::rgb(0.9, 0.9, 0.)
)
.with_pos(transform.translation),
Gizmo {
handle: gizmo_assets.add(GizmoAsset::default()),
line_config: GizmoLineConfig {
width: 10.,
..default()
},
..default()
},
)
],
)); ));
} }

View File

@@ -3,7 +3,7 @@ use crate::{
animation::{AnimationController, AnimationFlags}, animation::{AnimationController, AnimationFlags},
heads_database::HeadsDatabase, heads_database::HeadsDatabase,
loading_assets::GameAssets, loading_assets::GameAssets,
utils::trail::Trail, utils::trail::SpawnTrail,
}; };
use bevy::{ use bevy::{
animation::RepeatAnimation, ecs::system::SystemParam, platform::collections::HashMap, animation::RepeatAnimation, ecs::system::SystemParam, platform::collections::HashMap,
@@ -125,7 +125,6 @@ fn find_marker_bones(
mut commands: Commands, mut commands: Commands,
descendants: Query<&Children>, descendants: Query<&Children>,
name: Query<&Name>, name: Query<&Name>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) { ) {
let entity = trigger.event().entity; let entity = trigger.event().entity;
@@ -139,21 +138,12 @@ fn find_marker_bones(
commands.entity(child).insert(ProjectileOrigin); commands.entity(child).insert(ProjectileOrigin);
origin_found = true; origin_found = true;
} else if name.as_str().starts_with("Trail") { } else if name.as_str().starts_with("Trail") {
commands.entity(child).insert(( commands.entity(child).insert((SpawnTrail::new(
Trail::new( 20,
20, LinearRgba::new(1., 1.0, 1., 0.5),
LinearRgba::new(1., 1.0, 1., 0.5), LinearRgba::new(1., 1., 1., 0.5),
LinearRgba::new(1., 1., 1., 0.5), 24.,
), ),));
Gizmo {
handle: gizmo_assets.add(GizmoAsset::default()),
line_config: GizmoLineConfig {
width: 24.,
..default()
},
..default()
},
));
} }
} }

View File

@@ -19,7 +19,10 @@ use crate::{
platforms::ActivePlatform, platforms::ActivePlatform,
player::{Player, PlayerBodyMesh}, player::{Player, PlayerBodyMesh},
tick::GameTick, tick::GameTick,
utils::{auto_rotate::AutoRotation, billboards::Billboard, squish_animation::SquishAnimation}, utils::{
auto_rotate::AutoRotation, billboards::Billboard, squish_animation::SquishAnimation,
trail::SpawnTrail,
},
}; };
use avian3d::prelude::{ use avian3d::prelude::{
AngularInertia, AngularVelocity, CenterOfMass, Collider, ColliderDensity, CollisionLayers, AngularInertia, AngularVelocity, CenterOfMass, Collider, ColliderDensity, CollisionLayers,
@@ -96,6 +99,7 @@ pub fn plugin(app: &mut App) {
.replicate_once::<Npc>() .replicate_once::<Npc>()
.replicate::<SquishAnimation>() .replicate::<SquishAnimation>()
.replicate_once::<Transform>() .replicate_once::<Transform>()
.replicate_once::<SpawnTrail>()
.replicate::<UiActiveHeads>() .replicate::<UiActiveHeads>()
.replicate_as::<Visibility, SerVisibility>(); .replicate_as::<Visibility, SerVisibility>();

View File

@@ -1,7 +1,36 @@
use crate::GameState; use crate::GameState;
use bevy::prelude::*; use bevy::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Component)] #[derive(Copy, Clone, Component, Reflect, Deserialize, Serialize)]
#[reflect(Component)]
pub struct SpawnTrail {
pub points: usize,
pub col_start: LinearRgba,
pub col_end: LinearRgba,
pub width: f32,
pub init_pos: bool,
}
impl SpawnTrail {
pub fn new(points: usize, col_start: LinearRgba, col_end: LinearRgba, width: f32) -> Self {
Self {
points,
col_start,
col_end,
width,
init_pos: false,
}
}
pub fn init_with_pos(mut self) -> Self {
self.init_pos = true;
self
}
}
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct Trail { pub struct Trail {
points: Vec<Vec3>, points: Vec<Vec3>,
col_start: LinearRgba, col_start: LinearRgba,
@@ -9,17 +38,19 @@ pub struct Trail {
} }
impl Trail { impl Trail {
pub fn new(points: usize, col_start: LinearRgba, col_end: LinearRgba) -> Self { pub fn new(trail: SpawnTrail) -> Self {
Self { Self {
points: Vec::with_capacity(points), points: Vec::with_capacity(trail.points),
col_start, col_start: trail.col_start,
col_end, col_end: trail.col_end,
} }
} }
pub fn with_pos(self, pos: Vec3) -> Self { pub fn with_pos(self, pos: Option<Vec3>) -> Self {
let mut trail = self; let mut trail = self;
trail.add(pos); if let Some(pos) = pos {
trail.add(pos);
}
trail trail
} }
@@ -36,6 +67,32 @@ pub fn plugin(app: &mut App) {
FixedUpdate, FixedUpdate,
update_trail.run_if(in_state(GameState::Playing)), update_trail.run_if(in_state(GameState::Playing)),
); );
#[cfg(feature = "client")]
app.add_systems(Update, attach_trail.run_if(in_state(GameState::Playing)));
}
#[cfg(feature = "client")]
fn attach_trail(
mut commands: Commands,
query: Query<(Entity, &Transform, &SpawnTrail), Added<SpawnTrail>>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) {
for (entity, transform, trail) in query.iter() {
let width = trail.width;
let init_pos = trail.init_pos.then_some(transform.translation);
let id = commands
.spawn((
Trail::new(*trail).with_pos(init_pos),
Gizmo {
handle: gizmo_assets.add(GizmoAsset::default()),
line_config: GizmoLineConfig { width, ..default() },
..default()
},
))
.id();
commands.entity(entity).add_child(id);
}
} }
fn update_trail( fn update_trail(