Animation sync (#60)

This commit is contained in:
PROMETHIA-27
2025-07-29 07:32:54 -04:00
committed by GitHub
parent 0bd3fb0e80
commit c650924d68
16 changed files with 136 additions and 98 deletions

25
Cargo.lock generated
View File

@@ -4283,7 +4283,7 @@ dependencies = [
"lightyear_netcode",
"lightyear_prediction 0.22.4",
"lightyear_replication 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_steam",
"lightyear_sync 0.22.4",
"lightyear_transport 0.22.4",
@@ -4404,7 +4404,7 @@ dependencies = [
"bytes",
"lightyear_core 0.22.4",
"lightyear_link 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"serde",
"smallvec",
"thiserror 2.0.12",
@@ -4442,7 +4442,7 @@ dependencies = [
"bevy_reflect",
"bevy_time",
"chrono",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_utils 0.22.4",
"serde",
"tracing",
@@ -4601,7 +4601,7 @@ dependencies = [
"lightyear_core 0.22.4",
"lightyear_messages 0.22.4",
"lightyear_replication 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_sync 0.22.4",
"lightyear_utils 0.22.4",
"serde",
@@ -4671,7 +4671,7 @@ dependencies = [
"lightyear_connection 0.22.4",
"lightyear_core 0.22.4",
"lightyear_link 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_transport 0.22.4",
"lightyear_utils 0.22.4",
"serde",
@@ -4695,7 +4695,7 @@ dependencies = [
"lightyear_connection 0.22.4",
"lightyear_core 0.22.4",
"lightyear_link 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_transport 0.22.4",
"lightyear_utils 0.22.4",
"no_std_io2",
@@ -4754,7 +4754,7 @@ dependencies = [
"lightyear_link 0.22.4",
"lightyear_messages 0.22.4",
"lightyear_replication 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_sync 0.22.4",
"lightyear_utils 0.22.4",
"parking_lot",
@@ -4812,7 +4812,7 @@ dependencies = [
"lightyear_core 0.22.4",
"lightyear_link 0.22.4",
"lightyear_messages 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_transport 0.22.4",
"lightyear_utils 0.22.4",
"serde",
@@ -4842,9 +4842,9 @@ dependencies = [
[[package]]
name = "lightyear_serde"
version = "0.22.4"
version = "0.22.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8abacb721279778366728344102240d279e3f72693c2c8ed8a9feb527bf8b4da"
checksum = "0a45bd23d5f45b25ff53361f148dac09872c38ee184baf2ef9615250b4a9e09a"
dependencies = [
"bevy_derive",
"bevy_ecs",
@@ -4918,7 +4918,7 @@ dependencies = [
"lightyear_core 0.22.4",
"lightyear_link 0.22.4",
"lightyear_messages 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_transport 0.22.4",
"lightyear_utils 0.22.4",
"serde",
@@ -4973,7 +4973,7 @@ dependencies = [
"lightyear_connection 0.22.4",
"lightyear_core 0.22.4",
"lightyear_link 0.22.4",
"lightyear_serde 0.22.4",
"lightyear_serde 0.22.5",
"lightyear_utils 0.22.4",
"nonzero_ext",
"ringbuffer 0.16.0",
@@ -7145,6 +7145,7 @@ dependencies = [
"bevy_trenchbroom",
"happy_feet",
"lightyear",
"lightyear_serde 0.22.5",
"nil 0.14.0",
"rand 0.8.5",
"ron",

View File

@@ -36,6 +36,7 @@ lightyear = { version = "0.22.4", default-features = false, features = [
"udp",
] }
lightyear_avian3d = { git = "https://github.com/cBournhonesque/lightyear.git", rev = "03cbf419a2c0595261b64420bc0332fc3fe1cc3f" }
lightyear_serde = "0.22.4"
nil = "0.14.0"
rand = "=0.8.5"
ron = "0.8"

View File

@@ -65,6 +65,6 @@ fn spawn_disconnected_player(
heads_db: Res<HeadsDatabase>,
) {
if disconnected.state == ClientState::Disconnected {
shared::player::spawn(commands, query, asset_server, heads_db)
shared::player::spawn(commands, Entity::PLACEHOLDER, query, asset_server, heads_db)
}
}

View File

@@ -47,7 +47,7 @@ fn main() {
..default()
})
.set(bevy::log::LogPlugin {
filter: "info".into(),
filter: "info,lightyear_replication=warn".into(),
level: bevy::log::Level::INFO,
// provide custom log layer to receive logging events
custom_layer: bevy_debug_log::log_capture_layer,

View File

@@ -29,7 +29,7 @@ fn handle_new_client(
.entity(trigger.target())
.insert(ReplicationSender::default());
shared::player::spawn(commands, query, asset_server, heads_db);
shared::player::spawn(commands, trigger.target(), query, asset_server, heads_db);
Ok(())
}

View File

@@ -20,6 +20,7 @@ bevy_sprite3d = { workspace = true }
bevy_trenchbroom = { workspace = true }
happy_feet = { workspace = true }
lightyear = { workspace = true }
lightyear_serde = { workspace = true }
nil = { workspace = true }
rand = { workspace = true }
ron = { workspace = true }

View File

@@ -2,6 +2,7 @@ use crate::{
GameState, character::CharacterAnimations, head::ActiveHead, heads_database::HeadsDatabase,
};
use bevy::{animation::RepeatAnimation, ecs::query::QueryData, prelude::*};
use serde::{Deserialize, Serialize};
use std::time::Duration;
pub fn plugin(app: &mut App) {
@@ -13,17 +14,24 @@ pub fn plugin(app: &mut App) {
);
}
#[derive(Component, Default, Reflect)]
#[derive(Component, Default, Reflect, PartialEq, Serialize, Deserialize)]
#[reflect(Component)]
#[require(AnimationFlagCache)]
pub struct AnimationFlags {
pub any_direction: bool,
pub jumping: bool,
pub just_jumped: bool,
pub jump_count: u8,
pub shooting: bool,
pub restart_shooting: bool,
pub hit: bool,
}
#[derive(Component, Default, Reflect)]
#[reflect(Component)]
pub struct AnimationFlagCache {
pub jump_count: u8,
}
#[derive(QueryData)]
#[query_data(mutable)]
pub struct AnimationController {
@@ -69,16 +77,12 @@ impl AnimationControllerItem<'_> {
const DEFAULT_TRANSITION_DURATION: Duration = Duration::ZERO;
fn update_animation(
mut animated: Query<(
AnimationController,
&CharacterAnimations,
&mut AnimationFlags,
)>,
character: Query<&ActiveHead>,
mut animated: Query<(AnimationController, &CharacterAnimations)>,
mut character: Query<(&ActiveHead, &mut AnimationFlags, &mut AnimationFlagCache)>,
headdb: Res<HeadsDatabase>,
) {
for (mut controller, anims, mut flags) in animated.iter_mut() {
let head = character.get(anims.of_character).unwrap();
for (mut controller, anims) in animated.iter_mut() {
let (head, mut flags, mut cache) = character.get_mut(anims.of_character).unwrap();
let head = headdb.head_stats(head.0);
let is_playing_shoot = anims.shoot.is_some()
@@ -153,13 +157,13 @@ fn update_animation(
);
}
} else if flags.jumping {
if !controller.is_playing(anims.jump) || flags.just_jumped {
if !controller.is_playing(anims.jump) || flags.jump_count != cache.jump_count {
controller.play(
anims.jump,
DEFAULT_TRANSITION_DURATION,
RepeatAnimation::Never,
);
flags.just_jumped = false;
cache.jump_count = flags.jump_count;
}
} else if flags.any_direction {
if !controller.player.is_playing_animation(anims.run) {

View File

@@ -2,7 +2,6 @@ use crate::{
GameState,
animation::{AnimationController, AnimationFlags},
heads_database::HeadsDatabase,
hitpoints::Hitpoints,
loading_assets::GameAssets,
utils::trail::Trail,
};
@@ -47,7 +46,6 @@ impl CharacterHierarchy<'_, '_> {
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
#[relationship(relationship_target = HasCharacterAnimations)]
#[require(AnimationFlags)]
pub struct CharacterAnimations {
#[relationship]
pub of_character: Entity,
@@ -69,6 +67,7 @@ const ANIM_HIT: &str = "hit";
#[derive(Component)]
#[relationship_target(relationship = CharacterAnimations)]
#[require(AnimationFlags)]
pub struct HasCharacterAnimations(Entity);
pub fn plugin(app: &mut App) {
@@ -103,15 +102,15 @@ fn spawn(
});
let asset = gltf_assets.get(handle).unwrap();
let mut t =
let mut transform =
Transform::from_translation(Vec3::new(0., -1.45, 0.)).with_scale(Vec3::splat(1.2));
t.rotate_y(PI);
transform.rotate_y(PI);
commands
.entity(entity)
.insert((
t,
transform,
SceneRoot(asset.scenes[0].clone()),
AnimatedCharacterAsset(handle.clone()),
))
@@ -161,12 +160,16 @@ fn find_marker_bones(
}
}
#[derive(Component, Default, Reflect, PartialEq, Serialize, Deserialize)]
#[reflect(Component)]
pub struct Character;
fn setup_once_loaded(
mut commands: Commands,
mut query: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
parent: Query<&ChildOf>,
animated_character: Query<(&AnimatedCharacter, &AnimatedCharacterAsset)>,
characters: Query<Entity, With<Hitpoints>>,
characters: Query<Entity, With<Character>>,
gltf_assets: Res<Assets<Gltf>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {

View File

@@ -3,7 +3,6 @@ use crate::{
GameState,
abilities::TriggerStateRes,
animation::AnimationFlags,
character::HasCharacterAnimations,
control::{Controls, SelectedController, controls::ControllerSettings},
head::ActiveHead,
heads_database::{HeadControls, HeadsDatabase},
@@ -17,6 +16,7 @@ use happy_feet::prelude::{
GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour,
SteppingConfig,
};
use lightyear::prelude::Replicated;
use serde::{Deserialize, Serialize};
pub fn plugin(app: &mut App) {
@@ -49,16 +49,14 @@ pub fn plugin(app: &mut App) {
}
fn set_animation_flags(
mut flags: Query<&mut AnimationFlags>,
controls: Res<Controls>,
trigger: Res<TriggerStateRes>,
player: Single<(&Grounding, &HasCharacterAnimations), With<Player>>,
player: Single<(&Grounding, &mut AnimationFlags), (With<Player>, Without<Replicated>)>,
) {
let mut direction = controls.keyboard_state.move_dir;
let deadzone = 0.2;
let (grounding, has_anims) = *player;
let mut flags = flags.get_mut(*has_anims.collection()).unwrap();
let (grounding, mut flags) = player.into_inner();
if let Some(gamepad) = controls.gamepad_state {
direction += gamepad.move_dir;

View File

@@ -54,10 +54,12 @@ fn rotate_rig(
fn apply_controls(
mut character: Query<(&mut MoveInput, &MovementSpeedFactor)>,
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
) {
let (mut char_input, factor) = character.single_mut().unwrap();
) -> Result {
let (mut char_input, factor) = character.single_mut()?;
if let Some(ref rig_transform) = rig_transform_q {
char_input.set(-*rig_transform.forward() * factor.0);
}
Ok(())
}

View File

@@ -2,9 +2,9 @@ use super::{ControlState, ControllerSet};
use crate::{
GameState,
animation::AnimationFlags,
character::HasCharacterAnimations,
control::{controller_common::MovementSpeedFactor, controls::ControllerSettings},
player::PlayerBodyMesh,
utils::commands::IsServer,
};
use bevy::prelude::*;
use happy_feet::prelude::{Grounding, KinematicVelocity, MoveInput};
@@ -42,21 +42,19 @@ fn apply_controls(
&mut MoveInput,
&mut Grounding,
&mut KinematicVelocity,
&mut AnimationFlags,
&ControllerSettings,
&MovementSpeedFactor,
&HasCharacterAnimations,
)>,
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
mut anim_flags: Query<&mut AnimationFlags>,
is_server: Option<Res<IsServer>>,
) {
let Ok((mut move_input, mut grounding, mut velocity, settings, move_factor, has_anims)) =
let Ok((mut move_input, mut grounding, mut velocity, mut flags, settings, move_factor)) =
character.single_mut()
else {
return;
};
let mut flags = anim_flags.get_mut(*has_anims.collection()).unwrap();
let mut direction = -controls.move_dir.extend(0.0).xzy();
if let Some(ref rig_transform) = rig_transform_q {
@@ -72,8 +70,10 @@ fn apply_controls(
move_input.set(direction * move_factor.0);
if controls.jump && grounding.is_grounded() {
flags.jumping = true;
flags.just_jumped = true;
if is_server.is_some() {
flags.jumping = true;
flags.jump_count += 1;
}
happy_feet::movement::jump(settings.jump_force, &mut velocity, &mut grounding, Dir3::Y)
}
}

View File

@@ -4,7 +4,6 @@ use crate::{
GameState,
animation::AnimationFlags,
backpack::{BackbackSwapEvent, Backpack},
character::HasCharacterAnimations,
global_observer,
heads_database::HeadsDatabase,
hitpoints::Hitpoints,
@@ -215,8 +214,7 @@ fn reload(
mut commands: Commands,
mut active: Query<&mut ActiveHeads>,
time: Res<Time>,
player: Single<&HasCharacterAnimations, With<Player>>,
mut anim_flags: Query<&mut AnimationFlags>,
mut flags: Single<&mut AnimationFlags, With<Player>>,
) {
for mut active in active.iter_mut() {
if !active.reloading() {
@@ -231,7 +229,6 @@ fn reload(
if !head.has_ammo() && (head.last_use + head.reload_duration <= time.elapsed_secs()) {
// only for player?
commands.trigger(PlaySound::Reloaded);
let mut flags = anim_flags.get_mut(*player.collection()).unwrap();
flags.restart_shooting = true;
head.ammo = head.ammo_max;
}

View File

@@ -76,20 +76,18 @@ fn on_hp_added(mut commands: Commands, query: Query<Entity, Added<Hitpoints>>) {
fn on_hit(
trigger: Trigger<Hit>,
mut commands: Commands,
mut query: Query<(&mut Hitpoints, Option<&HasCharacterAnimations>)>,
mut anim_flags: Query<&mut AnimationFlags>,
mut query: Query<(&mut Hitpoints, Option<&mut AnimationFlags>)>,
time: Res<Time>,
) {
let Hit { damage } = trigger.event();
let Ok((mut hp, has_anims)) = query.get_mut(trigger.target()) else {
let Ok((mut hp, flags)) = query.get_mut(trigger.target()) else {
return;
};
commands.trigger(PlaySound::Hit);
if let Some(has_anims) = has_anims {
let mut flags = anim_flags.get_mut(*has_anims.collection()).unwrap();
if let Some(mut flags) = flags {
flags.hit = true;
}
@@ -102,18 +100,14 @@ fn on_hit(
}
fn reset_hit_animation_flag(
query: Query<(&Hitpoints, &HasCharacterAnimations)>,
mut animations: Query<(
&AnimationGraphHandle,
&CharacterAnimations,
&mut AnimationFlags,
)>,
mut query: Query<(&Hitpoints, &HasCharacterAnimations, &mut AnimationFlags)>,
animations: Query<(&AnimationGraphHandle, &CharacterAnimations)>,
graphs: Res<Assets<AnimationGraph>>,
clips: Res<Assets<AnimationClip>>,
time: Res<Time>,
) {
for (hp, anims) in query.iter() {
let (graph_handle, anims, mut flags) = animations.get_mut(*anims.collection()).unwrap();
for (hp, anims, mut flags) in query.iter_mut() {
let (graph_handle, anims) = animations.get(*anims.collection()).unwrap();
let graph = graphs.get(graph_handle.id()).unwrap();
let hit_anim = match graph.get(anims.hit).unwrap().node_type {
AnimationNodeType::Clip(ref handle) => clips.get(handle.id()).unwrap(),

View File

@@ -1,7 +1,7 @@
use crate::{
GameState,
ai::Ai,
character::AnimatedCharacter,
character::{AnimatedCharacter, Character},
global_observer,
head::ActiveHead,
head_drop::HeadDrops,
@@ -12,13 +12,19 @@ use crate::{
loading_assets::GameAssets,
sounds::PlaySound,
tb_entities::EnemySpawn,
utils::billboards::Billboard,
utils::{
billboards::Billboard,
commands::{EntityCommandExt, IsServer},
},
};
use bevy::{pbr::NotShadowCaster, prelude::*};
use lightyear::prelude::{NetworkTarget, Replicate};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Component, Reflect)]
#[derive(Component, Reflect, PartialEq, Serialize, Deserialize)]
#[reflect(Component)]
#[require(Character)]
pub struct Npc;
#[derive(Resource, Reflect, Default)]
@@ -57,7 +63,12 @@ fn on_spawn_check(
query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>,
heads_db: Res<HeadsDatabase>,
spawning: Res<NpcSpawning>,
is_server: Option<Res<IsServer>>,
) {
if is_server.is_none() {
return;
}
//TODO: move into HeadsDatabase
let mut names: HashMap<String, usize> = HashMap::default();
for i in 0..HEAD_COUNT {
@@ -87,6 +98,7 @@ fn on_spawn_check(
]),
))
.insert_if(Ai, || !spawn.disable_ai)
.insert_server(Replicate::to_clients(NetworkTarget::All))
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
.observe(on_kill);

View File

@@ -2,7 +2,7 @@ use crate::{
GameState,
camera::{CameraArmRotation, CameraTarget},
cash::{Cash, CashCollectEvent},
character::AnimatedCharacter,
character::{AnimatedCharacter, Character},
control::controller_common::PlayerCharacterController,
global_observer,
head::ActiveHead,
@@ -22,10 +22,11 @@ use bevy::{
prelude::*,
window::{CursorGrabMode, PrimaryWindow},
};
use lightyear::prelude::{NetworkTarget, PredictionTarget, Replicate};
use lightyear::prelude::{ControlledBy, Lifetime, NetworkTarget, PredictionTarget, Replicate};
use serde::{Deserialize, Serialize};
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
#[require(Character)]
pub struct Player;
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
@@ -49,6 +50,7 @@ pub fn plugin(app: &mut App) {
pub fn spawn(
mut commands: Commands,
owner: Entity,
query: Query<&Transform, With<SpawnPoint>>,
asset_server: Res<AssetServer>,
heads_db: Res<HeadsDatabase>,
@@ -64,6 +66,10 @@ pub fn spawn(
.insert_server((
Replicate::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::All),
ControlledBy {
owner,
lifetime: Lifetime::SessionBased,
},
))
.observe(on_kill);
@@ -96,7 +102,7 @@ fn player_bundle(transform: Transform, heads_db: &Res<HeadsDatabase>) -> impl Bu
Name::new("player-rig"),
PlayerBodyMesh,
CameraArmRotation,
children![AnimatedCharacter::new(0)]
children![AnimatedCharacter::new(0)],
)],
)
}

View File

@@ -1,7 +1,8 @@
use crate::{
abilities::BuildExplosionSprite,
animation::AnimationFlags,
camera::{CameraArmRotation, CameraTarget},
character::AnimatedCharacter,
character::{self, AnimatedCharacter},
control::{
controller_common::{MovementSpeedFactor, PlayerCharacterController},
controls::ControllerSettings,
@@ -25,38 +26,56 @@ use happy_feet::{
use lightyear::prelude::{
ActionsChannel, AppComponentExt, PredictionMode, PredictionRegistrationExt,
};
use lightyear_serde::{
SerializationError, reader::ReadInteger, registry::SerializeFns, writer::WriteInteger,
};
use serde::{Deserialize, Serialize};
pub fn plugin(app: &mut App) {
app.register_component::<ActiveHead>();
app.register_component::<ActiveHeads>();
app.register_component::<AngularVelocity>();
app.register_component::<AnimatedCharacter>();
app.register_component::<AnimationFlags>();
app.register_component::<CameraArmRotation>();
app.register_component::<CameraTarget>();
app.register_component::<Character>();
app.register_component::<character::Character>();
app.register_component::<CharacterDrag>();
app.register_component::<CharacterGravity>();
app.register_component::<CharacterMovement>();
app.register_component::<CollisionLayers>();
app.register_component::<ControllerSettings>();
app.register_component::<GltfSceneRoot>();
app.register_component::<GroundFriction>();
app.register_component::<Grounding>();
app.register_component::<GroundingConfig>();
app.register_component::<GroundingState>();
app.register_component::<KinematicVelocity>();
app.register_component::<LinearVelocity>();
app.register_component::<MoveInput>();
app.register_component::<MovementSpeedFactor>();
app.register_component::<Name>();
app.register_component::<Player>();
app.register_component::<PlayerBodyMesh>();
app.register_component::<PlayerCharacterController>();
app.register_component::<SteppingConfig>();
app.register_component::<Transform>()
.add_prediction(PredictionMode::Full)
.add_should_rollback(transform_should_rollback);
app.register_component::<GltfSceneRoot>();
app.register_component::<Player>();
app.register_component::<PlayerBodyMesh>();
app.register_component::<Name>();
app.register_component::<ActiveHead>();
app.register_component::<ActiveHeads>();
app.register_component::<CameraTarget>();
app.register_component::<CameraArmRotation>();
app.register_component::<CollisionLayers>();
app.register_component::<AnimatedCharacter>();
app.register_component::<PlayerCharacterController>();
app.register_component::<LinearVelocity>();
app.register_component::<AngularVelocity>();
app.register_component::<KinematicVelocity>();
app.register_component::<Character>();
app.register_component::<MoveInput>();
app.register_component::<MovementSpeedFactor>();
app.register_component::<CharacterMovement>();
app.register_component::<SteppingConfig>();
app.register_component::<GroundingConfig>();
app.register_component::<Grounding>();
app.register_component::<GroundingState>();
app.register_component::<CharacterGravity>();
app.register_component::<GroundFriction>();
app.register_component::<CharacterDrag>();
app.register_component::<ControllerSettings>();
// `Visibility` isn't `(De)Serialize`, so we have to provide custom serde for it.
app.register_component_custom_serde::<Visibility>(SerializeFns {
serialize: |comp, writer| writer.write_u8(*comp as u8).map_err(SerializationError::Io),
deserialize: |reader| {
let byte = reader.read_u8().map_err(SerializationError::Io)?;
Ok(match byte {
0 => Visibility::Inherited,
1 => Visibility::Hidden,
2 => Visibility::Visible,
_ => return Err(SerializationError::InvalidValue),
})
},
});
app.replicate_trigger::<BuildExplosionSprite, ActionsChannel>();