#visibility #ecs #networking #gamedev #interest-management

bevy_replicon_attributes

Extends bevy_replicon with attribute-based visibility control

3 releases (breaking)

0.3.0 Feb 23, 2024
0.2.0 Jan 27, 2024
0.1.0 Jan 22, 2024

#646 in Game dev

Download history 5/week @ 2024-01-21 135/week @ 2024-02-18 42/week @ 2024-02-25 7/week @ 2024-03-03 5/week @ 2024-03-10 56/week @ 2024-03-31

62 downloads per month

MIT/Apache

82KB
1K SLoC

Bevy Replicon Attributes

Extends bevy_replicon with attributes-based visibility control for server entities and events.

Basic example

use bevy::prelude::*;
use bevy_replicon::prelude::{ClientId, Replication};
use bevy_replicon_attributes::prelude::*;

#[derive(Component)]
struct Bat;

#[derive(Event)]
struct SpawnBat;
#[derive(Event, Copy, Clone)]
struct BatAlert;
#[derive(Event)]
struct GainedNightVision(ClientId);

#[derive(VisibilityAttribute, Default, PartialEq)]
struct HasNightVision;
#[derive(VisibilityAttribute, Default, PartialEq)]
struct IsAwake;
#[derive(VisibilityAttribute, Default, PartialEq)]
struct HatesBats;

fn spawn_bats(
    mut commands : Commands,
    mut events   : EventReader<SpawnBat>,
    mut sender   : ServerEventSender<BatAlert>,
    attributes   : ClientAttributes,
){
    for _ in events.read()
    {
        // Entity
        commands.spawn((Bat, Replication, vis!(HasNightVision)));

        // Server event
        sender.send(&attributes, BatAlert, vis!(all!(HasNightVision, IsAwake, HatesBats)));
    }
}

fn gain_night_vision(
    mut events     : EventReader<GainedNightVision>,
    mut attributes : ClientAttributes,
){
    for client_id in events.read()
    {
        // Client attribute
        attributes.add(client_id, HasNightVision);
    }
}

Usage

Setup

Add replicon to your server app. This crate only works with VisibilityPolicy::All and VisibilityPolicy::Whitelist.

See renet for how to set up a renet server.

use bevy::prelude::*;
use bevy_replicon::prelude::*;

app.add_plugins(bevy::time::TimePlugin)  //required by bevy_renet
    .add_plugins(ReplicationPlugins
        .build()
        .disable::<ClientPlugin>()
        .set(ServerPlugin{
            visibility_policy: VisibilityPolicy::Whitelist,
            ..Default::default(),
        })
    );

Add VisibilityAttributesPlugin to your server app after the replicon plugins. The plugin will panic if you used VisibilityPolicy::Blacklist. You must specify a ReconnectPolicy:

use bevy_replicon_attributes::prelude::*;

app.add_plugins(VisibilityAttributesPlugin{ reconnect_policy: ReconnectPolicy::Reset });

If you choose ReconnectPolicy::Repair, we recommend also using bevy_replicon_repair for preserving replicated state on clients.

Define attributes

Attributes can be derived with VisibilityAttribute, which requires Default and PartialEq. Only zero-sized types should use this derive.

#[derive(VisibilityAttribute, Default, PartialEq)]
struct InStartingArea;

More complex attributes should implement VisibilityAttribute manually.

struct InLocation(x: u32, y: u32);

impl VisibilityAttribute for InLocation
{
    fn inner_attribute_id(&self) -> u64
    {
        ((self.x as u64) << 32) + (self.y as u64)
    }
}

The inner_attribute_id defined here is used to differentiate attribute instances of the same type.

Add attributes to a client

Add attributes to clients with the ClientAttributes system parameter.

Client attributes are used when evaluating entity VisibilityConditions to determine if entities should be replicated to a client.

use bevy::prelude::*;
use bevy_renet::renet::ServerEvent;
use bevy_replicon_attributes::prelude::*;

#[derive(VisibilityAttribute, Default, PartialEq)]
struct IsDisconnected;

fn update_visibility_on_connect_events(
    mut server_events : EventReader<ServerEvent>,
    mut attributes    : ClientAttributes,
){
    for event in server_events.read()
    {
        match event
        {
            ServerEvent::ClientConnected{ id } =>
            {
                attributes.remove(id, IsDisconnected);
                attributes.add(id, InStartingArea);
            }
            ServerEvent::ClientDisconnected{ id, _ } =>
            {
                attributes.add(id, IsDisconnected);
            }
        }
    }
}

Default client attributes

All clients are given the Global and Client builtin attributes each time they connect.

Entity visibility

Entity visibility is controlled by VisibilityConditions, which are arbitrary combinations of VisibilityAttributes and not()/and()/or() logic.

Entity visibility conditions are evaluated against client attribute lists to determine if entities can be seen by clients.

For convenience we have a vis!() macro which produces new VisibilityCondition components. The any!()/all!()/none!() macros can be used inside the vis!() macro in addition to not()/and()/or().

An empty visibility condition always evaluates to false. If you want global visibility for an entity, use the builtin Global attribute that is given to clients when they connect.

Here is a low-level example how it works. In practice you only need to add VisibilityAttributes to clients and VisibilityCondition components to entities. This crate will take care of translating that information into entity visibility within bevy_replicon.

use bevy::prelude::*;
use bevy_replicon_attributes::prelude::*;

fn entity_demo(
    mut commands   : Commands,
    mut attributes : ClientAttributes,
){
    let client_id = ClientId::from_raw(0u64);

    // Add location to client.
    attributes.add(client_id, InLocation(0, 20));

    // Make location condition.
    let location = vis!(InLocation(0, 20));

    // Evaluate condition.
    let client_attributes = attributes.get(client_id).unwrap();
    assert!(location.evaluate(|a| client_attributes.contains(&a)));

    // Spawn entity.
    commands.spawn((Replication, location));
}

Here are examples of more complex visibility conditions:

// Basic
vis!();
vis!(A);
vis!(not(B));
vis!(and(A, B));
vis!(or(A, B));
vis!(and(A, not(B)));

// Composition
vis!(and(A, vis!(B)));

// Helpers
vis!(any!(A, B, C));   // vis!(or(A, or(B, C)))
vis!(all!(A, B, C));   // vis!(and(A, and(B, C)))
vis!(none!(A, B, C));  // vis!(not(or(A, or(B, C)))))

// Modification
vis!()
    .and(A)                           // vis!(A)
    .or(B)                            // vis!(or(A, B))
    .replace(or(A, B), and(C(1), D))  // vis!(and(C(1), D))
    .replace_type::<C>(E(2))          // vis!(and(E(2), D))
    .remove(E(2))                     // vis!(D)
    ;

Server events

Visibility of server events can be controlled with the ServerEventSender system parameter.

Server events must be registered with bevy_replicon. Clients will receive server events with EventReader<T>.

use bevy::prelude::*;
use bevy_replicon::prelude::*;
use bevy_replicon_attributes::prelude::*;

#[derive(Event, Copy, Clone)]
struct E;

fn setup(app: &mut App)
{
    // Replicon server event registration
    app.add_server_event::<E>(EventType::Ordered);
}

fn send_event(mut sender: ServerEventSender<E>, attributes: ClientAttributes)
{
    sender.send(&attributes, E, vis!(any!(Client::from(1), Client::from(2), Client::from(3))));
}

bevy_replicon compatability

bevy_replicon bevy_replicon_attributes
0.21 0.1 - master

Dependencies

~21–59MB
~1M SLoC