مقدمه
ساخت یک بازی آنلاین چندنفره فقط به طراحی رابط کاربری، گرافیک یا منطق بازی محدود نیست. مهمترین چالش در این نوع پروژهها، همگامسازی سریع و قابلاعتماد وضعیت بازی میان کاربران، مدیریت اتصال بازیکنان، ساخت اتاقهای بازی و جلوگیری از تقلب است.
Colyseus یک فریمورک متن باز برای ساخت بازیهای چندنفره و Real-time است که عمدتاً روی Node.js و TypeScript تمرکز دارد. این ابزار به شما کمک میکند بدون پیادهسازی دستی تمام پیچیدگیهای WebSocket، معماری اتاقها، همگامسازی state و مدیریت بازیکنان، بکاند یک بازی آنلاین را توسعه دهید.
به زبان ساده، Colyseus زیرساخت لازم برای ساخت سرور بازیهای آنلاین را فراهم میکند، از بازی های ساده مثل دوز و کارت گرفته تا بازی های رقابتی، همکاری محور و اپلیکیشنهای Real-time.
Colyseus چیست؟
Colyseus یک فریمورک Multiplayer Game Server برای Node.js است که توسعهدهنده با استفاده از آن میتواند سرورهای بازی چندنفره مبتنی بر WebSocket بسازد.
مفهوم اصلی در Colyseus، یک Room یا اتاق است. هر اتاق میتواند نماینده یک مسابقه، یک لابی، یک میز بازی، یک کانال گفتگو، یک جلسه همکاری یا حتی یک فضای مجازی باشد.
برای مثال:
- هر مسابقه فوتبال آنلاین یک Room مستقل است.
- هر میز بازی Poker یک Room جداگانه محسوب میشود.
- هر مسابقه Battle Royale میتواند یک اتاق باشد.
- یک لابی برای پیدا کردن بازیکن نیز یک Room است.
- یک جلسه وایتبورد یا همکاری تیمی بلادرنگ نیز میتواند بهشکل اتاق پیادهسازی شود.
Colyseus ارتباط بین کلاینت و سرور را از طریق WebSocket مدیریت میکند و مکانیزم مناسبی برای ارسال پیام، نگهداری state و ارسال تغییرات state به کاربران متصل فراهم میسازد.
چرا برای بازیهای آنلاین به یک فریمورک مانند Colyseus نیاز داریم؟
اگر بخواهید یک بازی چندنفره را فقط با WebSocket خام توسعه دهید، باید بسیاری از موضوعات مهم را خودتان طراحی و پیادهسازی کنید:
- مدیریت اتصال و قطع اتصال کاربران
- ساخت، حذف و جستجوی اتاقها
- تخصیص بازیکن به مسابقه مناسب
- مدیریت ظرفیت اتاق
- ارسال پیامهای Real-time
- همگامسازی وضعیت بازی
- جلوگیری از تداخل دستورات بازیکنان
- مدیریت زمان بازی و Game Loop
- بازیابی وضعیت پس از اتصال مجدد
- کنترل اعتبار پیامهای دریافتی
- مقیاسپذیری سرور
- انتقال کاربران میان چند instance سرور
Colyseus بسیاری از این مسئولیتها را با ساختاری استاندارد و قابلتوسعه در اختیار شما قرار میدهد. بنابراین تیم توسعه میتواند روی قوانین بازی، تجربه کاربری و منطق کسبوکار تمرکز بیشتری داشته باشد.
مهمترین ویژگیهای Colyseus
1. معماری مبتنی بر Room
هسته اصلی Colyseus بر پایه مفهوم Room طراحی شده است.
هر Room یک فضای مستقل برای کاربران متصل است که میتواند شامل موارد زیر باشد:
- بازیکنان یک مسابقه
- وضعیت فعلی بازی
- تایمرها
- قوانین اتاق
- ظرفیت بازیکنان
- پیامهای اختصاصی
- دادههای موقت هر مسابقه
- وضعیت برنده، بازنده یا پایان بازی
برای نمونه، در یک بازی دوز، هر اتاق ممکن است فقط دو بازیکن داشته باشد. در یک بازی نقشآفرینی آنلاین، هر اتاق میتواند شامل دهها یا صدها بازیکن باشد.
نمونه اولیه از تعریف یک اتاق در TypeScript:
import { Room, Client } from "colyseus";
export class GameRoom extends Room {
maxClients = 2;
onCreate(options: unknown) {
console.log("Game room created", options);
}
onJoin(client: Client, options: unknown) {
console.log(${client.sessionId} joined the room);
}
onLeave(client: Client) {
console.log(${client.sessionId} left the room);
}
onDispose() {
console.log("Game room disposed");
}
}
در این ساختار، چرخه عمر اتاق با متدهایی مانند onCreate، onJoin، onLeave و onDispose مدیریت میشود.
2. همگامسازی خودکار State
یکی از بهترین قابلیتهای Colyseus، سیستم State Synchronization است.
در بازیهای چندنفره، سرور باید وضعیت فعلی بازی را به کلاینتها منتقل کند. برای مثال:
- موقعیت بازیکنان
- امتیاز هر کاربر
- وضعیت کارتها
- زمان باقیمانده
- وضعیت نقشه
- نوبت بازیکن فعلی
- سلامت کاراکتر
- آیتمهای موجود در محیط
اگر این دادهها بهصورت کامل و در هر لحظه برای همه کاربران ارسال شوند، مصرف شبکه بهشدت افزایش مییابد. Colyseus با استفاده از سیستم Schema، فقط تغییرات state را به کلاینتها ارسال میکند.
این موضوع بهخصوص برای بازیهایی که تعداد پیام بالا یا بازیکنان متعدد دارند، اهمیت زیادی دارد.
نمونهای از تعریف state:
import { Schema, type, MapSchema } from "@colyseus/schema";
class Player extends Schema {
@type("number")
x = 0;
@type("number")
y = 0;
@type("number")
score = 0;
}
export class GameState extends Schema {
@type({ map: Player })
players = new MapSchema<Player>();
@type("string")
currentTurn = "";
}
در این مثال:
- هر بازیکن موقعیت x و y دارد.
- امتیاز بازیکن در score ذخیره میشود.
- بازیکنان در MapSchema نگهداری میشوند.
- تغییرات این دادهها بهصورت بهینه برای کلاینتها ارسال میشود.
3. ارتباط دوطرفه مبتنی بر WebSocket
Colyseus برای ارتباط لحظهای میان کلاینت و سرور از WebSocket استفاده میکند.
برخلاف HTTP که در آن کلاینت باید برای دریافت اطلاعات درخواست ارسال کند، در WebSocket ارتباط دائمیتری میان سرور و کاربر برقرار میشود. بنابراین سرور میتواند بدون نیاز به درخواست جدید از سمت کلاینت، تغییرات بازی را بلافاصله ارسال کند.
این قابلیت برای موارد زیر ضروری است:
- حرکت کاراکترها
- بهروزرسانی آنی امتیاز
- نمایش پیامهای حین بازی
- شروع همزمان مسابقه
- اعلام برد یا باخت
- نمایش حرکات بازیکنان دیگر
- مدیریت تایمر
- اجرای مسابقات زنده
4. مدیریت Lifecycle اتاق و بازیکنان
در Colyseus برای هر اتاق یک چرخه عمر مشخص وجود دارد. شما میتوانید در هر مرحله منطق اختصاصی خودتان را اجرا کنید.
مهمترین lifecycle hookها عبارتاند از:
| متد | کاربرد |
| onCreate | هنگام ایجاد اتاق اجرا میشود |
| onAuth | برای اعتبارسنجی و احراز هویت کاربر استفاده میشود |
| onJoin | هنگام ورود بازیکن به اتاق اجرا میشود |
| onLeave | هنگام خروج یا قطع اتصال کاربر اجرا میشود |
| onDispose | هنگام حذف کامل اتاق اجرا میشود |
نمونه اعتبارسنجی کاربر با JWT:
import { Room, Client } from "colyseus";
export class SecureGameRoom extends Room {
async onAuth(client: Client, options: { token?: string }) {
if (!options.token) {
throw new Error("Unauthorized: token is required");
}
// اعتبارسنجی JWT یا بررسی کاربر در دیتابیس
return {
userId: "user-123",
username: "player-one",
};
}
onJoin(client: Client) {
console.log("Authenticated user joined:", client.auth.userId);
}
}
این الگو برای بازیهایی که دارای حساب کاربری، کیف پول، پرداخت، رتبهبندی یا سیستم جایزه هستند اهمیت بالایی دارد.
5. ارسال و دریافت پیامهای سفارشی
همه دادهها نباید در State ذخیره شوند. برخی اطلاعات فقط یک رویداد لحظهای هستند و نیازی به نگهداری در وضعیت دائمی بازی ندارند.
مثالها:
- بازیکن دکمه آمادهبودن را زده است.
- کاربر درخواست حرکت داده است.
- کاربر پیام چت ارسال کرده است.
- بازیکن درخواست استفاده از یک آیتم را داده است.
- کاربر قصد شلیک دارد.
- بازیکن درخواست خروج از مسابقه را ثبت کرده است.
در Colyseus میتوانید برای این موارد از پیامهای سفارشی استفاده کنید.
دریافت پیام در سرور
import { Room, Client } from "colyseus";
export class GameRoom extends Room {
onCreate() {
this.onMessage("move", (client: Client, message: { x: number; y: number }) => {
console.log("Move received:", client.sessionId, message);
// اعتبارسنجی حرکت
// اعمال حرکت روی state
});
}
}
ارسال پیام از کلاینت
room.send("move", {
x: 10,
y: 5,
});
ارسال پیام از سرور به کاربران
this.broadcast("game-over", {
winnerId: "player-1",
reason: "score-limit-reached",
});
معماری Colyseus چگونه کار میکند؟
در معماری استاندارد Colyseus، چند بخش اصلی وجود دارد:
Client Application
│
│ WebSocket
▼
Colyseus Server
│
├── Matchmaking
├── Room Management
├── State Synchronization
├── Authentication
└── Game Logic
│
▼
Database / Cache / Queue
اجزای مهم این معماری
1. کلاینت یا Client
کلاینت میتواند با فناوریهای مختلف توسعه داده شود:
- JavaScript
- TypeScript
- React
- Vue
- Angular
- Phaser
- PixiJS
- Unity
- Godot
- Cocos Creator
- Defold
کلاینت مسئول نمایش بازی، دریافت ورودی کاربر، ارسال فرمانها و نمایش وضعیت دریافتی از سرور است.
2. سرور Colyseus
سرور وظایف اصلی زیر را انجام میدهد:
- ساخت اتاقها
- اعتبارسنجی کاربران
- اجرای قوانین بازی
- نگهداری state
- ارسال state به کلاینتها
- پردازش پیامها
- اجرای Game Loop
- مدیریت قطع اتصال و اتصال مجدد
3. دیتابیس
دیتابیس معمولاً برای ذخیره دادههای دائمی استفاده میشود، نه state لحظهای هر بازی.
برای مثال:
- اطلاعات کاربران
- تاریخچه مسابقات
- امتیازات
- رتبهبندی
- پرداختها
- کیف پول
- آیتمهای خریداریشده
- آمار بازی
- نتایج مسابقات
برای این بخش میتوان از ابزارهایی مثل PostgreSQL، MongoDB، MySQL یا Redis استفاده کرد.
4. Redis
Redis در پروژههای Colyseus میتواند کاربردهای مهمی داشته باشد:
- نگهداری sessionها
- cache کردن اطلاعات
- ذخیره دادههای کوتاهمدت
- مدیریت presence
- هماهنگی میان چند سرور
- مقیاسپذیری افقی
- صفبندی دادهها یا eventها
Matchmaking در Colyseus
یکی از نیازهای رایج در بازی آنلاین، پیدا کردن بازیکنان مناسب برای شروع مسابقه است. این فرآیند با نام Matchmaking شناخته میشود.
برای مثال، وقتی یک کاربر روی دکمه «شروع بازی» کلیک میکند، سیستم باید بررسی کند:
- آیا اتاق خالی وجود دارد؟
- آیا اتاقی با ظرفیت مناسب وجود دارد؟
- آیا بازیکنان سطح مشابه دارند؟
- آیا نوع بازی موردنظر کاربر با اتاق سازگار است؟
- آیا منطقه جغرافیایی یا ping بازیکن مناسب است؟
- آیا بازی دارای رمز یا تنظیمات خاص است؟
در Colyseus متدهایی مانند joinOrCreate برای چنین سناریوهایی کاربرد دارند.
const room = await client.joinOrCreate("game_room", {
gameMode: "ranked",
region: "me",
});
در سمت سرور میتوانید اتاقها را بر اساس metadata یا تنظیمات ورودی فیلتر کنید.
نمونه:
import { Room } from "colyseus";
export class RankedRoom extends Room {
maxClients = 2;
onCreate(options: { gameMode?: string }) {
this.setMetadata({
gameMode: options.gameMode ?? "casual",
});
}
}
Game Loop در Colyseus
بسیاری از بازیها به یک حلقه اجرای مداوم نیاز دارند که معمولاً Game Loop نام دارد.
برای نمونه، در یک بازی اکشن ممکن است لازم باشد در هر فریم موارد زیر بررسی شوند:
- موقعیت کاراکترها
- برخوردها
- سرعت و شتاب
- وضعیت گلولهها
- سلامت بازیکنان
- پایان زمان
- شرایط برد و باخت
در Colyseus میتوانید از setSimulationInterval استفاده کنید.
onCreate() {
this.setSimulationInterval((deltaTime) => {
this.updateGame(deltaTime);
});
}
private updateGame(deltaTime: number) {
// منطق اصلی بازی
}
نکته مهم این است که بهتر است نرخ Game Loop را متناسب با نوع بازی تعیین کنید.
| نوع پروژه | نرخ پیشنهادی |
| بازی نوبتی | بدون نیاز به loop سریع |
| بازی کارت | بر اساس event |
| بازی استراتژی | حدود 5 تا 20 بار در ثانیه |
| بازی اکشن دوبعدی | حدود 20 تا 30 بار در ثانیه |
| بازی سریع رقابتی | حدود 30 تا 60 بار در ثانیه |
هرچه نرخ بروزرسانی بالاتر باشد، مصرف CPU، شبکه و هزینه سرور نیز افزایش پیدا میکند.
چه پروژههایی برای Colyseus مناسب هستند؟
Colyseus فقط برای بازی نیست. هر پروژهای که به ارتباط Real-time، state مشترک و مدیریت چند کاربر در یک فضای مستقل نیاز داشته باشد، میتواند از آن استفاده کند.
1. بازیهای نوبتی
Colyseus برای بازیهای نوبتی گزینه بسیار مناسبی است، زیرا نیاز به همگامسازی سریع در سطح فریم ندارند و منطق اصلی روی سرور قابلکنترل است.
نمونهها:
- دوز
- شطرنج
- تختهنرد
- بازیهای کارتی
- Poker
- Uno
- حکم
- بازیهای رومیزی
- بازیهای کلمهای
- بازیهای حدس کلمه
ویژگیهای موردنیاز:
- مدیریت نوبت
- جلوگیری از حرکت غیرمجاز
- تایمر نوبت
- ثبت نتیجه بازی
- اتصال مجدد کاربر
- تاریخچه حرکتها
- رتبهبندی بازیکنان
2. بازیهای Casual و اجتماعی
بازیهای ساده و اجتماعی معمولاً با معماری Room سازگاری زیادی دارند.
نمونهها:
- مسابقات سوال و جواب
- بازی حدس تصویر
- بازیهای آموزشی
- اتاقهای رقابتی کوتاه
- بازیهای چندنفره دوستانه
- بازیهای مهمانی یا Party Games
برای مثال، در یک بازی کوییز آنلاین، هر اتاق میتواند شامل موارد زیر باشد:
- لیست شرکتکنندگان
- سوال فعلی
- گزینهها
- زمان باقیمانده
- پاسخهای ثبتشده
- امتیاز کاربران
- وضعیت پایان بازی
3. بازیهای اکشن دوبعدی
برای بازیهای دوبعدی سریع نیز میتوان از Colyseus استفاده کرد؛ بهخصوص اگر بازی با Phaser، PixiJS یا Unity توسعه داده شده باشد.
نمونهها:
- بازی تیراندازی دوبعدی
- بازی ماشینسواری آرکید
- بازی مبارزهای ساده
- بازیهای Top-down
- بازیهای فوتبال رومیزی
- بازیهای بقا دوبعدی
- بازیهای Arena
در این نوع پروژهها باید به موارد زیر توجه ویژه داشته باشید:
- Server Authoritative Architecture
- نرخ بروزرسانی سرور
- بهینهسازی پیامها
- پیشبینی حرکت در کلاینت
- جبران تأخیر شبکه
- اعتبارسنجی input کاربران
- جلوگیری از تقلب
4. بازیهای نقشآفرینی و RPG
اگر بازی شما دارای اتاقها، نقشهها، بازیکنان متعدد و تعاملات گروهی است، Colyseus میتواند گزینه خوبی باشد.
نمونهها:
- بازی RPG آنلاین سبک
- بازیهای Dungeon چندنفره
- بازیهای همکاریمحور
- بازیهای MMO-lite
- بازیهای جهان کوچک با اتاقهای متعدد
در این سناریو، هر نقشه یا dungeon میتواند یک Room باشد. بازیکنان بر اساس موقعیت، مرحله یا گروه خود به اتاق مناسب منتقل میشوند.
5. چت و اپلیکیشنهای Real-time
هرچند Colyseus یک فریمورک بازی است، اما ساختار آن برای برخی اپلیکیشنهای Real-time نیز کاربرد دارد.
نمونهها:
- چت گروهی اتاقمحور
- اتاقهای گفتوگوی صوتی یا متنی
- سیستم حضور کاربران
- داشبوردهای زنده
- سیستم مانیتورینگ بلادرنگ
- لایو کوییز
- اتاقهای همکاری تیمی
- وایتبورد مشترک
- ابزارهای Collaborative Editing
بااینحال، اگر پروژه شما صرفاً یک سیستم چت است، ابزارهایی مانند Socket.IO یا SignalR ممکن است سادهتر و مناسبتر باشند. نقطه قوت اصلی Colyseus زمانی دیده میشود که علاوهبر ارتباط لحظهای، به Room Management و Shared State ساختاریافته نیاز داشته باشید.
مزایای استفاده از Colyseus
1. توسعه سریعتر بازیهای چندنفره
Colyseus بسیاری از نیازهای تکراری بازیهای آنلاین را آماده در اختیار شما قرار میدهد. در نتیجه لازم نیست از صفر سیستم اتاق، sync state و lifecycle بازیکنان را طراحی کنید.
2. مناسب برای TypeScript
اگر بکاند شما با Node.js و TypeScript توسعه پیدا میکند، Colyseus تجربه توسعه خوبی ارائه میدهد.
مزیتهای مهم TypeScript در این پروژهها:
- type safety
- کاهش خطاهای runtime
- خوانایی بیشتر کد
- DTOهای مشخص برای پیامها
- refactor سادهتر
- autocomplete بهتر در WebStorm و VS Code
نمونه DTO برای حرکت بازیکن:
export interface MovePlayerDto {
x: number;
y: number;
sequence: number;
}
و استفاده در room:
this.onMessage("move", (client, message: MovePlayerDto) => {
if (
typeof message.x !== "number" ||
typeof message.y !== "number" ||
typeof message.sequence !== "number"
) {
client.send("error", {
code: "INVALID_MOVE_PAYLOAD",
message: "Invalid move request.",
});
return;
}
// پردازش حرکت معتبر
});
3. معماری Server Authoritative
در معماری Server Authoritative، سرور مرجع نهایی حقیقت است. یعنی کلاینت فقط درخواست یا input میفرستد و سرور تصمیم میگیرد که آن درخواست معتبر است یا خیر.
این معماری برای جلوگیری از تقلب اهمیت زیادی دارد.
برای مثال، در یک بازی آنلاین نباید به کلاینت اعتماد کنید که اعلام کند:
- من برنده شدم.
- امتیاز من 1000 شد.
- من به نقطه X منتقل شدم.
- من به بازیکن دیگر آسیب زدم.
- من کارت خاصی دارم.
- من پول یا جایزه دریافت کردم.
کلاینت باید فقط فرمان یا input ارسال کند و سرور باید تمام قوانین را بررسی کند.
نمونه نادرست:
this.onMessage("update-score", (client, message) => {
player.score = message.score;
});
نمونه بهتر:
this.onMessage("answer-question", (client, message: { answerId: string }) => {
const player = this.state.players.get(client.sessionId);
if (!player) {
return;
}
const isCorrect = this.validateAnswer(message.answerId);
if (isCorrect) {
player.score += 10;
}
});
4. ارسال بهینه تغییرات state
ارسال فقط تغییرات state باعث کاهش مصرف شبکه و بهبود تجربه کاربران میشود.
این موضوع در پروژههایی با ویژگیهای زیر بسیار مهم است:
- تعداد بازیکن زیاد
- state پیچیده
- اتصال اینترنت ضعیفتر
- کاربران موبایل
- بازیهایی با حرکت دائمی
- سرورهای با هزینه محدود
5. پشتیبانی از بازیابی اتصال
قطع اینترنت کاربر یک اتفاق رایج است؛ بهخصوص در بازیهای موبایل. Colyseus ابزارهایی برای مدیریت Reconnection در اختیار توسعهدهنده میگذارد.
نمونه:
async onLeave(client: Client, consented: boolean) {
if (consented) {
return;
}
try {
await this.allowReconnection(client, 20);
} catch {
this.removePlayer(client.sessionId);
}
}
در این مثال، اگر کاربر بهصورت ناگهانی قطع شود، تا 20 ثانیه فرصت اتصال مجدد دارد.
6. قابلیت مقیاسپذیری
در مرحله ابتدایی، ممکن است یک instance از سرور برای پروژه کافی باشد. اما با افزایش کاربران، نیاز به اجرای چند instance از سرور خواهید داشت.
Colyseus برای معماریهای مقیاسپذیر طراحی شده و میتواند در کنار ابزارهایی مانند موارد زیر استفاده شود:
- Redis
- Docker
- Kubernetes
- NGINX
- Traefik
- PostgreSQL
- Prometheus
- Grafana
برای پروژههای جدی، بهتر است از ابتدا به این موضوعات فکر کنید:
- sticky session
- load balancing
- room persistence
- Redis presence
- monitoring
- log aggregation
- error tracking
- autoscaling
مقایسه Colyseus با Socket.IO
بسیاری از توسعهدهندگان در ابتدا میان Colyseus و Socket.IO مردد هستند.
واقعیت این است که این دو ابزار دقیقاً برای یک هدف مشابه طراحی نشدهاند.
| ویژگی | Colyseus.io | Socket.IO |
| هدف اصلی | بازی و اپلیکیشنهای stateful | ارتباط Real-time عمومی |
| مدیریت اتاق | تخصصی و بازیمحور | وجود دارد اما سادهتر |
| همگامسازی state | داخلی و بهینه | باید دستی پیادهسازی شود |
| matchmaking | آماده و ساختار یافته | باید دستی توسعه یابد |
| game loop | پشتیبانی مناسب | باید دستی طراحی شود |
| state schema | دارد | ندارد |
| مناسب برای چت ساده | ممکن است بیشازحد باشد | بسیار مناسب |
| مناسب برای بازی آنلاین | بسیار مناسب | نیازمند توسعه بیشتر |
چه زمانی Socket.IO بهتر است؟
اگر پروژه شما شامل موارد زیر است، Socket.IO گزینه سادهتر و مناسبتری است:
- چت ساده
- اعلان لحظهای
- داشبورد مدیریتی زنده
- سیستم حضور کاربران
- انتقال eventهای ساده
- اپلیکیشن Real-time بدون state پیچیده
چه زمانی Colyseus بهتر است؟
اگر پروژه شما شامل موارد زیر است، Colyseus انتخاب قدرتمندتری است:
- بازی چندنفره
- اتاق مسابقه
- سیستم matchmaking
- state مشترک
- بازی نوبتی
- رتبهبندی و مسابقات
- قوانین سمت سرور
- بازی اکشن با sync state
- مدیریت reconnect بازیکنان
مقایسه Colyseus با SignalR
SignalR یک ابزار محبوب در اکوسیستم .NET است که برای ساخت اپلیکیشنهای Real-time کاربرد دارد. اگر بکاند شما با ASP.NET Core توسعه یافته، احتمالاً SignalR گزینه آشنایی برای شماست.
| ویژگی | SignalR | Colyseus |
| کوسیستم اصلی | ASP.NET Core و C# | Node.js و TypeScript |
| تمرکز اصلی | ارتباط بلادرنگ عمومی | بازی چندنفره |
| Room lifecycle | نیازمند طراحی بیشتر | بسیار مناسب برای بازی |
| state synchronization | عمدتاً دستی | داخلی |
| matchmaking | باید توسعه داده شود | دارد |
| game loop | باید دستی پیاده سازی شود | مناسب |
| بازیهای چندنفره | قابل استفاده اما با توسعه بیشتر | انتخاب تخصصیتر |
| پنل و داشبورد زنده | انتخاب عالی | قابلاستفاده |
اگر پروژه اصلی شما ASP.NET Core است اما قصد ساخت یک ماژول بازی آنلاین دارید، میتوانید معماری ترکیبی داشته باشید:
- سرویس اصلی با ASP.NET Core
- مدیریت کاربران، پرداخت و دیتابیس با ASP.NET Core
- سرور بازی با Colyseus
- ارتباط میان سرویسها با REST API، gRPC یا message broker
شروع پروژه با Colyseus
پیشنیازها
برای شروع توسعه با Colyseus معمولاً به موارد زیر نیاز دارید:
- Node.js
- npm یا pnpm
- TypeScript
- دانش پایه WebSocket
- آشنایی با مفاهیم بازی چندنفره
- یک کلاینت مانند Phaser، React یا Unity
ایجاد پروژه
نمونهای از راهاندازی اولیه:
bash
npm create colyseus-app@latest my-colyseus-game
سپس وارد پوشه پروژه شوید:
bash
cd my-colyseus-game
وابستگیها را نصب کنید:
bash
npm install
و پروژه را اجرا کنید:
bash
npm run start
ساختار پروژه معمولاً شامل بخشهایی مانند زیر است:
src/
├── app.config.ts
├── index.ts
├── rooms/
│ ├── GameRoom.ts
│ └── schema/
│ └── GameState.ts
└── services/
نمونه یک اتاق بازی ساده
در این مثال، یک اتاق ساده ایجاد میکنیم که بازیکنان به آن متصل میشوند و میتوانند موقعیت خود را تغییر دهند.
تعریف Player
import { Schema, type } from "@colyseus/schema";
export class Player extends Schema {
@type("number")
x = 0;
@type("number")
y = 0;
@type("number")
score = 0;
}
تعریف GameState
import { Schema, type, MapSchema } from "@colyseus/schema";
import { Player } from "./Player";
export class GameState extends Schema {
@type({ map: Player })
players = new MapSchema<Player>();
}
پیادهسازی GameRoom
import { Client, Room } from "colyseus";
import { GameState } from "./schema/GameState";
import { Player } from "./schema/Player";
interface MovePlayerDto {
x: number;
y: number;
}
export class GameRoom extends Room<GameState> {
maxClients = 4;
onCreate() {
this.setState(new GameState());
this.onMessage("move", (client, message: MovePlayerDto) => {
this.handleMove(client, message);
});
}
onJoin(client: Client) {
const player = new Player();
player.x = 0;
player.y = 0;
player.score = 0;
this.state.players.set(client.sessionId, player);
}
onLeave(client: Client) {
this.state.players.delete(client.sessionId);
}
private handleMove(client: Client, message: MovePlayerDto) {
const player = this.state.players.get(client.sessionId);
if (!player) {
client.send("error", {
code: "PLAYER_NOT_FOUND",
message: "Player state was not found.",
});
return;
}
if (
typeof message.x !== "number" ||
typeof message.y !== "number"
) {
client.send("error", {
code: "INVALID_MOVE",
message: "Move coordinates are invalid.",
});
return;
}
const maxCoordinate = 1000;
player.x = Math.max(-maxCoordinate, Math.min(message.x, maxCoordinate));
player.y = Math.max(-maxCoordinate, Math.min(message.y, maxCoordinate));
}
}
در این نمونه:
- حداکثر چهار بازیکن میتوانند وارد اتاق شوند.
- برای هر بازیکن یک Player در state ایجاد میشود.
- بازیکن پیام move ارسال میکند.
- سرور ورودی را اعتبارسنجی میکند.
- مختصات بازیکن در سرور بهروزرسانی میشود.
- تغییرات state برای کاربران دیگر sync میشود.
اتصال کلاینت به Colyseus
نمونه اتصال با JavaScript یا TypeScript:
import { Client } from "colyseus.js";
const client = new Client("ws://localhost:2567");
const room = await client.joinOrCreate("game_room", {
gameMode: "casual",
});
room.onStateChange((state) => {
console.log("New game state:", state);
});
room.send("move", {
x: 250,
y: 100,
});
در پروژههای واقعی، بهتر است اتصال، خطا، reconnect و lifecycle اتاق را در یک service مستقل مدیریت کنید.
برای مثال در React، میتوانید یک ColyseusGameService یا یک hook اختصاصی بسازید تا مسئولیتهای زیر را مدیریت کند:
- اتصال به سرور
- join کردن room
- خروج از room
- ارسال پیام
- دریافت تغییرات state
- مدیریت reconnect
- نمایش خطاها
- پاکسازی connection هنگام unmount شدن صفحه
نکات مهم امنیتی در پروژههای Colyseus
امنیت در بازیهای آنلاین بسیار مهم است؛ بهخصوص اگر پروژه دارای رتبهبندی، جایزه، پرداخت، کیف پول یا رقابت باشد.
1. هرگز به کلاینت اعتماد نکنید
کلاینت قابل تغییر است. کاربر میتواند پیامهای جعلی ارسال کند، کد را دستکاری کند یا درخواستهای غیرمجاز بسازد.
سرور باید موارد زیر را بررسی کند:
- آیا بازیکن واقعاً عضو اتاق است؟
- آیا نوبت بازیکن است؟
- آیا حرکت مجاز است؟
- آیا کاربر به منابع لازم دسترسی دارد؟
- آیا زمان انجام عملیات معتبر است؟
- آیا کاربر محدودیتها را رعایت کرده است؟
- آیا payload معتبر است؟
2. اعتبارسنجی پیامها
برای اعتبارسنجی دادههای دریافتی از کلاینت، میتوانید از ابزارهایی مانند موارد زیر استفاده کنید:
- Zod
- class-validator
- Joi
- Yup
نمونه با Zod:
import { z } from "zod";
const movePlayerSchema = z.object({
x: z.number().min(-1000).max(1000),
y: z.number().min(-1000).max(1000),
sequence: z.number().int().nonnegative(),
});
this.onMessage("move", (client, payload) => {
const validationResult = movePlayerSchema.safeParse(payload);
if (!validationResult.success) {
client.send("error", {
code: "INVALID_MOVE_PAYLOAD",
message: "Move payload validation failed.",
});
return;
}
const moveDto = validationResult.data;
// پردازش درخواست معتبر
});
3. استفاده از JWT
برای پروژههایی که حساب کاربری دارند، بهتر است کاربر قبل از ورود به اتاق با JWT احراز هویت شود.
اطلاعاتی که بهتر است در token یا session موجود باشد:
- userId
- username
- role
- walletId
- subscriptionStatus
- permissions
اما اطلاعات حساس مانند موجودی قابلاعتماد کیف پول نباید فقط از token خوانده شود؛ چنین دادههایی باید در سمت سرور و از منبع معتبر بررسی شوند.
4. Rate Limiting
کاربر مخرب ممکن است در هر ثانیه صدها پیام ارسال کند. این رفتار میتواند باعث فشار روی سرور یا اختلال در مسابقه شود.
باید برای eventهای حساس محدودیت ایجاد کنید:
- حرکت
- شلیک
- ارسال پیام چت
- درخواست خرید
- تغییر تنظیمات
- ثبت پاسخ
- ساخت اتاق
نمونه ساده:
const lastMoveByClient = new Map<string, number>();
private canMove(clientId: string): boolean {
const now = Date.now();
const lastMoveAt = lastMoveByClient.get(clientId) ?? 0;
if (now - lastMoveAt < 50) {
return false;
}
lastMoveByClient.set(clientId, now);
return true;
}
در پروژههای حرفهایتر، Redis یا middlewareهای rate limit گزینه مناسبتری هستند.
ذخیره نتایج بازی در PostgreSQL
state بازی معمولاً موقت است، اما اطلاعات مهم باید بعد از پایان مسابقه ذخیره شوند.
اطلاعاتی که میتوانید نگهداری کنید:
- شناسه مسابقه
- نوع بازی
- زمان شروع و پایان
- کاربران شرکتکننده
- برنده
- امتیازها
- تاریخچه حرکتها
- دلیل پایان مسابقه
- هزینه ورود یا جایزه
- وضعیت پرداخت
نمونه ساختار پیشنهادی جدول:
CREATE TABLE game_matches (
id UUID PRIMARY KEY,
room_id VARCHAR(100) NOT NULL,
game_type VARCHAR(50) NOT NULL,
status VARCHAR(30) NOT NULL,
winner_user_id UUID NULL,
started_at TIMESTAMP NULL,
finished_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
و جدول شرکتکنندگان:
CREATE TABLE game_match_players (
id UUID PRIMARY KEY,
match_id UUID NOT NULL REFERENCES game_matches(id),
user_id UUID NOT NULL,
score INT NOT NULL DEFAULT 0,
result VARCHAR(30) NULL,
joined_at TIMESTAMP NOT NULL DEFAULT NOW(),
left_at TIMESTAMP NULL
);
نکته مهم: نوشتن مداوم تمام state بازی در دیتابیس توصیه نمیشود، مگر اینکه بازی شما نیاز جدی به persistence داشته باشد. برای دادههای لحظهای معمولاً memory یا Redis مناسبتر است.
چالشها و محدودیتهای Colyseus
با وجود مزایای زیاد، Colyseus برای همه پروژهها بهترین انتخاب نیست.
1. برای بازیهای AAA سهبعدی سنگین مناسب نیست
اگر قصد دارید یک بازی بسیار سنگین با هزاران کاربر همزمان، فیزیک پیچیده، نقشه بزرگ و نیازهای شبکه بسیار حساس بسازید، ممکن است به معماریهای تخصصیتر یا موتورهای اختصاصی نیاز داشته باشید.
نمونهها:
- بازیهای FPS بسیار رقابتی
- بازیهای MMORPG بسیار بزرگ
- بازیهای جهانباز عظیم
- بازیهایی با فیزیک بسیار پیچیده
- بازیهای Battle Royale با صدها بازیکن در یک نقشه
Colyseus همچنان میتواند در بخشی از چنین معماریهایی استفاده شود، اما احتمالاً بهینهسازی، shard کردن دنیا، سرویسهای جداگانه و طراحی شبکه پیشرفته لازم خواهد بود.
2. نیاز به دانش شبکه و معماری بازی
استفاده از Colyseus باعث نمیشود چالشهای شبکه کاملاً حذف شوند. در بازیهای سریع همچنان باید مفاهیمی مانند زیر را درک کنید:
- latency
- packet loss
- client prediction
- interpolation
- extrapolation
- reconciliation
- tick rate
- authoritative server
- cheating prevention
3. state بسیار بزرگ میتواند مشکلساز باشد
اگر state اتاق بسیار حجیم باشد یا تغییرات زیادی در هر tick رخ دهد، ممکن است مصرف شبکه و پردازش بالا برود.
راهکارها:
- نگهداری فقط دادههای ضروری در state
- ارسال پیامهای جداگانه برای eventهای موقت
- کاهش نرخ بروزرسانی
- محدود کردن تعداد بازیکنان هر room
- تقسیم نقشه به roomهای کوچکتر
- حذف دادههای غیرضروری
- استفاده از visibility یا interest management در طراحی بازی
پیشنهاد معماری برای یک بازی آنلاین واقعی
برای ساخت یک پروژه حرفهای، میتوانید معماری زیر را در نظر بگیرید:
Frontend Game Client
├── React / Phaser / Unity
└── colyseus.js
│
│ WebSocket
▼
Game Server
├── Colyseus
├── Game Rooms
├── Matchmaking
├── State Synchronization
└── Game Rules
│
├── Redis
│ ├── Presence
│ ├── Session
│ ├── Cache
│ └── Distributed Coordination
│
├── PostgreSQL
│ ├── Users
│ ├── Matches
│ ├── Leaderboard
│ └── Transactions
│
└── Main Backend API
├── NestJS یا ASP.NET Core
├── Authentication
├── Payment
├── Wallet
├── Admin Panel
└── Reporting
این معماری بهخصوص برای پروژههایی که پرداخت، کیف پول، مسابقه پولی یا سیستم رتبهبندی دارند، منطقی است.
بهترین سناریوها برای استفاده از Colyseus
Colyseus انتخاب بسیار خوبی است اگر پروژه شما یکی از ویژگیهای زیر را داشته باشد:
- بازی چندنفره مبتنی بر اتاق
- بازی نوبتی یا کارتی
- مسابقات کوتاه و سریع
- نیاز به matchmaking
- نیاز به sync شدن state میان بازیکنان
- استفاده از Node.js و TypeScript
- نیاز به کنترل کامل منطق بازی در سمت سرور
- نیاز به reconnect بازیکنان
- توسعه بازی با Phaser، Unity یا React
- نیاز به جلوگیری از تقلب در بخشهای مهم بازی
چه زمانی بهتر است از Colyseus استفاده نکنیم؟
در شرایط زیر شاید ابزار دیگری مناسبتر باشد:
| سناریو | گزینه پیشنهادی |
| چت ساده | Socket.IO یا SignalR |
| اعلان لحظهای در پنل | Socket.IO، SignalR یا SSE |
| CRUD معمولی | REST API یا GraphQL |
| داشبورد سازمانی زنده | SignalR یا Socket.IO |
| بازی سهبعدی عظیم و بسیار رقابتی | معماری اختصاصی یا سرویسهای تخصصی |
| اپلیکیشن ساده بدون state مشترک | WebSocket ساده یا SSE |
جمعبندی
Colyseus یک فریمورک قدرتمند برای ساخت بازیهای آنلاین چندنفره و اپلیکیشنهای Real-time مبتنی بر Node.js است. مهمترین مزیت آن، فراهم کردن ساختار آماده برای مدیریت Room، matchmaking، همگامسازی state، مدیریت lifecycle بازیکنان و ارتباط WebSocket است.
اگر میخواهید یک بازی مانند دوز، شطرنج، حکم، بازی کارتی، کوییز آنلاین، بازی اکشن دوبعدی، مسابقه چندنفره یا یک سیستم همکاری بلادرنگ بسازید، Colyseus میتواند سرعت توسعه شما را بهطور قابلتوجهی افزایش دهد.
برای پروژههای حرفهای، بهتر است Colyseus را در کنار ابزارهایی مانند PostgreSQL، Redis، JWT، Docker و یک API اصلی با NestJS یا ASP.NET Core استفاده کنید. در این حالت، Colyseus مسئول منطق لحظهای و مدیریت مسابقه خواهد بود و بکاند اصلی مسئول کاربران، پرداختها، گزارشگیری، مدیریت دادههای دائمی و پنل ادمین میشود.