โรงประมูลและตลาดผู้เล่นในเกม
รายการ การประมูล และเวลานับถอยหลังแบบสดในทุกไคลเอนต์ ขยายเวลาอัตโนมัติเมื่อประมูลในวินาทีสุดท้ายเพื่อกันการตัดหน้า มีซื้อทันทีและราคาขาย โดยเซิร์ฟเวอร์ของคุณตัดสินทุกการประมูล และเกมของคุณเก็บเหรียญกับไอเท็มไว้เอง
โรงประมูล ตลาดซื้อขาย หรือตลาดของผู้เล่นช่วยให้เศรษฐกิจในเกมหมุนไปได้ แต่จะรู้สึกยุติธรรมก็ต่อเมื่อทุกคนเห็นสิ่งเดียวกันในเวลาเดียวกัน คือเห็นราคาใหม่ทันทีที่มีคนประมูล เห็นเวลานับถอยหลังเท่ากันทุกหน้าจอ และไม่มีใครแย่งไปได้ในวินาทีสุดท้าย แพ็กเกจโอเพนซอร์ส @hibira-io/auction ให้สิ่งนี้บน Hibira โดยเซิร์ฟเวอร์ของคุณเองเป็นผู้ตัดสินทุกการประมูล
สิ่งที่ผู้เล่นได้รับ
- รายการที่ลงขาย เปลี่ยนราคา และปิดลง จะแสดงในทุกไคลเอนต์ที่เปิดอยู่โดยไม่ต้องรีเฟรช
- เวลานับถอยหลังตรงกับนาฬิกาของเซิร์ฟเวอร์ และการประมูลในวินาทีสุดท้ายจะเพิ่มเวลาแทนที่จะถูกตัดหน้า
- แจ้งทันทีเมื่อมีคนประมูลสูงกว่า ซื้อทันทีได้ และดูราคาขายล่าสุดของแต่ละไอเท็มได้

สร้างด้วย
- หนึ่งแชนแนลสาธารณะหรือส่วนตัวต่อหนึ่งโรงประมูล (
auction-main) มีอีเวนต์เดียวคือauction-updateที่ส่งข้อมูลรายการทั้งหมดพร้อมหมายเลขเวอร์ชัน - HTTP API (
trigger) จากเซิร์ฟเวอร์ของคุณ แต่ละการเปลี่ยนแปลงถูกส่งครั้งเดียว ไม่ว่าจะมีผู้เล่นดูอยู่กี่คน - @hibira-io/auction: ฝั่งเซิร์ฟเวอร์ตรวจการประมูล ขยายเวลาการประมูลที่ใกล้ปิด และปิดการขาย ฝั่งไคลเอนต์เก็บเวอร์ชันล่าสุดของแต่ละรายการและตามทันหลังเชื่อมต่อใหม่
ขั้นที่ 1: สร้างโรงประมูลบนเซิร์ฟเวอร์
ไอเท็มและเหรียญยังอยู่ในระบบของเกมคุณ โรงประมูลจะขอให้คุณกันเหรียญของผู้ประมูลไว้ คืนให้เมื่อมีคนประมูลสูงกว่า และโอนไอเท็มกับเหรียญเมื่อการประมูลปิด
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ขั้นที่ 2: เอนด์พอยต์สี่ตัว
ดึงผู้เล่นจากเซสชันของคุณเองเสมอ อย่าอ่านจากเนื้อหาคำขอ
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()ขั้นที่ 3: แสดงโรงประมูลในเกม
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) คืนราคาขายล่าสุดของไอเท็มแต่ละประเภทสำหรับกราฟราคา หลังเชื่อมต่อใหม่ โรงประมูลจะโหลดสแนปช็อตอีกครั้ง จึงไม่พลาดอะไร
ลองใช้
ตัวอย่างโรงประมูล เป็นตลาดในเกมที่สมบูรณ์ด้วย Next.js มีพ่อค้า NPC กระเป๋า ซื้อทันที การแจ้งเตือนเมื่อถูกประมูลสูงกว่า และราคาขาย รองรับเจ็ดภาษา เปิดสองหน้าต่างแล้วลองประมูลในหน้าต่างหนึ่ง
เคล็ดลับ
- ทุกการเปลี่ยนแปลงนับเป็นหนึ่งข้อความ ไม่ว่าจะมีผู้เล่นดูกี่คน แบ่งโรงประมูลขนาดใหญ่ตามหมวดหมู่หรือภูมิภาค (
name: 'weapons') เพื่อให้แต่ละไคลเอนต์โหลดเฉพาะส่วนที่แสดง - ถ้ามีเกมเซิร์ฟเวอร์หลายเครื่อง ให้ส่ง
storeที่อยู่บนฐานข้อมูลของคุณ การบันทึกเป็นแบบเปรียบเทียบก่อนเขียน สองเซิร์ฟเวอร์จึงรับการประมูลเดียวกันพร้อมกันไม่ได้ - มันโอนเหรียญและไอเท็มของเกมคุณเอง ไม่ใช่เงินจริง
- ไคลเอนต์ Unity, Godot หรือ Pusher SDK ใดก็ได้ สามารถติดตามแชนแนล
auction-mainเดียวกันและเรียกเอนด์พอยต์เดียวกันได้ สำหรับห้องและการจับคู่ ดูห้องเกมและล็อบบี้แบบผู้เล่นหลายคน