Animation sync (#60)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>>,
|
||||
) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)],
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user