Last active
November 23, 2024 11:14
-
-
Save RJ/e08f16e4953267ed6cfbde39003af817 to your computer and use it in GitHub Desktop.
spacepit bullet plugin, observer vs system ordering issue for PreSpawnedPlayerObject hashes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use crate::prelude::*; | |
use avian2d::prelude::*; | |
use bevy::prelude::*; | |
use lightyear::client::components::ComponentSyncMode; | |
use serde::{Deserialize, Serialize}; | |
pub mod prelude { | |
pub const BULLET_SIZE: f32 = 1.5; | |
pub use super::Bullet; | |
pub use super::BulletBundle; | |
pub use super::BulletPlugin; | |
} | |
pub struct BulletPlugin; | |
impl Plugin for BulletPlugin { | |
fn build(&self, app: &mut App) { | |
app.register_component::<Bullet>(ChannelDirection::ServerToClient) | |
.add_prediction(ComponentSyncMode::Once); | |
// using the observer results in the server hashing the prespawned obj based on just the components | |
// from the BulletBundle, but the client hashing the full list after the observer runs and decorates.. | |
// app.observe(on_add_bullets); | |
app.add_systems(FixedUpdate, decorate_new_bullets.in_set(FixedSet::PostMain)); | |
} | |
} | |
#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq)] | |
pub struct Bullet { | |
pub damage: u32, | |
pub owner: WeaponOwner, | |
} | |
#[derive(Bundle)] | |
pub struct BulletBundle { | |
pub bullet: Bullet, | |
pub position: Position, | |
pub linear_velocity: LinearVelocity, | |
pub color: ColorComponent, | |
pub prespawned_player_object: PreSpawnedPlayerObject, | |
} | |
impl BulletBundle { | |
pub fn new( | |
damage: u32, | |
owner: WeaponOwner, | |
position: Vec2, | |
linear_velocity: Vec2, | |
color: Color, | |
) -> Self { | |
// could do custom hash to make ordering issue moot.. | |
info!("BulletBundle::new, owner: {:?}", owner); | |
Self { | |
// the default hashing algorithm uses the tick and component list. in order to disambiguate | |
// between two players spawning a bullet on the same tick, we add client_id to the mix. | |
prespawned_player_object: PreSpawnedPlayerObject::default_with_salt(owner.to_bits()), | |
bullet: Bullet { damage, owner }, | |
position: Position(position), | |
linear_velocity: LinearVelocity(linear_velocity), | |
color: ColorComponent(color), | |
} | |
} | |
} | |
/// Adds physics and other bits to newly spawned Bullet entities | |
/// On client we only want to decorate the Predicted entity, | |
/// on server, we decorate the server's entity.. | |
fn decorate_new_bullets( | |
mut commands: Commands, | |
mut bullet_query: Query< | |
(Entity, &Bullet), | |
Or<( | |
Added<ReplicationTarget>, | |
Or<(Added<PreSpawnedPlayerObject>, Added<client::Predicted>)>, | |
)>, | |
>, | |
identity: NetworkIdentity, | |
tick_manager: Res<TickManager>, | |
) { | |
for (entity, _bullet) in bullet_query.iter_mut() { | |
let mut cmd = commands.entity(entity); | |
cmd.insert(( | |
Name::from("Bullet"), | |
Lifetimer { | |
origin_tick: tick_manager.tick(), | |
lifetime: FIXED_TIMESTEP_HZ as i16 * 60, | |
}, | |
Hp::new(10), | |
Collider::circle(BULLET_SIZE), | |
RigidBody::Dynamic, | |
ColliderDensity(10.0), | |
SpatialBundle::default(), | |
CollisionLayerPreset::Bullet, | |
ExternalForce::default().with_persistence(false), | |
)); | |
if identity.is_server() { | |
let replicate = server::Replicate { | |
sync: server::SyncTarget { | |
prediction: NetworkTarget::All, | |
..Default::default() | |
}, | |
group: DEFAULT_REPLICATION_GROUP, | |
..default() | |
}; | |
cmd.insert(replicate); | |
} | |
} | |
} | |
/// Decorates newly spawned BulletBundles with the necessary components for bullets. | |
fn on_add_bullets( | |
trigger: Trigger<OnAdd, Bullet>, | |
mut commands: Commands, | |
q: Query<&Bullet, RenderedEntity>, | |
identity: NetworkIdentity, | |
tick_manager: Res<TickManager>, | |
) { | |
let Ok(_bullet) = q.get(trigger.entity()) else { | |
warn!("on add bullet, but can't find bullet {}", trigger.entity()); | |
return; | |
}; | |
let mut cmd = commands.entity(trigger.entity()); | |
cmd.insert(( | |
Name::from("Bullet"), | |
Lifetimer { | |
origin_tick: tick_manager.tick(), | |
lifetime: FIXED_TIMESTEP_HZ as i16 * 60, | |
}, | |
Hp::new(10), | |
Collider::circle(BULLET_SIZE), | |
RigidBody::Dynamic, | |
ColliderDensity(10.0), | |
SpatialBundle::default(), | |
CollisionLayerPreset::Bullet, | |
ExternalForce::default().with_persistence(false), | |
)); | |
if identity.is_server() { | |
let replicate = server::Replicate { | |
sync: server::SyncTarget { | |
prediction: NetworkTarget::All, | |
..Default::default() | |
}, | |
group: DEFAULT_REPLICATION_GROUP, | |
..default() | |
}; | |
cmd.insert(replicate); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment