← 使用场景

多人游戏房间与大厅

派对、问答、桌游和卡牌游戏:用代码加入的房间、座位、房主、准备状态和聊天,建立在 presence 频道和客户端事件上,不需要游戏服务器。

派对游戏、问答、桌游和卡牌游戏、一起看的房间,开头都一样:一个房间代码、谁在里面的名单、一个“准备”按钮,还有一位按下开始的房主。这部分不需要游戏服务器。房间就是一个 presence 频道,开源包 @hibira-io/lobby 会在浏览器里把它变成座位、房主、准备状态、踢人、开始和房间聊天。

玩家得到什么

  • 用代码或链接就能加入的房间,座位一个个坐满,满了会清楚显示“房间已满”。
  • 准备状态、房主标记和开始按钮,所有人同时看到。
  • 有“正在输入…”的房间聊天,房主离开时自动换人。

用到的功能

  • Presence 频道(presence-),记录每个房间里有谁。
  • 客户端事件(client-*),准备状态、设置、开始、聊天和游戏操作都在浏览器之间直接传递。
  • HTTP API(GET /channels?filter_by_prefix=presence-room-&info=user_count),不用数据库就能做房间列表。
  • @hibira-io/lobby,把上面这些组合成房间的逻辑。

步骤 1:在服务器授权房间

授权端点决定谁能加入、签上每位玩家加入的时间,房间满了就拒绝。所有玩家都用这个时间排顺序,所以大家对座位和房主的看法一致:

import Pusher from 'pusher';
import { authorizeRoom, listRooms, RoomFullError } from '@hibira-io/lobby/server';

const hibira = new Pusher({ appId, key, secret, host: 'ws.hibira.io', useTLS: true });

app.post('/rooms/auth', requireLogin, express.urlencoded({ extended: false }), async (req, res) => {
  try {
    res.json(await authorizeRoom(hibira, {
      socketId: req.body.socket_id,
      channel: req.body.channel_name,
      userId: req.user.id,
      userInfo: { name: req.user.name },
      capacity: 4,
    }));
  } catch (err) {
    res.status(err instanceof RoomFullError ? 403 : 500).end();
  }
});

// Room browser: [{ id: 'KQTZ', channel: 'presence-room-KQTZ', members: 3 }]
app.get('/rooms', async (req, res) => res.json(await listRooms(hibira)));

步骤 2:在浏览器加入房间

先在应用设置里打开 允许客户端事件,然后:

import { Room } from '@hibira-io/lobby';

const room = Room.join(pusher, 'presence-room-KQTZ', { capacity: 4 });

room.on('members', ({ players, host }) => renderSeats(players, host));
room.on('message', (m) => chat.append(m.info.name, m.text));
room.on('full', () => show('This room is full'));

readyButton.onclick = () => room.setReady(!room.me.ready);
chatForm.onsubmit = () => room.send(input.value);
input.oninput = () => room.sendTyping();

room.players 按座位顺序列出玩家,每位都有 ready 和 host。自己的操作会立刻显示;只有房主能发的事件,如果是别人发的会被忽略。

步骤 3:开始游戏

大家都准备好后由房主开始。传给 start() 的数据会发到每位玩家,例如每回合的时间、随机种子或游戏服务器的地址:

if (room.isHost && room.allReady) room.start({ seed: 1234, rounds: 5 });

room.on('start', ({ data }) => game.begin(data));

游戏中的操作可以用同一个频道的客户端事件 room.channel.trigger('client-move', move),需要游戏服务器的游戏也可以改走服务器。

Laravel 的写法

在 presence 频道的 callback 返回加入时间,再使用 Echo.join() 的频道:

// routes/channels.php
Broadcast::channel('room.{code}', function ($user, $code) {
    return ['id' => $user->id, 'name' => $user->name, 'joined_at' => (int) floor(microtime(true) * 1000)];
});
const room = new Room(Echo.join(`room.${code}`).subscription, { capacity: 4 });

试玩一下

Reaction rooms 示例 是一个完整的 Next.js 游戏:房间列表、四个座位、准备、聊天,还有五回合的反应速度游戏。免费方案的应用就能跑。

小提醒

  • 一个房间最多 100 人,也就是 presence 频道的上限。
  • 每个客户端事件算一条消息,不管有几位玩家收到。四个人玩一局短游戏大约几十条。
  • 游戏开始后是信任客户端的。如果作弊是个问题,请在服务器检查结果,或把游戏状态放在游戏服务器上,房间只用来做大厅。
  • 用 Unity 开发?请看 Unity 大厅、匹配与排行榜。