Lightyear setup (#55)

This commit is contained in:
extrawurst
2025-07-10 23:21:11 +02:00
committed by GitHub
parent 691b9eed33
commit 78b09b33d6
26 changed files with 2515 additions and 242 deletions

View File

@@ -58,11 +58,15 @@ fn on_trigger_arrow(
state.rot.mul_quat(Quat::from_rotation_y(PI))
};
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.;
let damage = heads_db.head_stats(state.head).damage;
commands.spawn((Name::new("projectile-arrow"), ArrowProjectile { damage }, t));
commands.spawn((
Name::new("projectile-arrow"),
ArrowProjectile { damage },
transform,
));
}
fn update(

View File

@@ -6,12 +6,17 @@ use crate::{
hitpoints::Hit,
loading_assets::GameAssets,
physics_layers::GameLayer,
protocol::GltfSceneRoot,
tb_entities::EnemySpawn,
utils::{auto_rotate::AutoRotation, global_observer, sprite_3d_animation::AnimationTimer},
utils::{
auto_rotate::AutoRotation, commands::CommandExt, global_observer,
sprite_3d_animation::AnimationTimer,
},
};
use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use lightyear::prelude::{NetworkTarget, Replicate};
use std::f32::consts::PI;
const MAX_SHOT_AGES: f32 = 15.;
@@ -58,8 +63,6 @@ fn on_trigger_missile(
query_transform: Query<&Transform>,
time: Res<Time>,
heads_db: Res<HeadsDatabase>,
assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>,
) {
let state = trigger.event().0;
@@ -76,33 +79,32 @@ fn on_trigger_missile(
let head = heads_db.head_stats(state.head);
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.0;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
let mesh = assets.projectiles[format!("{}.glb", head.projectile).as_str()].clone();
let asset = gltf_assets.get(&mesh).unwrap();
commands.spawn((
Name::new("projectile-missile"),
CurverProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
CollisionEventsEnabled,
Visibility::default(),
t,
children![(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
SceneRoot(asset.scenes[0].clone()),
),],
));
commands
.spawn((
Name::new("projectile-missile"),
CurverProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
CollisionEventsEnabled,
Visibility::default(),
transform,
children![(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
GltfSceneRoot::Projectile(head.projectile.clone()),
),],
))
.insert_server(Replicate::to_clients(NetworkTarget::All));
}
fn enemy_hit(

View File

@@ -97,8 +97,8 @@ fn on_trigger_gun(
state.rot.mul_quat(Quat::from_rotation_y(PI))
};
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.0;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
commands.spawn((
Name::new("projectile-gun"),
@@ -114,7 +114,7 @@ fn on_trigger_gun(
Sensor,
CollisionEventsEnabled,
Visibility::default(),
t,
transform,
Children::spawn(Spawn(Gizmo {
handle: gizmo_assets.add({
let mut g = GizmoAsset::default();

View File

@@ -5,14 +5,17 @@ use crate::{
heads_database::HeadsDatabase,
loading_assets::GameAssets,
physics_layers::GameLayer,
protocol::GltfSceneRoot,
sounds::PlaySound,
utils::{
explosions::Explosion, global_observer, sprite_3d_animation::AnimationTimer, trail::Trail,
commands::CommandExt, explosions::Explosion, global_observer,
sprite_3d_animation::AnimationTimer, trail::Trail,
},
};
use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use lightyear::prelude::{NetworkTarget, Replicate};
use std::f32::consts::PI;
const MAX_SHOT_AGES: f32 = 15.;
@@ -56,8 +59,6 @@ fn on_trigger_missile(
query_transform: Query<&Transform>,
time: Res<Time>,
heads_db: Res<HeadsDatabase>,
assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) {
let state = trigger.event().0;
@@ -75,51 +76,50 @@ fn on_trigger_missile(
let head = heads_db.head_stats(state.head);
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.0;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
let mesh = assets.projectiles["missile.glb"].clone();
let asset = gltf_assets.get(&mesh).unwrap();
commands.spawn((
Name::new("projectile-missile"),
MissileProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
CollisionEventsEnabled,
Visibility::default(),
t,
children![
(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse())
.with_scale(Vec3::splat(0.04)),
SceneRoot(asset.scenes[0].clone()),
commands
.spawn((
Name::new("projectile-missile"),
MissileProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
(
Trail::new(
12,
LinearRgba::rgb(1., 0.0, 0.),
LinearRgba::rgb(0.9, 0.9, 0.)
)
.with_pos(t.translation),
Gizmo {
handle: gizmo_assets.add(GizmoAsset::default()),
line_config: GizmoLineConfig {
width: 10.,
Sensor,
CollisionEventsEnabled,
Visibility::default(),
transform,
children![
(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse())
.with_scale(Vec3::splat(0.04)),
GltfSceneRoot::Projectile("missile".to_string()),
),
(
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()
},
..default()
},
)
],
));
)
],
))
.insert_server(Replicate::to_clients(NetworkTarget::All));
}
fn update(mut query: Query<&mut Transform, With<MissileProjectile>>) {

View File

@@ -5,9 +5,10 @@ use crate::{
heads_database::HeadsDatabase,
loading_assets::GameAssets,
physics_layers::GameLayer,
protocol::GltfSceneRoot,
sounds::PlaySound,
utils::{
auto_rotate::AutoRotation, explosions::Explosion, global_observer,
auto_rotate::AutoRotation, commands::CommandExt, explosions::Explosion, global_observer,
sprite_3d_animation::AnimationTimer,
},
};
@@ -15,10 +16,12 @@ use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_ballistic::launch_velocity;
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use lightyear::prelude::{NetworkTarget, Replicate};
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
#[derive(Component)]
struct ThrownProjectile {
#[derive(Component, Serialize, Deserialize, PartialEq)]
pub struct ThrownProjectile {
impact_animation: bool,
damage: u32,
}
@@ -50,8 +53,6 @@ fn on_trigger_thrown(
trigger: Trigger<TriggerThrow>,
mut commands: Commands,
query_transform: Query<&Transform>,
assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>,
heads_db: Res<HeadsDatabase>,
) {
let state = trigger.event().0;
@@ -76,8 +77,6 @@ fn on_trigger_thrown(
};
let head = heads_db.head_stats(state.head);
let mesh = assets.projectiles[format!("{}.glb", head.projectile).as_str()].clone();
let asset = gltf_assets.get(&mesh).unwrap();
//TODO: projectile db?
let explosion_animation = !matches!(state.head, 8 | 16);
@@ -102,9 +101,10 @@ fn on_trigger_thrown(
Visibility::default(),
Sensor,
))
.insert_server(Replicate::to_clients(NetworkTarget::All))
.with_child((
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
SceneRoot(asset.scenes[0].clone()),
GltfSceneRoot::Projectile(head.projectile.clone()),
));
}

View File

@@ -114,11 +114,11 @@ fn update_player_aim(
}
}
if let Some(e) = &aim_target.0 {
if commands.get_entity(*e).is_err() {
aim_target.0 = None;
return;
}
if let Some(e) = &aim_target.0
&& commands.get_entity(*e).is_err()
{
aim_target.0 = None;
return;
}
if new_target != aim_target.0 {
@@ -166,11 +166,11 @@ fn update_npc_aim(
}
}
if let Some(e) = &aim_target.0 {
if commands.get_entity(*e).is_err() {
aim_target.0 = None;
return;
}
if let Some(e) = &aim_target.0
&& commands.get_entity(*e).is_err()
{
aim_target.0 = None;
return;
}
if new_target != aim_target.0 {

View File

@@ -140,16 +140,16 @@ fn sync(
target_data: Query<(&Hitpoints, &ActiveHeads), With<Npc>>,
) {
let mut new_state = None;
if let Some(e) = player_target.iter().next().and_then(|target| target.0) {
if let Ok((hp, heads)) = target_data.get(e) {
let head = heads.current().expect("target must have a head on");
new_state = Some(UiHeadState {
head: head.head,
health: hp.health(),
ammo: 1.,
reloading: None,
});
}
if let Some(e) = player_target.iter().next().and_then(|target| target.0)
&& let Ok((hp, heads)) = target_data.get(e)
{
let head = heads.current().expect("target must have a head on");
new_state = Some(UiHeadState {
head: head.head,
health: hp.health(),
ammo: 1.,
reloading: None,
});
}
if new_state != target.head {

View File

@@ -10,17 +10,14 @@ use crate::{
};
use avian3d::{math::*, prelude::*};
use bevy::prelude::*;
use happy_feet::{
KinematicVelocity,
ground::{Grounding, GroundingConfig},
prelude::{
Character, CharacterDrag, CharacterFriction, CharacterGravity, CharacterMovement,
CharacterPlugin, MoveInput, SteppingBehaviour, SteppingConfig,
},
use happy_feet::prelude::{
Character, CharacterDrag, CharacterGravity, CharacterMovement, CharacterPlugins,
GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour,
SteppingConfig,
};
pub fn plugin(app: &mut App) {
app.add_plugins(CharacterPlugin::default());
app.add_plugins(CharacterPlugins::default());
app.register_type::<MovementSpeedFactor>();
@@ -180,7 +177,7 @@ impl CharacterControllerBundle {
};
Self {
character_controller: Character { up: Dir3::Y },
character_controller: Character,
collider,
move_input: MoveInput::default(),
movement_factor: MovementSpeedFactor(1.0),
@@ -197,7 +194,7 @@ struct MovementConfig {
step: SteppingConfig,
ground: GroundingConfig,
gravity: CharacterGravity,
friction: CharacterFriction,
friction: GroundFriction,
drag: CharacterDrag,
settings: ControllerSettings,
}
@@ -208,16 +205,19 @@ const RUNNING_MOVEMENT_CONFIG: MovementConfig = MovementConfig {
acceleration: 40.0,
},
step: SteppingConfig {
max_height: 0.25,
max_vertical: 0.25,
max_horizontal: 0.4,
behaviour: SteppingBehaviour::Grounded,
max_substeps: 8,
},
ground: GroundingConfig {
max_angle: PI / 4.0,
max_distance: 0.2,
snap_to_surface: true,
up_direction: Dir3::Y,
},
gravity: CharacterGravity(vec3(0.0, -60.0, 0.0)),
friction: CharacterFriction(10.0),
gravity: CharacterGravity(Some(vec3(0.0, -60.0, 0.0))),
friction: GroundFriction(10.0),
drag: CharacterDrag(0.0),
settings: ControllerSettings {
jump_force: 25.0,
@@ -231,16 +231,19 @@ const FLYING_MOVEMENT_CONFIG: MovementConfig = MovementConfig {
acceleration: 300.0,
},
step: SteppingConfig {
max_height: 0.25,
max_vertical: 0.25,
max_horizontal: 0.4,
behaviour: SteppingBehaviour::Never,
max_substeps: 8,
},
ground: GroundingConfig {
max_angle: 0.0,
max_distance: -1.0,
snap_to_surface: false,
up_direction: Dir3::Y,
},
gravity: CharacterGravity(Vec3::ZERO),
friction: CharacterFriction(0.0),
gravity: CharacterGravity(Some(Vec3::ZERO)),
friction: GroundFriction(0.0),
drag: CharacterDrag(10.0),
settings: ControllerSettings {
jump_force: 0.0,

View File

@@ -89,14 +89,14 @@ impl ActiveHeads {
continue;
}
if let Some(head) = head {
if head.health < head.health_max {
head.health = head
.health
.saturating_add(heal_amount)
.clamp(0, head.health_max);
healed = true;
}
if let Some(head) = head
&& head.health < head.health_max
{
head.health = head
.health
.saturating_add(heal_amount)
.clamp(0, head.health_max);
healed = true;
}
}

View File

@@ -24,6 +24,7 @@ pub mod npc;
pub mod physics_layers;
pub mod platforms;
pub mod player;
pub mod protocol;
pub mod sounds;
pub mod steam;
pub mod tb_entities;

View File

@@ -79,7 +79,8 @@ fn move_active(
let t = (elapsed - active.start_time) / active.duration;
transform.rotation = active.start.rotation.lerp(active.target.rotation, t);
} else {
*transform = active.target;
transform.translation = active.target.translation;
transform.rotation = active.target.rotation;
commands.entity(e).remove::<(ActiveMovable, Movable)>();
}
}

View File

@@ -65,10 +65,10 @@ fn on_spawn_check(
}
for (e, spawn, transform) in query.iter() {
if let Some(order) = spawn.spawn_order {
if order > spawning.spawn_index {
continue;
}
if let Some(order) = spawn.spawn_order
&& order > spawning.spawn_index
{
continue;
}
let id = names[&spawn.head];

View File

@@ -0,0 +1,41 @@
use crate::{global_observer, loading_assets::GameAssets};
use bevy::prelude::*;
use lightyear::prelude::AppComponentExt;
use serde::{Deserialize, Serialize};
pub fn plugin(app: &mut App) {
app.register_component::<Transform>();
app.register_component::<GltfSceneRoot>();
global_observer!(app, spawn_gltf_scene_roots);
}
#[derive(Component, Reflect, Serialize, Deserialize, PartialEq)]
#[reflect(Component)]
pub enum GltfSceneRoot {
Projectile(String),
}
fn spawn_gltf_scene_roots(
trigger: Trigger<OnAdd, GltfSceneRoot>,
mut commands: Commands,
gltf_roots: Query<&GltfSceneRoot>,
assets: Res<GameAssets>,
gltfs: Res<Assets<Gltf>>,
) -> Result {
let root = gltf_roots.get(trigger.target())?;
let (gltf, index) = match root {
GltfSceneRoot::Projectile(addr) => (
assets.projectiles[format!("{addr}.glb").as_str()].clone(),
0,
),
};
let gltf = gltfs.get(&gltf).unwrap();
let scene = gltf.scenes[index].clone();
commands.entity(trigger.target()).insert(SceneRoot(scene));
Ok(())
}

View File

@@ -0,0 +1,21 @@
use bevy::ecs::{
bundle::Bundle, resource::Resource, system::EntityCommands, world::EntityWorldMut,
};
#[derive(Default, Resource)]
pub struct IsServer;
pub trait CommandExt {
fn insert_server(&mut self, bundle: impl Bundle) -> &mut Self;
}
impl<'w> CommandExt for EntityCommands<'w> {
fn insert_server(&mut self, bundle: impl Bundle) -> &mut Self {
self.queue(|mut entity: EntityWorldMut| {
if entity.world().contains_resource::<IsServer>() {
entity.insert(bundle);
}
});
self
}
}

View File

@@ -1,9 +1,15 @@
pub mod auto_rotate;
pub mod billboards;
pub mod commands;
pub mod explosions;
pub mod observers;
pub mod sprite_3d_animation;
pub mod squish_animation;
pub mod trail;
use bevy::prelude::*;
pub(crate) use observers::global_observer;
pub fn plugin(app: &mut App) {
app.add_plugins(observers::plugin);
}