5 个版本 (3 个重大更新)

0.4.0 2022 年 2 月 7 日
0.3.0 2022 年 2 月 7 日
0.1.1 2021 年 10 月 20 日
0.1.0 2021 年 10 月 20 日
0.0.0 2021 年 10 月 20 日

#667 in 网络编程

MIT/Apache

105KB
2K SLoC

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(此 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 许可证定义的、旨在包含在本作品中的任何贡献,将按上述方式双授权,不附加任何额外的条款或条件。

依赖项

~3–16MB
~154K SLoC