Skip to content

Instantly share code, notes, and snippets.

@RJ
Last active November 23, 2024 11:14
Show Gist options
  • Save RJ/e08f16e4953267ed6cfbde39003af817 to your computer and use it in GitHub Desktop.
Save RJ/e08f16e4953267ed6cfbde39003af817 to your computer and use it in GitHub Desktop.
spacepit bullet plugin, observer vs system ordering issue for PreSpawnedPlayerObject hashes
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