#websocket-server #multiplayer #browser #ez #text-based #player

ezbrowsergameserver

使用 WebSocket 轻松制作多人在线浏览器游戏

4 个版本

0.2.2 2023 年 12 月 3 日
0.2.1 2023 年 10 月 31 日
0.2.0 2023 年 10 月 30 日
0.1.0 2023 年 10 月 29 日

WebSocket 中排名第 76

每月下载 46

MIT/Apache

19KB
262

ezbrowsergameserver

你有没有想过创建并自己托管一个简单的、通常是基于文本的多人在线浏览器游戏?

我也是!这是一个小型库,希望能使这个过程尽可能简单 :)

入门指南

在你做任何事情之前,要知道 ezbrowsergameserver 不是一个为你的网站提供的服务器 - 它只提供 WebSocket,这将用于更新并同步所有玩家。

如果你已安装了 python3,你可以使用脚本 examples/server.sh 作为快速的无配置 Web 服务器,运行在 0.0.00:8080 上。

在这个示例中,你将使用 0.0.0.0:8080/00_min.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Min - ezbrowsergameserver</title>
    <meta name="color-scheme" content="light dark">
    <script>
      var con = undefined;
      function createLobby() {
        joinLobby("new");
      }
      function joinLobby(id) {
        let ip;
        // if possible, use the same host that is hosting this html file.
        // assume localhost if not possible (i.e. opened the file directly)
        if (window.location.hostname) {
          ip = window.location.hostname;
        } else {
          ip = "0.0.0.0";
        }
        // connect to the websocket
        con = new WebSocket("ws://" + ip + ":8081");
        // handle incoming messages
        con.onmessage = (e) => {
          let msg = e.data;
          // allow the server to update the clients html
          bodyDiv.innerHTML = msg;
        };
        // when the websocket finishes connecting...
        con.onopen = () => {
          // join a lobby
          con.send(id);
        };
      }
    </script>
  </head>
  <body>
    <div id="bodyDiv">
      <p>Lobby ID: <input id="lobbyId"></p>
      <button onclick=createLobby()>Create new lobby</button>
      <button onclick=joinLobbyPressed()>Join lobby by ID</button>
      <script>
        function joinLobbyPressed() {
          joinLobby(lobbyId.value);
        }
      </script>
    </div>
  </body>
</html>

这允许你通过在文本输入中输入其 ID 来创建一个新的游戏室或加入一个现有的游戏室。使用 WebSocket 连接到你的游戏服务器 - 你即将使用此库创建的服务器。

但在我们开始制作你的第一个 ezbrowsergame 之前,你需要一个游戏室。

游戏室是游戏开始前人们聚集的地方。在游戏室中,人们可以更改游戏模式、游戏设置或你在游戏室中实现的其他任何内容。游戏室也是全局状态的地方,因为游戏结束后它仍然存在。游戏室可以通过其 ID 访问,格式为十六进制字符串({id:X})。

此示例将在玩家加入/离开游戏室时每次启动“游戏”。“游戏”将持续一秒钟并显示游戏室中的新玩家数量。

以下是示例中所需的导入项

use std::time::Instant;
use ezbrowsergameserver::prelude::*;

首先,为您的全局状态创建一个结构体

struct GlobalState {
    player_count_changed: bool,
}

然后,为您的游戏创建一个

struct PlayerCountGame(Option<Instant>);

现在,为您的 GlobalState 结构体实现 LobbyState

#[async_trait]
impl LobbyState for GlobalState {
    type PlayerState = ();
    fn new() -> Self {
        Self {
            player_count_changed: false,
        }
    }
    fn new_player() -> Self::PlayerState {
        ()
    }
    async fn player_joined(_id: usize, lobby: &mut Lobby<Self>, _player: PlayerIndex) {
        lobby.state.player_count_changed = true;
    }
    async fn player_leaving(_id: usize, lobby: &mut Lobby<Self>, _player: PlayerIndex) {
        lobby.state.player_count_changed = true;
    }
    async fn lobby_update(id: usize, lobby: &mut Lobby<Self>) -> Option<Box<dyn GameState<Self>>> {
        if lobby.reset {
            lobby.reset = false;
            // show lobby screen to all clients
            for (i, player) in lobby.players_mut().enumerate() {
                player
                    .send(format!(
                        "<h1>You are player #{}</h1><p>Lobby ID: {id:X}</p>",
                        i + 1
                    ))
                    .await;
            }
        }
        for player in lobby.players_mut() {
            if let Some(_) = player.get_msg().await {
                // if we don't try to get_msg, we don't detect player disconnects
            }
        }
        if lobby.state.player_count_changed {
            lobby.state.player_count_changed = false;
            // start the "game"
            return Some(Box::new(PlayerCountGame(None)));
        }
        None
    }
}

然后,为您的 PlayerCountGame 实现代码 GameState

#[async_trait]
impl GameState<GlobalState> for PlayerCountGame {
    async fn update(&mut self, lobby: &mut Lobby<GlobalState>) -> bool {
        // game starts, update all clients
        if self.0.is_none() {
            self.0 = Some(Instant::now());
            let c = lobby.players().len();
            for player in lobby.players_mut() {
                player.send(format!("<h1>There are {c} players</h1>")).await;
            }
        }
        // game ends after 1 seconds
        self.0.is_some_and(|start| start.elapsed().as_secs() >= 1)
    }
    async fn player_leaving(&mut self, lobby: &mut Lobby<GlobalState>, _player: PlayerIndex) {
        lobby.state.player_count_changed = true;
    }
}

最后,添加您的 main 函数

#[tokio::main]
async fn main() {
    host::<GlobalState>("0.0.0.0:8081").await;
}

这就完成了 - 我们已经创建了一个“游戏”。

如果您想尝试一下,请转到 examples/ 目录,运行 ./server.sh,然后运行 cargo run --example 00_min,然后在浏览器中打开 0.0.0.0:8080/00_min.html 并创建一个新的大厅。

快速入门

cargo new my_project
cd my_project/
cargo add ezbrowsergameserver
cargo add tokio --features macros
echo 'use ezbrowsergameserver::prelude::*;

#[tokio::main]
async fn main() {
    host::<ToDo>("0.0.0.0:8081").await;
}' > src/main.rs

请随意通过将示例文件复制到 src/main.rs 并更改不同的内容来实验示例文件 :)

依赖项

~3.5–5.5MB
~95K SLoC