← Tình huống

Phòng chơi và sảnh chờ cho game nhiều người

Trò chơi nhóm, đố vui, board game và game bài: phòng vào bằng mã, chỗ ngồi, chủ phòng, trạng thái sẵn sàng và chat, xây trên kênh presence và sự kiện phía client mà không cần máy chủ game.

Trò chơi nhóm, đố vui, board game, game bài và phòng xem chung đều bắt đầu giống nhau: một mã phòng, danh sách ai đang ở trong phòng, một nút sẵn sàng và một chủ phòng bấm bắt đầu. Phần này không cần máy chủ game. Mỗi phòng là một kênh presence, và gói mã nguồn mở @hibira-io/lobby biến nó thành chỗ ngồi, chủ phòng, trạng thái sẵn sàng, mời ra, bắt đầu và chat trong phòng ngay trên trình duyệt.

Người chơi nhận được gì

  • Một phòng vào bằng mã hoặc đường link, chỗ ngồi đầy dần và báo rõ "phòng đã đầy".
  • Trạng thái sẵn sàng, huy hiệu chủ phòng và nút bắt đầu mà mọi người cùng thấy một lúc.
  • Chat trong phòng có "đang nhập…", và khi chủ phòng rời đi thì người tiếp theo lên thay.

Dùng những gì

  • Kênh presence (presence-) cho danh sách người trong mỗi phòng.
  • Sự kiện phía client (client-*) cho trạng thái sẵn sàng, cài đặt, bắt đầu, chat và các nước đi trong game, gửi thẳng giữa các trình duyệt.
  • HTTP API (GET /channels?filter_by_prefix=presence-room-&info=user_count) để có danh sách phòng mà không cần cơ sở dữ liệu.
  • @hibira-io/lobby, ghép những thứ trên thành logic của phòng.

Bước 1: cấp quyền vào phòng trên máy chủ

Endpoint cấp quyền quyết định ai được vào, ký thời điểm mỗi người chơi vào phòng và từ chối khi phòng đã đầy. Mọi người chơi sắp xếp phòng theo thời điểm đó, nên ai cũng thấy giống nhau về chỗ ngồi và chủ phòng:

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

Bước 2: vào phòng trên trình duyệt

Bật Cho phép sự kiện phía client trong cài đặt ứng dụng, rồi:

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 liệt kê người chơi theo thứ tự chỗ ngồi, mỗi người có ready và host. Thao tác của bạn hiện ra ngay, và những sự kiện chỉ chủ phòng được gửi sẽ bị bỏ qua nếu người khác gửi.

Bước 3: bắt đầu game

Chủ phòng bắt đầu khi mọi người đã sẵn sàng. Dữ liệu truyền vào start() đến tay mọi người chơi, ví dụ thời gian từng vòng, seed ngẫu nhiên hoặc địa chỉ máy chủ game của bạn:

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

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

Các nước đi trong game có thể là sự kiện phía client trên cùng kênh, room.channel.trigger('client-move', move), hoặc đi qua máy chủ game nếu game cần.

Tương tự với Laravel

Trả về thời điểm vào phòng từ callback của kênh presence, rồi dùng kênh của 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 });

Chơi thử

Ví dụ Reaction rooms là một game Next.js hoàn chỉnh: danh sách phòng, bốn chỗ ngồi, sẵn sàng, chat và một game phản xạ năm vòng. Chạy được với ứng dụng gói miễn phí.

Mẹo

  • Mỗi phòng chứa tối đa 100 người, giới hạn của kênh presence.
  • Mỗi sự kiện phía client tính là một tin nhắn, dù bao nhiêu người chơi nhận. Một ván ngắn cho bốn người chỉ gửi vài chục sự kiện.
  • Sau khi game bắt đầu, client được tin tưởng. Nếu gian lận là vấn đề, hãy kiểm tra kết quả trên máy chủ hoặc giữ trạng thái game trên máy chủ game, và chỉ dùng phòng làm sảnh chờ.
  • Làm game bằng Unity? Xem Sảnh chờ, ghép trận và bảng xếp hạng trong Unity.