Какие архитектурные подходы выбрать для разработки клиент-серверной игры?

Доброго времени суток. Пытаюсь немного разобраться в реализации клиент-серверной архитектуры и возникло непонимание некоторых базовых вещей, хотелось бы спросить мнение более опытных разработчиков. Вылезать из хоть сколько-нибудь привычной колеи у меня получается с трудом, плохо представляю с чего начать.

Игровая модель
Близка к играм вроде Elite/Space Rangers/X. Есть условный двухмерный космос, в нем существуют и взаимодействуют объекты (звезды, планеты, корабли и т.д.). Реалтайм, но "медленный", по типу стратегий от Paradox.

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

Что пытаюсь получить
  1. На хосте крутится сервер, вероятно обычное самописное C# приложение. Сервер авторитарный, все игровое состояние находится и симулируется на нем.
  2. Подключенные Unity клиенты получают от сервера только доступную им (видимую сенсорами кораблей игрока, например) информацию об игровом мире и отрисовывают ее.


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

Собственно, вопросы:

  1. Эксперементировал с реализацией похожей механики в однопользовательском варианте, там все было довольно просто:
    • Любой сохраняемой сущности сопоставлен сериализуемый в JSON DTO (простое хранилище данных), включая корень игрового мира.
    • Эти сущности могут создать свой DTO из своих данных (и из вложенных в них сущностей), или восстановиться из него. Т.е. DTO является универсальным способом создания чего-либо или его сохранения/восстановления.
    • Сохранение/загрузка мира - просто создание/восстановление некоего WorldContext.

    Это неплохо работает, мир относительно легко сохранить/восстановить, но только одним куском.
    Если я правильно понимаю, для серверных приложений этот метод не используют, т.к. данные могут быть легко потеряны при сбое, а периодическое сохранение мира одним куском может вызвать зависания. Т.е. держать весь мир в RAM и периодически скидывать его в json будет плохим решением.

    Прочитав статьи от mail.ru на эту тему, так и не осознал, что хранится в БД, а что - в RAM, и как это применить к моему случаю, как состояние игры "размазывается" между состоянием в памяти и базой данных. Например, если состояние структуры корабля сохранено в базе, должно ли оно существовать как объект в памяти или при необходимости доступа к нему оно читается из базы? Или обычно пишут какие-нибудь автоматические системы кеширования/сброса в БД?

    Большая часть из этого, конечно, неактуальна для меня, но будет ли оправданным решением изначально делать всю симуляцию полностью в RAM и поддерживать БД в актуальном состоянии по ходу игры (т.е. постоянно записывать изменения, но не читать), а при необходимости потом организовать выгрузку из памяти тех или иных больших коллекций объектов? Или я вообще не в том направлении думаю?


  2. Как, собственно, производить синхронизацию клиента и сервера? Даже простой мир на начальных стадиях разработки довольно быстро становится объемным по количеству разнородных сущностей/данных в них, особенно с появлением сложных объектов вроде кораблей, в то время как в туторах по какому-нибудь UNet обычно руками синхронизируют позицию игрока и пару параметров и на этом останавливаются. У меня получалось что-то наподобие вот такого save.json.

    Я думаю над вариантом создавать на клиенте экземпляр той же симуляции мира, что и на сервере. В нее загружать данные, прилетающие с сервера, создавая свою локальную "частичную симуляцию", таким образом переиспользуя код с сервера (остается только создать ее графическое представление). Вот только данные могут прийти неполные (скажем, вместо подробной структуры вражеского корабля какой-нибудь условный NoAccess< ShipStructure >), что придется обрабатывать и, соответственно, захламлять код сервера.

    В любом случае, не очень понятно, как проводить непосредственно синхронизацию. Я так понимаю, при изменении состояния объекта (скажем, корабль изменил вектор тяги), нужно отправить обновленный сериализованный корабль клиенту, где он перезагрузит его в свою симуляцию (я планирую использовать компонентный подход, поэтому нужно будет обновить только ответственный компонент).

    Насколько это плохая идея? Ведь в какой-нибудь EVE Online вряд ли возможно запускать симуляцию на клиенте, что в таком случае может приходить клиенту там?


  3. На данный момент я не хочу использовать photon/аналоги и планирую ограничиться разве что какими-нибудь открытыми библиотеками. Насколько много я теряю от такого подхода и какие сетевые библиотеки могут быть полезны? Читал о вещах вроде SignalR, например. И может ли что-нибудь дать использование ASP.NET (возможно core) в качестве основы, или для таких целей его использование бессмысленно?



Понимаю, что на подобные вопросы нет и не может быть однозначного ответа, но был бы рад узнать по крайней мере степень глупости описанных мною решений. Искал информацию в тех же dev блогах EVE, но у них в основном про более "взрослые" вещи пишут. В каком направлении вы бы стали реализовывать описанную модель игры? Было бы интересно услышать.
  • Вопрос задан
  • 1666 просмотров
Решения вопроса 3
jamakasi666
@jamakasi666
Просто IT'шник.
1) Разделите мир на "чанки", т.е. просто на квадраты конкретного размера. В случае 2д это будет очень удобно.
2) Сервер обрабатывает только те чанки в которых находятся игроки, в случае отсутсвия игрока в нем то он сейвится на винт\бд. На клиенте чанки так же выгружается из памяти если он ушел в новый чанк.
3) Понадобится система пресказания какой чанк надо подгрузить. Скажем зная направления игрока предполагаем что он попадет в такойто то чанк а значит грузим его заранее.
4) Игроку при приближении к чанку отправляй его полное состояние а уже после только то что в нем изменяется.
5) Часть вычислений для линейных(предсказуемых) объектов можно синхронизировать только периодически. К примеру летящий астероид логически обсчитывается и на клиенте и на сервере но только в случае событий(столкновение к примеру) идет синхронизация между клиент-сервером.

На заметку, чем больше линейных(т.е. предсказуемых) объектов тем меньше можно синхронизировать их. Кроме того это сразу же даст бонус в виде достаточно простой реализации интерполяции и экстраполяции.
Ответ написан
Griboks
@Griboks Куратор тега C#
Вы думаете не в том направлении. Сервер главный, клиенты - рабы его. Захотели куда-то полететь? Спрашивайте разрешения у сервера. Захотели посмотреть данные о планете? Спрашивайте у сервера? Захотели сходить в туалет? Спрашивайте у сервера) Разумеется, всё это для выделенного сервера, который вы хотите написать на C#.

1) Надо отталкиваться от того, что любой клиент можно подделать. Следовательно, всё, что не отвечает за рендеринг, интерфейс и красивые эффекты, должно храниться и обрабатываться сервером. Клиент должен просто показывать ваши нули и единицы в удобной для пользователя формы.
2) Любое действие, совершаемое игроком, должно отправляться на сервер и проверяться им. Естественно, по данным, которые хранятся на сервере. Пользователь нажимает кнопку, посылается запрос на сервер, он симулирует действия. Рассматривайте сервер как единственное место, в котором реально происходит "игра" (симуляция мира). Клиент же рассматривайте как клавиатуру, мышку и монитор в оффлайн играх.
3) Симуляция (которая происходит ТОЛЬКО на сервере) должна быть ни чем иным, как простым кодом. Без всяких переиспользований и прочего. Просто какая-то функция OnClientSendData, которая, грубо говоря, осуществляет покупку от имени приславшего клиента нового корабля, сохраняет id в профиль игрока и возвращает сообщение об успешном выполнении покупки.
4) Связь между клиентом и сервером должна осуществляться через ваш собственный или выбранный протокол. На низком уровне. Обобщая, без использования стандартных юнитовских rpc, т.к. они не работают с выделенным сервером. Если очень хочется, то работают, но тогда сервер придётся писать на самой юнити, и он уже не будет консольным. В таком случае, опять таки надо забыть про рендеринг и прочее, ведь сервер должен быть быстрым, и на него никто не будет пялиться каждый день. Сборки, разумеется, должны быть разные для сервера и для клиента.
5) Интерполяция, экстраполяция, сглаживание и прочие умные словечки будут очень полезны в плане разгрузки сервера. Ведь можно каждую секунду передавать координаты планеты всем клиентам, а можно задать его эфемериду на несколько часов вперёд, ограничившись таким образом одной отправкой. Кстати, именно так сделано в GPS.
6) Некоторые логические действия всё-таки можно производить на клиенте, если их результату сервер сможет доверять. Ну или просто "разрешить" читерство, проводя симуляцию на клиенте, а сервер превратив в "тонкую" базу для синхронизации.
7) Ну и под конец, необходимо использовать всякие спец. приёмчики. Например, зачем каждый раз загружать с сервера всю карту, если можно загрузить только изменившийся кусок. Некоторые действия можно вообще сделать "локальными". Например, зная результат боя, можно сразу загрузить его и симулировать на клиенте, а не получать каждую секунду новые параметры.
Ответ написан
lexxpavlov
@lexxpavlov
Программист, преподаватель
так и не осознал, что хранится в БД, а что - в RAM ... автоматические системы кеширования/сброса в БД?

В БД хранится вся информация мира, в текущий момент. Представьте, что сервер внезапно "умер", вся инфа в памяти исчезла, и теперь доступна только та инфа, которая сейчас в БД. Но получить инфу довольно долго, поэтому, используются кэши, иногда - многоуровневые. Если бы БД имела бесконечную скорость работы, то кэш был бы не нужен. Но, к сожалению, кэш необходим. В идеале, в кэше находится вся та же инфа, что и в БД. Но сложнейшая задача - инвалидация кэша (в БД инфа уже изменилась, а в кэше - нет, ещё хуже - в одном кэше тоже изменилась, а в другом - нет). Для того, чтобы потом не собирать баги лопатами, то придётся создавать автоматические системы кеширования/сброса.
Почитайте статьи "Архитектура высоконагруженных приложений. Масштабирование распределенных систем" (части 1 2) от Badoo.

Как, собственно, производить синхронизацию клиента и сервера?

Весь мир только на сервере, у клиента нужны только доступные (видимые) игроку объекты, иначе игроки узнают "лишнюю" инфу (например, в World of Tanks есть способы узнать "невидимые" танки). Но, к сожалению, так обычно не получается. Клиенту приходится знать чуть о большем количестве объектов, для того, чтобы прогнозировать их поведение. Но в космических играх попытаться можно сделать видимость объектов. Вы сказали про сенсоры корабля, эта очень хорошая мысль - объект исчез из сенсора, то пусть клиент удаляет этот объект. Если в дальнейшем объект становится в сенсоре, то пусть об этом сервер сообщит клиенту (или не сообщит, например, корабль включил маскировку, тогда клиент вообще не знает, что "кто-то" есть). Это можно добавить в геймдизайн - если объект попадает в зону сенсора, то сенсор увидит не в тот же миг, а через некоторе время, в зависимости от харакетристик сенсора (но не меньше пинга, хе-хе).
Почитайте серию статей "Сетевое программирование для разработчиков игр" (части 1 2 3), а также блог 0fps.net (на английском, нет нормального содержания, но есть сильные статьи). Ещё полезнейшая статья "Борьба с читерами в онлайн-играх: 22 «нужно» и «нельзя»".

создавать на клиенте экземпляр той же симуляции мира, что и на сервере

На клиенте создавать серверный мир не нужно, но и полностью отделяться тоже не стоит. Часть кода между сервером и клиентом будет общим. И чем больше, тем легче потом разрабатывать и клиент, и сервер. Помните про наследование с полиморфизмом, про общие проекты в решении.
Сервер не должен ничего знать про Unity3d, да и клиент игровую логику должен делать без использования инструментов Unity (про это ещё говорит TheShock). Так будет гораздо проще в дальнейшем.

может ли что-нибудь дать использование ASP.NET (возможно core) в качестве основы, или для таких целей его использование бессмысленно?

ASP.NET не нужен, имхо, он мало пользы может дать игровому серверу. Я бы начал делать сервер на .NET Core (можно и .NET Framework, просто сложнее будет деплоить. Подумайте про Azure).

Дмитрий Александров предложил про чанки. Мне кажется, что это хорошая мысль. Почитайте статью "Введение в октодеревья", возможно, это решит проблему с длинными расстояниями. Чанки будут нужны, потому что однажды один сервер не сможет "осилить" весь мир, придётся разделять его.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы