← Tình huống

Nhà đấu giá và chợ người chơi trong game

Món đồ, lượt trả giá và đồng hồ đếm ngược trực tiếp trên mọi client, tự gia hạn khi trả giá ở giây cuối để chống bắn tỉa, có mua ngay và giá đã bán; máy chủ của bạn quyết định mọi lượt trả giá, game của bạn giữ tiền và vật phẩm.

Nhà đấu giá, sàn giao dịch hay chợ người chơi giữ cho nền kinh tế trong game vận hành, nhưng chỉ công bằng khi mọi người cùng thấy một thứ vào cùng một lúc: thấy giá mới ngay khi có người trả giá, đồng hồ đếm ngược giống nhau trên mọi màn hình, và không ai giành được món đồ ở giây cuối. Gói mã nguồn mở @hibira-io/auction mang điều đó lên Hibira, với máy chủ của chính bạn quyết định mọi lượt trả giá.

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

  • Món đồ được đăng, đổi giá và kết thúc hiện trên mọi client đang mở mà không cần tải lại.
  • Đồng hồ đếm ngược khớp với đồng hồ máy chủ, và lượt trả giá ở những giây cuối sẽ cộng thêm thời gian thay vì bị "bắn tỉa".
  • Thông báo ngay khi bị trả giá cao hơn, mua ngay, và giá bán gần đây của từng món.
Ví dụ nhà đấu giá: một người chơi trả giá Cung thợ săn, cửa sổ thứ hai trả cao hơn ngay sau đó, và người chơi đầu tiên thấy giá mới cùng thông báo.
Ví dụ nhà đấu giá: một người chơi trả giá Cung thợ săn, cửa sổ thứ hai trả cao hơn ngay sau đó, và người chơi đầu tiên thấy giá mới cùng thông báo.

Xây dựng bằng

  • Một kênh công khai hoặc riêng tư cho mỗi nhà đấu giá (auction-main), chỉ có một sự kiện auction-update mang toàn bộ món đồ và số phiên bản.
  • HTTP API (trigger) từ máy chủ của bạn: mỗi thay đổi chỉ gửi một lần, dù có bao nhiêu người chơi đang xem.
  • @hibira-io/auction: phía máy chủ kiểm tra lượt trả giá, gia hạn phiên sắp kết thúc và chốt giao dịch; phía client giữ bản mới nhất của mỗi món và bắt kịp sau khi kết nối lại.
Người chơi AMáy chủ của bạnHibiraNgười chơi BPOST /auction/:id/bid120 vànghouse.bid() kiểm tra lượt trả giá và giữ vàngauction-updategiá 120, A dẫn đầuauction-updateauction-update"Trả giá 126"
Lượt trả giá đến máy chủ của bạn, Hibira báo cho mọi người chơi

Bước 1: tạo nhà đấu giá trên máy chủ

Game của bạn vẫn giữ vật phẩm và tiền tệ. Nhà đấu giá nhờ bạn giữ tiền của người trả giá, trả lại khi có người trả cao hơn, và chuyển vật phẩm cùng tiền khi phiên kết thúc:

import Pusher from 'pusher';
import { createAuctionHouse } from '@hibira-io/auction/server';

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

const house = createAuctionHouse({
  pusher: hibira,
  name: 'main',
  extendWithin: 30000, // a bid in the last 30 s adds time
  reserve: async ({ userId, amount }) => {
    if (!(await wallet.hold(userId, amount))) throw new Error('not enough gold');
  },
  release: async ({ userId, amount }) => wallet.unhold(userId, amount),
  settle: async ({ listing }) => game.deliver(listing), // idempotent by listing.id
});
house.start(); // closes and settles ended auctions

Bước 2: bốn endpoint

Luôn lấy người chơi từ phiên đăng nhập của bạn, không bao giờ từ nội dung yêu cầu:

app.get('/auction', async (req, res) => res.json(await house.snapshot()));

app.post('/auction/:id/bid', requireLogin, async (req, res) => {
  try {
    res.json(await house.bid({ listingId: req.params.id, bidderId: req.user.id, bidder: { name: req.user.name }, amount: req.body.amount }));
  } catch (err) {
    res.status(err.status ?? 500).json({ error: err.code });
  }
});
// The same for house.buyout() and house.list()

Bước 3: hiển thị nhà đấu giá trong game

import { AuctionHouse } from '@hibira-io/auction';

const house = AuctionHouse.open(pusher, 'main', {
  load: () => fetch('/auction').then((r) => r.json()),
});

house.on('change', ({ listings }) => render(listings));
setInterval(() => listings.forEach((l) => showTimeLeft(l, house.timeLeft(l))), 250);

bidButton.onclick = () => fetch(`/auction/${l.id}/bid`, { method: 'POST', body: JSON.stringify({ amount: l.minBid }) });

house.prices(type) trả về giá bán gần đây của một loại vật phẩm để vẽ biểu đồ giá. Sau khi kết nối lại, nhà đấu giá tải lại ảnh chụp trạng thái nên không bỏ lỡ gì.

Dùng thử

Ví dụ nhà đấu giá là một chợ game hoàn chỉnh bằng Next.js: thương nhân NPC, túi đồ, mua ngay, thông báo bị trả giá cao hơn và giá đã bán, bằng bảy ngôn ngữ. Mở nó trong hai cửa sổ và trả giá ở một bên.

Mẹo

  • Mỗi thay đổi là một tin nhắn, dù có bao nhiêu người chơi đang xem. Chia nhà đấu giá lớn theo danh mục hoặc khu vực (name: 'weapons') để mỗi client chỉ tải phần nó hiển thị.
  • Khi có nhiều máy chủ game, hãy truyền một store trên cơ sở dữ liệu của bạn; việc lưu là so sánh rồi mới ghi, nên hai máy chủ không thể cùng chấp nhận một lượt trả giá.
  • Nó chuyển tiền tệ và vật phẩm trong game của bạn, không phải tiền thật.
  • Client Unity, Godot hay bất kỳ Pusher SDK nào cũng có thể theo dõi cùng kênh auction-main và gọi cùng các endpoint. Về phòng và ghép trận, xem phòng game nhiều người chơi và sảnh chờ.