Клиент-серверная архитектура казуальных сетевых игр

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

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

Рассмотрим протокол P2P. Под Peer-To-Peer (P2P) архитектурой подразумевается, что каждый из компьютеров подключается к общей сети, и каждый компьютер имеет контроль над данными игры. Другими словами, P2P это полное отсутствие безопасности. Здесь возможна и подмена данных, и несогласованность между двумя пирами. Конечно, P2P не является мракобесьем, и эта технология имеет место быть в некоторых случаях, например для игры в крестики-нолики без сервера, но по факту, P2P годится только для игр с небольшой продолжительностью игровой сессии и небольшим количеством одновременно играющих игроков в одной сессии. Сразу сделаю поправку, что на территории РФ и постсоветского пространства у очень большого количества геймеров установлены всякие MonsterDebugger и прочие читерские программы для извлечения и подмены данных, и разработчику приходится организовывать дополнительные методы защиты. Но защитить то, что находится на компьютере пользователя, очень сложно. Как же быть?

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

Так как в прошлом, а иногда и в настоящем я флешер, то рассмотрим способы обмена данных с сервером на примере разработки флеш-игр (да, флеш не умеет использовать ничего, кроме TCP протоколов : )). Для начала зададимся самым жизненным вопросом современного школьника: как можно взломать игру на флеше? Перекомпилировать. Или использовать альтернативные Flash-клиенты с изменённым функционалом, или даже шестнадцатеричное редактирование. Но вся необходимая для игры информация обрабатывается на сервере! В таком случае атака на клиент нашей игры фактически бесполезна, он не имеет особой ценности, будучи программой только для демонстрации игрового процесса. Клиент отправляет сообщения серверу и при этом не имеет особого влияния на игровую логику. Итак, чтобы обеспечить безопасность, желательно все вычисления проводить на стороне сервера. Это ясно.

Но в таком случае появляется пинг. Пинг это время, которое тратит сообщение на путь от клиента к серверу и обратно. И по честному исправить ситуацию невозможно. Как результат, задержка добавляет игре дёрганности и прочих лагов. В быстрых играх, если игрок за 900мс пробежит 5 метров, то при лаге в 900мс игрок телепортируется на 5 метров. То же касается и эффектов. Если игрок наступил на мину и успел отбежать назад, но при лаге пинга эта  информация не успеет обновиться,  игрок будет отброшен взрывной волной совершенно не в ту сторону, в которую он рассчитывал. Думаю, проблема ясна. При этом latency 100мс это вполне ок и это могут заметить только профессиональные игроки, не казуальщики.

Решить проблему постановкой задачи программисту не получится, от пинга избавиться невозможно. Но можно создать иллюзию отсутствия таких задержек. Если игрок имеет хороший пинг, менее 20 мс, то игрока вполне можно обманывать. А именно, восстановить то, что должно быть на экране у второго игрока. Попросту угадать действия игрока, и использовать интерполяцию для сглаживания анимации. Есть много способов интерполяции, начиная с обычной линейной (плавное перемещение игрока из места, где он находится в место, где он должен быть), заканчивая интерполяций на основе кубических сплайнов,  но всё это полумеры, так как время, которое тратится на передвижение игрока к нужной позиции, способно ввести в заблуждение других игроков. А ведь все проблемы, связанные с задержками, вызваны взаимодействием игроков друг с другом. На сервере есть самая актуальная информация и действии игроков, и пока сервер не скажем игровым клиентам об этих действиях, эти действия не произойдут. А предугадывание действия оставим для полёта взрывчатого снаряда, предугадывание годится только для вещей с линейным движением.

Будем ориентироваться исключительно на сервер, получать информацию о действиях игрока только с сервера, пока сервер не скажет — ничего не меняется. Но игрок будет точно недоволен несвоевременной реакцией игрового персонажа на нажатие клавиш.  Игрок банально теряет контроль над своим героем. Что делать? Брать и физически насиловать дизайнеров и аниматоров, разумеется }:  ). Пусть анимация содержит задержки, вроде торможения персонажа после бега, приседание перед прыжком, поворот оружия перед выстрелом, это позволит скрыть несовершенства клиент-серверной архитектуры.  И, как очевидно, имеет смысл сделать игру чуть медленнее, чем мечтает арт-директор, это тоже поможет дать запас времени для обмена данными с сервером.

Из оптимизации есть кое что ещё: многопользовательские игры масштабируются по функции О(n^2), где n = 128, это игроки. Один из игроков сделал ход, соответственно, всем игрокам надо об этом сообщить, и для каждого игрока придётся создать подключение. Количество передаваемой информации, как вы видите, очень высоко, нагрузка на сеть растёт. Но ведь один игрок не может видеть сразу всех 128 игроков, ни при каких обстоятельствах. Тогда количество соединений можно оптимизировать. Если из этих 128 игроков половина в нашей команде, то количество информации об их действиях также можно оптимизировать. Ведь не факт, что они способны нанести нам урон или даже врезаться в нас по идеологии игры.

Вы уже знаете, что интерполяция с линейным сглаживанием весьма неоднозначное решение, и назвать хорошим его нельзя, а вот упомянутая интерполяция с использованием кубических сплайнов выглядит более интересно. Из школьного курса математики все помнят, что линейная функция может быть представлена уравнением с переменной первой степени, например x = 5*y. В кубической функции как минимум один член возводится в степень, x = 5*y ^2. Подробнее здесь.

Есть и другие хитрости, например все знают, что в MMORPG распространено перемещение персонажа кликом по локации. А ведь это так просто! Передали информацию о том, куда надо направить персонажа и запустили анимацию, никаких проблем. Персонаж бежит, игра рисует красивую живую картину, сервер успевает отрабатывать. В крайнем случае, можно уменьшить скорость игрока на несколько процентов, что даст запас времени в несколько десятков мс и это будет незаметно для игрока.

Немного углубимся в проблему написания приложения. Настраивать IP-адресацию на стороне клиента не удобно. Представьте типичного казуального игрока, который сам прописывает IP-адресацию. Так себе картинка. Вполне понятно желание избавить пользователя от такой настройки. Для этого используется DHCP, конфигуратор хостов. Настраивает не только IP, но и hostname , DNS и так далее.

Как это работает? Используется простая широковещательная рассылка, DHCP работает через протокол UDP. DHCP-клиент на компьютере знает, в каком формате и что отправлять. На третьем уровне модели OSI нет IP, поэтому destination 255.255.255.255, а source 0.0.0.0. На втором урвоне мы не знаем MAC-адрес, адрес назначения ffff.ffff.ffff.

  1. Первый этап DHCPDISCOVER, просто стремяем в сеть сообщением о том, что хотим получить хоть какой нибудь IP-адрес.
  2. Второй — получение на компьютер некого количества DHCP Offer.
  3. Третий шаг, когда получили то, что надо, отправляем DHCP Request, таким образом подтверждаем, что готовы взять такой IP-адрес.
  4. Четвертый шаг — сервер подтверждает отдачу IP-адреса. IP-адрес выдается в аренду серверу, минус во всем тут это тип взаимодействия — broadcast. DHTP-сервер должен стоять в одном проадкаст-домене с нашим компьютером, хочется централизованного нахожение DHTP-сервера.

Решается с помощью DHCP relay, который отдает сообщение в DHCP-сервер, посылает конкретный UDP с одинаковым source-портом. В роутер прилетает broadcast, роутер переделывает сообщение из broadcast в unicast. После этого сообщение улетает на DHCP-сервер юникастом, смотрит source IP, понимает подсеть и отвечает обратно юникастом. Так выдаются IP-адреса всем устройствам централизованно. Все это звучит довольно сложно.

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

Тут есть два протокола: TCP и UDP. Например, телефонный разговор это огромное количество маленьких пакетиков. Голос обрабатывется кодеком, он оцифровывается и передается маленькими порциями по сети. Предположим, что по буквам. Слово «привет» передается как «п-р-и-в-е-т». Если потерялся пакет с букаой «В», то через секунду он нам уже не нужен. А вот другой пример: перевод денег со счета на счет в банке. Нам важно перевести именно ту сумму, которую указал пользователь Нельзя терять ни одну цифру. В этом случае нужно ждать потерянную информацию.

TCP обеспечивает гарантию доставки пакетов или заканчивает сессию. Считается, что UDP быстрее TCP, но тут вопрос с количестве переданной информации. Если речь о небольшом размере информации, то UDP быстрее, а на 1Гб TCP будет быстрее.

Гарантировать доставку всей информации не просто. Клиент и сервер друг о друге ничего не знают. Пакеты нумериуются, и TCP для начала обмена данными устанавливает соединение. UDP не устанавливает соединение, поэтому оно и быстрее на небольшом количестве данных. Компьютер отправляет специальное TCP-сообщение с флагом TCP SYN, а на сервере весит некая программа, к которой можно подключаться к порту 80. И сокеты живут в ожидании новых подключений.

Пакет прилетел на сервер, и сервер отвечает флагами acknowledgement + syn. Говорит, что его точка отчета = y. Компьютер клиента отвечает серверу пакетом y+1, и после этого начинается обмен данными.

TCP для установки сессии использует минимум 3 пакета, это называется 3-way handshake. Даже если у вас скорость интернета 400 Гбит/сек, это никак не ускорит процесс установки соединения. Если сервер далеко, то будет большая задержка в миллисекундах. Распространение света между Питером и Москвой это 5-7 мс, а еще надо преобразовать свет в электричество, этот процесс невозможно ускорить. А между Москвой и Нью-Йорком задержка 200 мс.

А теперь UDP. Есть компьютер и сервер, компьютер просто отправляет пакет в сторону сервера. И нет функции проверки доставки пакетов. Если при написании приложения мы указали неправиоьный IP-адрес, мы этого никогда не узнаем.

Протоколы без установки соединения используются, когда требуется доставка небольших порций данных и когда потеря или порядок переданных сообщений не критичен, когда скорость передачи важнее гарантированной доставки всех сообщений. UDP хорош для онлайн-трансляций, VoIP (либо искажение голоса/потеря пакетов, либо задержка/дождаться всех пакетов). И UDP популярен в реализации онлайн-игр.

Проблема в том, что IP-адресов мало, и часть недоступна для компьютеров, например, диапазон 224.0.0.0 — 239.255.255.255 не может быть назначены на наш сетевой адаптер. Поэтому используется NUT, который группе адресов не из интернета присваивает один публичный IP-адрес, который смотрит в интернет. NUT технология ужасная и ретроградная, но она есть.

Порты. Мы вписываем некий URL в приложение (браузер), а так как браузер по умолчанию заточен под работу с вебом, то порт либо 80, либо 443. И мы можем не писать порт руками в браузерной строке. Мы его всегда знаем, он задан разработчиком в приложении. HTTP = 80, HTTPS = 443, DNS = 53. Можно в явном виде написать http://ya.ru:45/, тогда будем пытаться стучаться на порт 45. Если у яндекса нет странички на таком порту, то ничего не откроется.

Это про destination port, а source port выбирается случайным образом из 65 000 вариантов. Это позволяет создавать разные сессии между одним и тем же отправителем и получателем. А у сокетов используется 5-tuple: протокол, source IP, source port, destination IP, destination port. Эти 5 значений позволяют создавать уникальную сессию. Зачастую это source port. Единственный случай, когда порты могут закончиться, это балансировшики нагрузок.

Резюмируя, проблема синхронизации в играх — это обман, в котором участвуют и дизайнеры, и программисты. Сначала нужно делать проект технически правильно, и исходя из результата уже накручивать красоту, это и есть задача дизайнера и художника-аниматора, который работает в команде над IT проектом.

12 комментариев

  1. Саша Власов

    Здравствуйте! У меня следующий вопрос: как запустить SWF на маке?

    • your-scorpion (Author)

      Если не один из браузеров не воспроизводит, то попробуйте standalone версию Flash-плеера.

      • Igor cloobok

        С какими настройками вы озвучивали ваши мобильные приложения и игры?

        • your-scorpion (Author)

          -6 децибел, ogg для chrome и unity, mp3 для flash.

          • Никита Красов

            Привет! а каким редактором пользоваться для озвучки игр? спасибо!

          • Цветков Максим (Author)

            Довольно широкий вопрос. В редакторах, в UE4 точно есть свои внутренние инструменты для этого. Либо как стандарт — Wwise или Fmod. Wwise умеет худо-бедно интегрироваться в UE4.

  2. Василий Гаала

    Добрый день, не могли бы подсказать, для игр нужно дополнительно использовать субтитры, если есть озвучка на множестве языков?

    • Цветков Максим (Author)

      На мой взгляд нужно, есть же много факторов, которые не позволят послушать аудио. Например, громкие звуки в игре могут заглушать диалоги персонажей, или игрок отключит вообще весь звук в игре и включит на фоне свою музыку. Не все играют в наушниках, а колонки могут быть включены весьма тихо из уважения к остальным жильцам. Да и для не носителей языка субтитры очень помогают. Не забывайте еще и про людей с нарушением слуха… я бы даже добавил в настройки размер субтитров и заложил контрастный режим для людей с дислексией (черный фон и белый текст). Обязательно почитайте гайдлайны BBC про размер субтитров.

      + посмотрел на официальную статистику за 2018 год: более 60% игроков играют с включенными субтитрами.

  3. Илья

    Вопрос не совсем по теме, но как оптимизировать графику для веб-игр или крутых сайтов, там же спрайты могут и 100 мегабайт достигать! И еще не понятно, как одна и таже картинка весит по разному в редакторе и в проводнике.

    • Цветков Максим (Author)

      В проводнике размер картинки рассчитан со сжатием, а некоторые редакторы показывают картинку без сжатия. 1 байт на канал, 4 байта на пиксель, 4х8 = 32 бита на пиксель. 2048х512х32/8/1024/1024 = 4 мб.

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

      Раз возник такой вопрос, то надежды на дизайнера нет, и юзайте TinyPNG как плагин в галпе/вебпаке при билде проекта, вот npm. Можно сжимать jpeg с помощью mozjpeg, а альфу держать отдельно в 2 бита, если без мягкой прозрачности. Если делаете для веба и все серьезно, то используйте для дебага nvidia nsight, может вам надо не спрайты оптимизировать, а шейдера и батчинг.

  4. Ilia Zviagin

    Добрый день! возник вопрос с коллегами, даже спор. WASD раскладка клавиатуры это самое оптимальное для управления персонажем?

    • Цветков Максим (Author)

      С момента выхода WoW, WASD точно стало стандартом. А устоялась еще в 1995-м году на чемпионате по Quake. Используется и для управления камерой. Время от времени люди ищут альтернативу, вроде WADX + S для приседания, или ESDF (в которой труднее дотянуться до Shift или Ctrl). Бывают даже всякие ASXC.

«Взаимодействуя с данным сайтом, вы, как пользователь, автоматически даете согласие согласие на обработку персональных данных» Согласие

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.