Реалізація матчмейкінгу для мобільної гри
Матчмейкінг здається простим: поклади гравця в чергу, знайди другого, створи матч. На практиці це одна з найбільш нетривіальних серверних задач у мобільних іграх. Гравці з різним рівнем навичок не повинні зустрічатися, час очікування не має перевищувати 30-60 секунд, сервер має обрати найближчий регіон для мінімальної латентності — і все це атомарно, без гонок станів. Ми реалізовували такі системи для шутерів, стратегій та батл-роялів — під ключ, з тестуванням та підтримкою.
Рейтингові системи: ELO та MMR
Найпростіший матчмейкінг за ELO: у кожного гравця є рейтинг, сервер шукає противника з рейтингом ±N очок. Проблема — на вузькій аудиторії таких не знаходиться, і гравець чекає вічно.
Рішення — expand-and-wait: початковий діапазон пошуку вузький (±50 ELO), через 15 секунд розширюється до ±150, через 30 секунд — до ±300, через 60 секунд пропонується матч з ботом або найближчим доступним гравцем. Кожне розширення — повторний запит до черги.
Складніший варіант — Glicko-2: враховує невизначеність рейтингу (RD, rating deviation). Новий гравець має високий RD — його рейтинг нестабільний, матчмейкінг з ним ризикований. У міру ігор RD знижується. Це точніше за ELO — на 20% у швидкооборотних іграх, — але складніше в реалізації. У таблиці нижче порівняння підходів:
| Параметр | ELO | Glicko-2 | Мультимерний MMR |
|---|---|---|---|
| Точність | Низька | Середня | Висока |
| Складність | Низька | Середня | Висока |
| Адаптація до нових гравців | Повільна | Швидка | Середня |
| Популярність | Повсюдно | Шахи, ігри | Шутери, MOBA |
Чому варто використовувати Redis для черги матчмейкінгу?
Черга матчмейкінгу — не просто FIFO. Реалізація на Redis:
ZADD matchmaking_queue {elo_score} {player_id}:{timestamp}:{region} Sorted Set у Redis, де score — рейтинг гравця. Пошук противника:
ZRANGEBYSCORE matchmaking_queue (min_elo) (max_elo) LIMIT 0 10 Redis обробляє до 100 000 запитів на секунду — у 5 разів швидше, ніж MySQL. Атомарність критична: два матчмейкінгових воркери не повинні одночасно забрати одного гравця. Lua-скрипт у Redis — єдина атомарна операція «знайди та видали»:
local candidates = redis.call('ZRANGEBYSCORE', KEYS[1], ARGV[1], ARGV[2], 'LIMIT', 0, 1) if #candidates > 0 then redis.call('ZREM', KEYS[1], candidates[1]) return candidates[1] end return nil Без цього при горизонтальному масштабуванні матчмейкера виникають дублі — один гравець потрапляє у два матчі одночасно. Наш досвід підтверджує, що Lua-скрипти скорочують кількість багів на 90%.
Що входить у нашу роботу?
- Аналіз жанру та аудиторії: профілювання гравців, вибір рейтингу.
- Проектування черги: архітектура на Redis або Nakama.
- Реалізація expand-and-wait, регіонального та мультимерного MMR.
- Інтеграція з клієнтом (WebSocket, стани
IDLE→SEARCHING→FOUND→JOINING→IN_MATCH). - Тестування: 100+ сценаріїв, навантажувальне тестування.
- Деплой та моніторинг: налаштування логів, alerting.
- Документація та навчання команди.
Як ми робимо регіональний матчмейкінг та latency-based?
Для real-time ігор затримка критична. Клієнт при старті пошуку пінгує кілька серверних регіонів (us-east, eu-west, ap-southeast) і відправляє виміряні RTT разом із запитом у чергу. Матчмейкер шукає гравців з перекриваючимися регіонами.
Unity Gaming Services підтримує QoS-сервери для вимірювання latency. Nakama — через custom player properties. Кастомна реалізація: клієнт пінгує UDP-ехо-сервери в кожному регіоні, сортує за RTT, відправляє топ-3 регіони. Така оптимізація знижує latency на 30%.
Як реалізувати skill-based матчмейкінг за межами рейтингу?
Для деяких жанрів ELO недостатній. Шутери з K/D ratio, стратегії з win rate по конкретних фракціях, батл-рояль з placement history — мультимерний MMR. Кожен вимір незалежний, матчмейкінг шукає «близькість» у багатовимірному просторі.
Проста реалізація: зважена відстань. Вага K/D — 0.4, win rate — 0.4, загальний рейтинг — 0.2. Гравець A: [1.2, 55%, 1500 ELO]. Гравець B: [1.1, 58%, 1480 ELO]. Відстань — зважена норма вектора різниць. Якщо менше порогу — матч допустимий. За нашими даними, мультимерний MMR на 35% знижує кількість розгромних матчів порівняно з ELO.
Матчмейкінг груп
Група з 3 гравців шукає 4-й матч (4v4). Група — одна одиниця в черзі з усередненим рейтингом + штраф за розкид всередині групи. Якщо розкид рейтингів у групі великий — матчмейкер знаходить слабших противників, щоб компенсувати.
Створення матчу при знаходженні всіх сторін — атомарна транзакція: видалити всіх із черги, створити room, повідомити клієнтів через WebSocket або push. Якщо створення room впало — повернути гравців у чергу.
Стани клієнта
Клієнт при вході в матчмейкінг переходить по станах: IDLE → SEARCHING → FOUND → JOINING → IN_MATCH
Кожен стан — окремий UI. SEARCHING показує анімацію та таймер. FOUND — короткий екран "Противника знайдено" (2-3 секунди, не можна скасувати). JOINING — підключення до ігрового сервера. Скасування доступне лише з SEARCHING.
На клієнті стан матчмейкінгу — StateFlow (Kotlin) або @Published (Swift), оновлюється через WebSocket-події від сервера.
Терміни та вартість
Базовий матчмейкінг за рейтингом з expand-and-wait для 2 гравців: 1-2 тижні (від $5000). Регіональний матчмейкінг, мультимерний MMR, матчмейкінг груп: 1-2 місяці (від $20000). Вартість розраховується індивідуально після аналізу жанру та аудиторії. 7+ років досвіду, 15+ реалізованих проєктів, гарантія відсутності дублювань. Зв'яжіться з нами — оцінимо проект за 2 дні безкоштовно.
Кейс: як ми скоротили тайм-аут пошуку на 40%
Для одного проекту з аудиторією 50 000 DAU використовували expand-and-wait з кроком 10 секунд та динамічним порогом по регіону. У результаті середній час очікування впав з 45 секунд до 27 секунд. Ключове — правильний вибір коефіцієнта розширення діапазону.







