0.1.0 2021 年 10 月 16 日

#2 in #fantastic

MIT/Apache

1KB

evoke - 简易网络代码

crates docs actions MIT/Apache loc

Evoke 提供了将网络功能添加到游戏引擎的构建块。

Evoke 支持

  • 具有授权服务器的客户端-服务器模型

客户端-服务器

对于客户端-服务器模型,Evoke 自动执行从服务器到客户端的状态复制,并进行 delta 压缩
以及从客户端到服务器的命令复制。

Evoke 对游戏中使用的组件不假设任何东西。
用户需要在服务器中注册 server::Descriptor 以及在客户端中注册 client::Descriptor 以复制所需的组件。对于可比较的组件和可序列化的组件,提供了泛型实现。
对于 服务器客户端

核心

Evoke 的核心提供了非常抽象的客户端和服务器会话,支持发送和接收类似于 ConnectAddPlayerSendInputUpdate 等命令,具有泛型有效载荷。Evoke 的核心作为单独的 crate evoke-core 提供,并从这个 crate 作为 evoke::core

evoke crate(这个)不同,evoke-core 不依赖于 edict,并且可以用于任何游戏引擎,即使是用除了 Rust 之外的编程语言编写的,只要打包成 FFI 准备的库。

使用方法

要开始使用 Evoke,只需在服务器和客户端分别配置并运行 ServerSystemClientSystem

服务器

要配置 ServerSystem,提供组件复制的描述符和 RemotePlayer 实现。

/// Information associated with player.
#[derive(serde::Deserialize)]
struct MyPlayerInfo;

/// Player input serializable representation.
#[derive(serde::Deserialize)]
struct MyPlayerInput;

/// This type drives player lifecycle and input processing.
struct MyRemotePlayer;

impl evoke::server::RemotePlayer for MyRemotePlayer {
    type Info = MyPlayerInfo;
    type Input = MyPlayerInput;

    fn accept(info: MyPlayerInfo, pid: evoke::PlayerId, world: &mut edict::World) -> eyre::Result<Self> {
        // Decide here whether accept new player based on `info` provided.
        // `Ok` signals that player is accepted.
        // `Err` signals that player is rejected.
        Ok(MyRemotePlayer)
    }

    fn apply_input(&mut self, entity: edict::EntityId, world: &mut edict::World, pack: MyPlayerInput) {
        // Input is associated with provided entity.
        // This code should transform input and put it where other systems would be able to consume it properly.
        // Usually it do the reverse of [`client::LocalPlayer::replicate`].
    }
}

/// Component that is own descriptor.
#[derive(Clone, Copy, PartialEq, serde::Serialize)]
pub struct MyComponent;

/// Prepare channel listener.
let listener = tokio::net::TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 12523)).await?;

/// Build server system.
let mut server = evoke::server::ServerSystem::builder()
    .with_descriptor::<MyComponent>()
    .with_player::<MyRemotePlayer>()
    .build(listener);

let mut world = edict::World::new();
let scope = scoped_arena::Scope::new();

// game loop
loop {
    //
    // Game loop tick
    //

    // Run server every tick.
    server.run(&mut world, &scope);
}

客户端

要配置 ClientSystem,提供组件复制的描述符和 LocalPlayer 实现。

/// Information associated with player.
#[derive(serde::Serialize)]
struct MyPlayerInfo;

/// Player input serializable representation.
#[derive(serde::Serialize)]
struct MyPlayerInput;

/// This type drives player lifecycle and input processing.
struct MyLocalPlayer;

impl<'a> evoke::client::LocalPlayerPack<'a> for MyLocalPlayer {
    type Pack = &'a MyPlayerInput;
}

impl evoke::client::LocalPlayer for MyLocalPlayer {
    type Query = &'static MyPlayerInput;

    fn replicate<'a>(item: &'a MyPlayerInput, _scope: &'a scoped_arena::Scope<'_>) -> &'a MyPlayerInput {
        item
    }
}

/// Component that is own descriptor.
#[derive(Clone, Copy, PartialEq, serde::Deserialize)]
pub struct MyComponent;

/// Build client system.
let mut client = evoke::client::ClientSystem::builder()
    .with_descriptor::<MyComponent>()
    .with_player::<MyLocalPlayer>()
    .build();

let mut world = edict::World::new();
let scope = scoped_arena::Scope::new();

client.connect((std::net::Ipv4Addr::LOCALHOST, 12523), &scope).await?;


let player_id: evoke::PlayerId = client.add_player(&MyPlayerInfo, &scope).await?;
// The player can controls all entities to which server attaches same `PlayerId` as component.

// game loop
loop {
    //
    // Game loop tick
    //

    // Run client every tick.
    client.run(&mut world, &scope);
}

许可证

许可协议为以下之一

任选其一。

贡献

除非您明确说明,否则根据 Apache-2.0 许可证定义的,任何有意提交以包含在作品中的贡献,都将如上所述双重许可,不附加任何额外的条款或条件。

无运行时依赖