Controller Replication (#57)
This commit is contained in:
@@ -3,11 +3,12 @@ use crate::{
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CameraTarget;
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CameraArmRotation;
|
||||
|
||||
/// Requested camera rotation based on various input sources (keyboard, gamepad)
|
||||
|
||||
@@ -10,12 +10,13 @@ use bevy::{
|
||||
animation::RepeatAnimation, ecs::system::SystemParam, platform::collections::HashMap,
|
||||
prelude::*, scene::SceneInstanceReady,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{f32::consts::PI, time::Duration};
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct ProjectileOrigin;
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedCharacter {
|
||||
head: usize,
|
||||
}
|
||||
|
||||
@@ -5,16 +5,22 @@ use crate::{
|
||||
animation::AnimationFlags,
|
||||
character::HasCharacterAnimations,
|
||||
control::{Controls, SelectedController, controls::ControllerSettings},
|
||||
heads_database::HeadControls,
|
||||
head::ActiveHead,
|
||||
heads_database::{HeadControls, HeadsDatabase},
|
||||
physics_layers::GameLayer,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
};
|
||||
use avian3d::{math::*, prelude::*};
|
||||
use bevy::prelude::*;
|
||||
use bevy::{
|
||||
ecs::{component::HookContext, world::DeferredWorld},
|
||||
prelude::*,
|
||||
};
|
||||
use happy_feet::prelude::{
|
||||
Character, CharacterDrag, CharacterGravity, CharacterMovement, CharacterPlugins,
|
||||
GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour,
|
||||
SteppingConfig,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(CharacterPlugins::default());
|
||||
@@ -148,10 +154,28 @@ fn decelerate(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
#[derive(Component, Reflect, PartialEq, Serialize, Deserialize)]
|
||||
#[reflect(Component)]
|
||||
pub struct MovementSpeedFactor(pub f32);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
#[component(on_add = add_controller_bundle)]
|
||||
pub struct PlayerCharacterController;
|
||||
|
||||
fn add_controller_bundle(mut world: DeferredWorld, ctx: HookContext) {
|
||||
let head = world
|
||||
.entity(ctx.entity)
|
||||
.get::<ActiveHead>()
|
||||
.expect("player must be spawned with an `ActiveHead`")
|
||||
.0;
|
||||
let controls = world.resource::<HeadsDatabase>().head_stats(head).controls;
|
||||
world
|
||||
.commands()
|
||||
.entity(ctx.entity)
|
||||
.insert(CharacterControllerBundle::new(controls));
|
||||
}
|
||||
|
||||
/// A bundle that contains the components needed for a basic
|
||||
/// kinematic character controller.
|
||||
#[derive(Bundle)]
|
||||
@@ -163,11 +187,13 @@ pub struct CharacterControllerBundle {
|
||||
collision_events: CollisionEventsEnabled,
|
||||
movement_config: MovementConfig,
|
||||
interpolation: TransformInterpolation,
|
||||
layers: CollisionLayers,
|
||||
}
|
||||
|
||||
impl CharacterControllerBundle {
|
||||
pub fn new(collider: Collider, controls: HeadControls) -> Self {
|
||||
pub fn new(controls: HeadControls) -> Self {
|
||||
// Create shape caster as a slightly smaller version of collider
|
||||
let collider = Collider::capsule(0.9, 1.2);
|
||||
let mut caster_shape = collider.clone();
|
||||
caster_shape.set_scale(Vector::ONE * 0.98, 10);
|
||||
|
||||
@@ -184,6 +210,10 @@ impl CharacterControllerBundle {
|
||||
collision_events: CollisionEventsEnabled,
|
||||
movement_config: config,
|
||||
interpolation: TransformInterpolation,
|
||||
layers: CollisionLayers::new(
|
||||
LayerMask(GameLayer::Player.to_bits()),
|
||||
LayerMask::ALL & !GameLayer::CollectiblePhysics.to_bits(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use bevy::{
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<Controls>();
|
||||
@@ -43,7 +44,7 @@ pub fn plugin(app: &mut App) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, PartialEq, Reflect)]
|
||||
#[derive(Component, Clone, PartialEq, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(Component)]
|
||||
pub struct ControllerSettings {
|
||||
pub deceleration_factor: f32,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ActiveHead(pub usize);
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
sounds::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub static HEAD_COUNT: usize = 18;
|
||||
pub static HEAD_SLOTS: usize = 5;
|
||||
@@ -21,7 +22,7 @@ pub struct HeadsImages {
|
||||
pub heads: Vec<Handle<Image>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Serialize, Deserialize)]
|
||||
pub struct HeadState {
|
||||
pub head: usize,
|
||||
pub health: u32,
|
||||
@@ -51,7 +52,7 @@ impl HeadState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Reflect, Debug)]
|
||||
#[derive(Component, Default, Reflect, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
pub struct ActiveHeads {
|
||||
heads: [Option<HeadState>; 5],
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::{Cash, CashCollectEvent},
|
||||
character::AnimatedCharacter,
|
||||
control::controller_common::CharacterControllerBundle,
|
||||
control::controller_common::PlayerCharacterController,
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
head_drop::HeadDrops,
|
||||
@@ -12,9 +12,9 @@ use crate::{
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
loading_assets::AudioAssets,
|
||||
npc::SpawnCharacter,
|
||||
physics_layers::GameLayer,
|
||||
sounds::PlaySound,
|
||||
tb_entities::SpawnPoint,
|
||||
utils::commands::EntityCommandExt,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{
|
||||
@@ -22,17 +22,18 @@ use bevy::{
|
||||
prelude::*,
|
||||
window::{CursorGrabMode, PrimaryWindow},
|
||||
};
|
||||
use lightyear::prelude::{NetworkTarget, PredictionTarget, Replicate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[require(Transform, Visibility)]
|
||||
pub struct PlayerBodyMesh;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Startup, (toggle_cursor_system, cursor_recenter));
|
||||
app.add_systems(OnEnter(GameState::Playing), spawn);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
@@ -46,10 +47,10 @@ pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_update_head_mesh);
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
pub fn spawn(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
let Some(spawn) = query.iter().next() else {
|
||||
@@ -58,35 +59,11 @@ fn spawn(
|
||||
|
||||
let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));
|
||||
|
||||
let collider = Collider::capsule(0.9, 1.2);
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
Name::from("player"),
|
||||
Player,
|
||||
ActiveHead(0),
|
||||
ActiveHeads::new([
|
||||
Some(HeadState::new(0, heads_db.as_ref())),
|
||||
Some(HeadState::new(3, heads_db.as_ref())),
|
||||
Some(HeadState::new(6, heads_db.as_ref())),
|
||||
Some(HeadState::new(10, heads_db.as_ref())),
|
||||
Some(HeadState::new(9, heads_db.as_ref())),
|
||||
]),
|
||||
Hitpoints::new(100),
|
||||
CameraTarget,
|
||||
transform,
|
||||
Visibility::default(),
|
||||
CollisionLayers::new(
|
||||
LayerMask(GameLayer::Player.to_bits()),
|
||||
LayerMask::ALL & !GameLayer::CollectiblePhysics.to_bits(),
|
||||
),
|
||||
CharacterControllerBundle::new(collider, heads_db.head_stats(0).controls),
|
||||
children![(
|
||||
Name::new("player-rig"),
|
||||
PlayerBodyMesh,
|
||||
CameraArmRotation,
|
||||
children![AnimatedCharacter::new(0)]
|
||||
)],
|
||||
.spawn(player_bundle(transform, &heads_db))
|
||||
.insert_server((
|
||||
Replicate::to_clients(NetworkTarget::All),
|
||||
PredictionTarget::to_clients(NetworkTarget::All),
|
||||
))
|
||||
.observe(on_kill);
|
||||
|
||||
@@ -98,6 +75,32 @@ fn spawn(
|
||||
commands.trigger(SpawnCharacter(transform.translation));
|
||||
}
|
||||
|
||||
fn player_bundle(transform: Transform, heads_db: &Res<HeadsDatabase>) -> impl Bundle {
|
||||
(
|
||||
Name::from("player"),
|
||||
Player,
|
||||
ActiveHead(0),
|
||||
ActiveHeads::new([
|
||||
Some(HeadState::new(0, heads_db.as_ref())),
|
||||
Some(HeadState::new(3, heads_db.as_ref())),
|
||||
Some(HeadState::new(6, heads_db.as_ref())),
|
||||
Some(HeadState::new(10, heads_db.as_ref())),
|
||||
Some(HeadState::new(9, heads_db.as_ref())),
|
||||
]),
|
||||
Hitpoints::new(100),
|
||||
CameraTarget,
|
||||
transform,
|
||||
Visibility::default(),
|
||||
PlayerCharacterController,
|
||||
children![(
|
||||
Name::new("player-rig"),
|
||||
PlayerBodyMesh,
|
||||
CameraArmRotation,
|
||||
children![AnimatedCharacter::new(0)]
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
fn on_kill(
|
||||
trigger: Trigger<Kill>,
|
||||
mut commands: Commands,
|
||||
|
||||
@@ -1,20 +1,72 @@
|
||||
use crate::{
|
||||
abilities::BuildExplosionSprite, global_observer, loading_assets::GameAssets,
|
||||
abilities::BuildExplosionSprite,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
character::AnimatedCharacter,
|
||||
control::{
|
||||
controller_common::{MovementSpeedFactor, PlayerCharacterController},
|
||||
controls::ControllerSettings,
|
||||
},
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
heads::ActiveHeads,
|
||||
loading_assets::GameAssets,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
utils::triggers::TriggerAppExt,
|
||||
};
|
||||
use avian3d::prelude::{AngularVelocity, CollisionLayers, LinearVelocity};
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::{ActionsChannel, AppComponentExt};
|
||||
use happy_feet::{
|
||||
grounding::GroundingState,
|
||||
prelude::{
|
||||
Character, CharacterDrag, CharacterGravity, CharacterMovement, GroundFriction, Grounding,
|
||||
GroundingConfig, KinematicVelocity, MoveInput, SteppingConfig,
|
||||
},
|
||||
};
|
||||
use lightyear::prelude::{
|
||||
ActionsChannel, AppComponentExt, PredictionMode, PredictionRegistrationExt,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_component::<Transform>();
|
||||
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>();
|
||||
|
||||
app.replicate_trigger::<BuildExplosionSprite, ActionsChannel>();
|
||||
|
||||
global_observer!(app, spawn_gltf_scene_roots);
|
||||
}
|
||||
|
||||
fn transform_should_rollback(this: &Transform, that: &Transform) -> bool {
|
||||
this.translation.distance_squared(that.translation) >= 0.01f32.powf(2.)
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
pub enum GltfSceneRoot {
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod billboards;
|
||||
pub mod commands;
|
||||
pub mod explosions;
|
||||
pub mod observers;
|
||||
pub mod run_conditions;
|
||||
pub mod sprite_3d_animation;
|
||||
pub mod squish_animation;
|
||||
pub mod trail;
|
||||
|
||||
5
crates/shared/src/utils/run_conditions.rs
Normal file
5
crates/shared/src/utils/run_conditions.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use bevy::ecs::{resource::Resource, system::Res};
|
||||
|
||||
pub fn resource_absent<R: Resource>(res: Option<Res<R>>) -> bool {
|
||||
res.is_none()
|
||||
}
|
||||
Reference in New Issue
Block a user