게임 경매장과 플레이어 장터
등록, 입찰, 카운트다운을 모든 클라이언트에 실시간으로 보여 주고, 마감 직전 입찰은 자동 연장으로 스나이핑을 막으며, 즉시 구매와 낙찰가 추이를 지원합니다. 입찰 판단은 여러분의 서버가, 재화와 아이템은 게임이 관리합니다.
경매장, 거래소, 플레이어 장터는 게임 경제를 돌아가게 하지만, 모두가 같은 것을 같은 순간에 볼 때만 공정하게 느껴집니다. 누군가 입찰하는 순간 새 가격이 보이고, 모든 화면의 카운트다운이 같고, 마지막 1초에 빼앗기지 않아야 합니다. 오픈 소스 @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 auctions2단계: 엔드포인트 네 개
플레이어는 요청 본문이 아니라 반드시 여러분의 세션에서 가져오세요.
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)는 아이템 종류별 최근 낙찰가를 돌려주므로 가격 차트에 쓸 수 있습니다. 재연결 뒤에는 스냅샷을 다시 불러오므로 놓치는 변경이 없습니다.
직접 해 보기
경매장 예제는 NPC 상인, 가방, 즉시 구매, 입찰 알림, 낙찰가를 갖춘 완전한 Next.js 게임 장터이며 7개 언어를 지원합니다. 두 창에서 열고 한쪽에서 입찰해 보세요.
팁
- 보는 플레이어가 몇 명이든 변경 한 번은 메시지 하나입니다. 큰 경매장은 카테고리나 지역으로 나누면(
name: 'weapons') 각 클라이언트가 보여 줄 부분만 불러옵니다. - 게임 서버가 여러 대라면 데이터베이스 위의
store를 넘기세요. 저장은 비교 후 쓰기라서 두 서버가 같은 입찰을 함께 받아들일 수 없습니다. - 옮기는 것은 게임 안의 재화와 아이템이며, 실제 돈은 다루지 않습니다.
- Unity, Godot, 그 밖의 Pusher SDK 클라이언트도 같은
auction-main채널을 구독하고 같은 엔드포인트를 호출할 수 있습니다. 방과 매치메이킹은 멀티플레이어 게임 방과 로비를 참고하세요.