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

bevy_replicon_attributes

通过属性控制扩展 bevy_replicon 的可见性

6 个版本 (破坏性更新)

0.6.0 2024年7月4日
0.5.0 2024年5月28日
0.4.0 2024年5月24日
0.3.0 2024年2月23日
0.1.0 2024年1月22日

#343 in 游戏开发

MIT/Apache

82KB
1K SLoC

Bevy Replicon Attributes

为服务器实体和事件扩展 bevy_replicon 的基于属性的可见性控制。

基本示例

use bevy::prelude::*;
use bevy_replicon::prelude::{ClientId, Replicated};
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, Replicated, 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);
    }
}

用法

设置

将 replicon 添加到您的服务器应用程序中。此软件包仅与 VisibilityPolicy::AllVisibilityPolicy::Whitelist 一起工作。

有关设置 renet 服务器的信息,请参阅 renet

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

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

在 replicon 插件之后将 VisibilityAttributesPlugin 添加到您的服务器应用程序中。如果使用了 VisibilityPolicy::Blacklist,则插件将引发恐慌。您必须指定一个 ReconnectPolicy

use bevy_replicon_attributes::prelude::*;

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

如果您选择 ReconnectPolicy::Repair,我们建议同时使用 bevy_replicon_repair 以在客户端保留复制状态。

定义属性

可以使用 VisibilityAttribute 派生属性,它需要 DefaultPartialEq。仅应使用零大小类型的 derive。

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

更复杂的属性应手动实现 VisibilityAttribute

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)
    }
}

此处定义的 inner_attribute_id 用于区分相同类型的属性实例。

向客户端添加属性

使用 ClientAttributes 系统参数向客户端添加属性。

客户端属性用于评估实体 VisibilityConditions,以确定实体是否应复制到客户端。

use bevy::prelude::*;
use bevy_replicon::prelude::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);
            }
        }
    }
}

默认客户端属性

所有客户端在每次连接时都会获得 GlobalClient 内置属性。

实体可见性

实体可见性由 VisibilityConditions 控制,这些是任意组合的 VisibilityAttributesnot()/and()/or() 逻辑。

实体可见性条件将与客户端属性列表进行比较,以确定实体是否可以被客户端看到。

为了方便,我们有一个 vis!() 宏,该宏生成新的 VisibilityCondition 组件。在 vis!() 宏内可以使用 any!()/all!()/none!() 宏,这些宏可以在 not()/and()/or() 之外使用。

空可见性条件始终评估为 false。如果您想为实体设置全局可见性,请使用在客户端连接时提供的内置 Global 属性。

以下是一个低级示例,说明其工作原理。在实践中,您只需要将 VisibilityAttributes 添加到客户端,并将 VisibilityCondition 组件添加到实体。此crate将负责将此信息转换为 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((Replicated, location));
}

以下是一些更复杂的可见性条件示例

// 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)
    ;

服务器事件

服务器事件的可视性可以通过使用ServerEventSender系统参数来控制。

服务器事件必须通过bevy_replicon进行注册。客户端将通过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兼容性

bevy_replicon bevy_replicon_attributes
0.27 0.6 - master
0.26 0.5
0.25 0.4
0.21 0.1 - 0.3

依赖

~39–75MB
~1.5M SLoC