Skip to content

Latest commit

 

History

History
124 lines (100 loc) · 5.38 KB

advanced.md

File metadata and controls

124 lines (100 loc) · 5.38 KB

Advanced guide

This will walk you through the more advanced features of bevy-pigeon.

This guide assumes you've read and understand the Quickstart Guide.

Custom message types

bevy has a serde feature flag that enables serialization support for some of its types, so you should check to see if the type you want to network already has serde support. bevy-pigeon also provides multiple network-able versions of non-network-able types in the bevy-pigeon::types module. If this doesn't capture your use case, continue reading.

Sometimes, you want to use bevy-pigeon's NetComp to easily sync your components, but the component you want to sync can't be sent by carrier-pigeon for whatever reason (contains references, isn't serializable ...). Or maybe you want custom control over how to serialize it (to help save bandwidth). The solution to this is to make a custom type that can be sent by carrier-pigeon, and tell bevy-pigeon to use that.

For example, to send Transforms:

#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Debug)]
pub struct MyNetTransform {
    pub translation: Vec3,
    pub rotation: Quat,
    // If you dont use scale, it can be taken out to reduce bandwidth.
}

// You must impl these conversion types so `pigeon` can convert it.
impl From<Transform> for MyNetTransform {
    fn from(o: Transform) -> Self {
        MyNetTransform {
            translation: o.translation,
            rotation: o.rotation,
        }
    }
}

impl From<MyNetTransform> for Transform {
    fn from(o: MyNetTransform) -> Self {
        Transform {
            translation: o.translation,
            rotation: o.rotation,
            ..default()
        }
    }
}

You can look at the types in the bevy-pigeon::types module for more examples.

Change Detection.

Change detection is an optimization were the sync messages are only sent if the component changes. It uses bevy's change detection. It is also enabled by default. The field cd on NetComp is what turns the change detection on or off. It is public and can be changed at any time, or you can use the .no_cd() method on construction.

It can cause issues if the last packet before a component stops changing is lost, or if a new player joins while a component is not changing.

Message table registration.

When calling app.sync_comp::<T, M>(&mut table, UDP) or any of its variants, it will not register type M into the table. It will actually register NetCompMsg<M>. This doesn't matter for the most part. It does mean that you can register type M into the table in addition to calling sync_comp. This also means that sending a message of type M will not be applied to the component on the other end.

Dynamically Creating Networked Entities.

Dynamically creating networked entities is possible, but it is not an included feature (for good reason). To do this, you should create a message type for spawning an entity of that type. For an example we will use an example of spawning a bullet. You would create a message type like so:

struct SpawnBullet {
    /// The id of the NetEntity.
    id: u64,
    /// The amount of damage the bullet does when it hits the target.
    damage: u32,
    // Add any other info that you need to know to construct the bullet.
}

Don't forget to register it in the MsgTable.

Then, when you want to spawn a bullet, just send it using the client or server. You do, of course do have to make a system that handles spawning the bullets on the other end.

fn spawn_bullets(
    mut commands: Commands,
    client: Res<carrier_pigeon::Client>,
) {
    for msg in client.recv::<SpawnBullet>().unwrap() {
        commands.spawn()
            .insert(NetEntity::new(msg.id)
            .insert(Damage(msg.damage))
            //.insert texture/collider/mesh
            //.insert other things bullet has
        ;
    }
}

The reason bevy-pigeon can't have this feature built in, is that you have to specify how to make a bullet. Especially the non-networked components. There is too much that is application specific to make it a generic feature.

Picking an id.

How do you pick the id for the NetEntity? It depends. bevy-pigeon uses a u64 for the id so that a random number would have a very low chance of collision. Using a random u64 is definitely a valid strategy. A random u64 has a 1/(1.84*10^19) or 5.42*10^-20 chance of colliding with any other existing id. This is incredibly small, but if it is critical for absolutely no collisions to happen, you could keep a list of existing ids and generate one that is guaranteed not to collide. An incrementing integer would also work if only one connected instance (likely the server) generates new ids.

What happens if there is a collision?

Though this is unspecified behavior, I imagine a number of things could happen depending on the circumstance; your game will probably not crash.

  • If the 2 entities with the id collisions both have the same synced component (i.e. they are both syncing transform), those components will likely get written to the same thing (If there are two bullets syncing transform, they will be synced to the same transform).
  • If the 2 entities with the id collision have different synced components (i.e. a bottle syncing water level and a bullet syncing transform), then this will probably behave normally.

Labels.

Networking systems added by bevy-pigeon are labeled with the NetLabel label.