Большой мир целиком не помещается в память. Обычно его режут на локации, а стык прячут за загрузочным экраном. В онлайн-игре это особенно неудобно: локации живут на разных машинах, и на границе между ними мир должен остаться единым — со взрывом, который задевает существ по обе стороны.

Как обычно решают

Открытый мир нелинеен: идёшь куда хочешь, по дороге цепляешь побочные задания. Плата за это — ресурсы: мир не загрузить целиком. Отсюда деление на локации с загрузкой на переходе.

край локации игры Fallout справа внизу
край локации игры Fallout справа внизу

Бесшовный мир устроен иначе: в память подгружаются соседние локации, когда игрок приближается к их границам, а дальние выгружаются — в 3D-играх это часто прикрывают туманом.

мы видим соседние загруженные игровые локации
мы видим соседние загруженные игровые локации

Как это сделано у меня

Мир изначально задумывался открытым и масштабируемым — каждая локация может стоять на отдельной машине. Оставалось сделать его бесшовным.

Первой мыслью было подключать игрока сразу к нескольким серверам-локациям. От неё пришлось отказаться: знать о соседях нужно не только клиенту, но и самому серверу — иначе взрыв на границе не заденет тех, кто стоит на соседней локации. В итоге сервер-локация сам соединяется с соседями и становится для них клиентом.

Сразу вылезли две проблемы. Локация поднимается только когда на ней кто-то играет — понадобилось внутреннее API, которым серверы проверяют, запускают и останавливают друг друга. И согласованность: соседи одновременно хотят соединиться со мной, а я с ними. Пока развязка простая — инициирует тот, у кого сумма цифр в адресе больше, а при нескольких локациях на одной машине сравнивается ещё и порт.

Приоритетный сервер подымает локацию и инициирует соединение websocket с ней
Приоритетный сервер подымает локацию и инициирует соединение websocket с ней
Соседняя локация на которой никого нет поднялась и приняла запрос на соединение с приоритетным сервером (где авторизовался игрок)
Соседняя локация на которой никого нет поднялась и приняла запрос на соединение с приоритетным сервером (где авторизовался игрок)

Дальше сервер получает от соседей пакеты о происходящем у них, ретранслирует их своим игрокам и в ответ рассылает соседям то, что творится у него.

Граница нескольких сервер-локаций
Граница нескольких сервер-локаций

Переход через границу

Остался сам переход между серверами. Он собран так:

  • Игрок отправляет на сервер команду на выход

  • Сервер сохраняет игрока в бд

  • Сервер убирает из массива существ находящихся на нем игрока, рассылаем всем команду что игрок удаляется с сервера

  • Клиенты получают команду что игрок отключается, ждут пару секунд и убирают со сцены данный «прафаб» (в Unity игровые сущности называются префабами)

  • Если за те пару секунд что клиенты ждут удаление префаба он появился в другой сервер локации — мы анимируем плавный переход границы

В клиенте это выглядит как две группы слоёв: графика текущей и соседней локации отдельно, существа — отдельно.

Группа слоев Map — содержит графику текущей (center) локации и соседней (left)
Группа слоев Map — содержит графику текущей (center) локации и соседней (left)
Группа слоев World — содержит прафабы текущей (center) локации и соседней (left)
Группа слоев World — содержит прафабы текущей (center) локации и соседней (left)

Первые опыты дали переход не очень плавным. Как выглядело тогда:

Что было дальше

Переход довели: сейчас границы карт стыкуются в один открытый мир, игрок пересекает их без загрузочного экрана, а сервер обсчитывает только то, что реально видно игрокам. На этих же стыках работают порталы между картами и переносы внутри локации.

Как это выглядит в игре, показываю на канале проекта: youtube.com/@mmogick_ru.

Если вы делали бесшовный мир — расскажите, как решали согласованность между узлами.

История:

  1. Введение

  2. Масштабируемость и асинхронность

  3. WebSocket

  4. Redis

  5. LUA и JavaScript

  6. Выбор технологий, протокола и архитектурный шаблон Entity Component System

  7. Игровые локации (тайловые карты)

  8. Клиентская часть на Unity

  9. Игровые серверные механики

  10. Открытый бесшовный мир в 2D игре

  11. FPS, Ping, паузы между командами, интерполяция и экстраполяция

  12. Очереди и параллельное программирование на CPU

  13. Event-driven паттерн, JSON-RPC и почему не сервисная (SOA) архитектура

  14. Сетевая карта и задержка кадра (Latency frame) по RFC 2544 (1242)

  15. Создание сервера для онлайн ММО игр на PHP

  16. Готовое MVP сервиса 2D MMO RPG игр (realtime)

  17. Внедряю ИИ: механики из одного описания

  18. Конвейер контента для MMO: импорт анимации, экипировка и обновление игры без патчей

  19. Каталог ассетов и git: как командой вести игру в облаке

  20. Тесты для кода, который пишет ИИ: контракты вместо ассертов