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

Хочу предупредить, что статья нацелена на новичков. Людей которые хотели бы написать свою первую многопользовательскую игру. Если вы хоть раз занимались сетевым взаимодействием в играх, ничего полезного здесь не найдёте.

Техническое отступление


И дабы не остаться теоретическим материалом была написана небольшая демка. Ее можно запустить, посмотреть какими данными обмениваются клиент и сервер. На чьём примере и будет рассмотрено клиент-серверное взаимодействие.

Приложение использует технологию canvas для графики и websockets для взаимодействия с сервером. Код не является предметом обсуждения, поэтому писался быстро (основная часть около 2-3 часов), без проектирования и рефакторинга. Я не рекомендую использовать его или его часть где бы то ни было.

Вы выбираете героя, сервер вас регистрирует, ищет игру. Ждёт пока не войдут достаточное кол-во игроков.



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

Каждый герой имеет свои характеристики атаки, защиты, дальности хода, запаса жизней.

Выбор героя осуществляется клавишами 1 — 4. Поиск игры с выбранным героем — Enter.



Если ход принадлежит вам, то на экране появится область хода. Передвижение осуществляется клавишами влево, вправо, вверх и вниз. Если вы достаёте до героя вражеской команды, возможно нанесение урона. Подтверждение действия — Enter. Закончим с этим.

Можно было и не так вырвиглазно нарисовать, но не суть.

Какие данные имеются?


Как и в любой онлайн игре, мы имеем данные об игроках. Тут для упрощения данные о игре и игроках живут только на протяжении игры, а в дальнейшем удаляются.

По порядку:

  • Местоположение всех юнитов в игре;
  • Парамерты героев, выбранных игроками;
  • Принадлежность к команде;
  • Очерёдность хода.

Поговорим отдельно про каждый пункт.

1. Местоположение юнитов. Конечно на основе этой информации рисуются юниты на поле, но можем ли мы не хранить эти данные на сервере? Нет, и вот почему. Если эта информация не хранится, то и проверять то как передвигаются юниты мы не можем. А значит в независимости от скорости персонажа, он может хоть в другой конец карты, хоть на луну.

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

3. Очерёдность хода так же влияет на игру. Мы же не ходим чтобы кто-то ходил по пять раз за ход, оправдываясь что у него сработала инициатива.

4. Принадлежность

Теперь всё вышеперечисленное на примере запроса на атаку (unit1 атакует unit2):

ATTACK unit1_id unit2_id

Запрос в реальной игре, в зависимости от механики, может посылать дополнительные данные:

{"game_key":"...","private_key":"...","action":"attack","attacked":{"x":4,"y":2},"old_position":{"x":4,"y":3}}

Где attacked это положение атакуемого юнита, a old_position — положение откуда будет производиться атака.

На самом деле это всё, что нужно серверу. Ему остаётся провести следующие операции:

  1. Проверить что ход принадлежит unit1
  2. Проверить что запрос отправлен от пользователя которому принадлежит unit1
  3. Проверить может ли unit1 атаковать unit2

Если все проверки проходят, то сервер изменяет состояние и отсылает эту же операцию всем клиентам, а те просто приводят своё состояние в актуальное. Вот так это выглядит на картинке:



Как клиент обновляет своё состояние? Запрос сервера (да, именно запрос):

{"new_position": {"y": 2, "x": 2}, "request": "move"}

И этого достаточно. Ведь игра знает какому юниту принадлежит ход. И знает что информация проверена. А значит нужно просто перерисовать активного юнита по новым координатам.

Если вы только начинаете, не грех будет дать совет, дублируйте вообще всю информацию.

Как поступать с неверными данными?


Это самый сложный вопрос, однозначного ответа на него нет. Можно например не принимать эти запросы и оставлять состояние игры неизменным. Можно блокировать игрока посылающего такое запросы, а победу присудить его сопернику. Можно дополнительно изменять параметры такого игрока, путь его герой имеет всего одну жизнь (или искусственно завысить сложность игры). Тут мы ограничены только воображением.

Исходный код

Если найдётся достаточно пользователей можно потестить.

P.S. Ищу работу Python/Django разработчика. По всем вопросам в личные сообщения или на почту.
В теме сетевого завимодействия (СВ) я бы прочитал о

Проголосовало 33 человека. Воздержалось 13 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Поделиться с друзьями
-->

Комментарии (10)


  1. alexkunin
    21.01.2017 12:00
    +2

    А зачем клиент в команде атаки посылает координаты текущего положения? Эта информация должна уже быть на сервере, клиент только указывает направление атаки.


    1. deQU
      21.01.2017 20:08

      если бы там была физика — выявить рассинхрон, например, и просчитать движение с учётом отклонения


      1. alexkunin
        21.01.2017 20:26
        +1

        Совершенно верно, но только тут походовка, отсюда и мой вопрос.


    1. RokkerRuslan
      21.01.2017 22:14
      +1

      Тут вот какая ситуация. Он посылает не текущее своё положение, оно уже есть на сервера как вы правильно сказали, а то место откуда будет нападать, так как для атаки нужно приблизиться к атакуемому.


      Так как передвижение происходит стрелками, юнит должен пройти весь путь (зелёные стрелочки), но все передвижения не передаются на сервер, только те координаты откуда будет он атаковать (красная стрелочка).


  1. NadyaO
    21.01.2017 18:39

    Интересная статья, спасибо!


  1. foxmuldercp
    21.01.2017 19:29

    То есть, всё сводится к аутентификации и авторизации (jwt token в заголовках http давно один из RFC стандартов, например) и проверке, что user id 1 это таки юнит, расположенный по х:2, у:4 и может перейти в точки в радиусе одной единицы от текущей ({move:{'up','1'}}) и если дельта перемещения >1 — считать команду не валидной.


  1. marsermd
    21.01.2017 19:38

    Здравствуйте. Я правильно понимаю, что игра пошаговая? Если да, зачем передается координата old_position в атаке?


    1. RokkerRuslan
      21.01.2017 22:15

      Ответил в этом комментации


  1. malbaron
    22.01.2017 06:41

    Это самый сложный вопрос, однозначного ответа на него нет. Можно например не принимать эти запросы и оставлять состояние игры неизменным. Можно блокировать игрока посылающего такое запросы, а победу присудить его сопернику. Можно дополнительно изменять параметры такого игрока, путь его герой имеет всего одну жизнь (или искусственно завысить сложность игры). Тут мы ограничены только воображением.


    Можно возвращать на запросы подтверждающие ответы. С указанием ответом изменения состояния игры.
    В простейшем случае это будут полные дубли.

    Клиент должен принимать решения не по своему усмотрению, а на основании ответов.

    И защита от читеров.
    Защита от сбоев синхронизации на клиенте.
    И возможность предсказания клиентом что следует рисовать (как приходит ответ от сервера — клиент уточняет картинку графическую).


  1. Deosis
    23.01.2017 07:32

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