Написание ИИ для хоккея. Часть 2

hockey-ai-score

Прежде чем читать эту часть, советуем вам взглянуть на предыдущий урок. А в этой статье мы продолжим реализацию искусственного интеллекта для игры в хоккей с использованием «рулевого поведения» (steering behaviors), основанного на конечном автомате (FSM — finite state machine). Сегодня мы будем акцентировать наше внимание на атаке, не забудем о перехвате шайбы и научим наших хоккеистов забивать гол в ворота соперников.

Несколько слов об атаке

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

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

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

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

Организация атаки с помощью состояний

Мы разобьем всю атаку на несколько мелких частей, каждая из которых будет выполнять какую-то свою специфическую задачу. Все эти куски будут являться состояниями конечного автомата, основанного на стеке. Как было сказано ранее, каждое состояние будет прикладывать силу в «рулевом управлении» (steering force), вследствие чего хоккеист будет вести себя соответственно.

Управление этими состояниями, а также переключение между ними и определяет такое сложное явление, как атака. Изображение ниже иллюстрирует те самые состояния, на которые была разобрана вся атака:

Состояния атаки

Состояния атаки

Все эти состояния будут переключаться в зависимости от расстояния до шайбы и от того, кто является ее владельцем. Например, мы будем включать состояние attack, если выполняется условие team has the puck (наша команда владеет шайбой).

Как видно из изображения выше, атака делится на 4 стадии: idle, attack, stealPuck, и pursuePuck. Одно из них, состояние idle, уже было реализовано в прошлой статье. Оно является отправной точкой, с которой и начинается процесс атаки. Затем спортсмены переходят к:

  • attack, если команда уже владеет шайбой;
  • stealPuck, если соперники владеют шайбой
  • pursuePuck, если шайба никому не принадлежит и попросту скользит по льду

Давайте более подробно ознакомимся с этими состояниями.

attack описывает наступательные действия. Игрок, владеющий шайбой и именуемый лидером (leader), пытается продвинуться к вражеским воротам. Партнеры по команде тоже не стоят на месте. Они продвигаются вперед, чтобы оказать поддержку своему лидеру.

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

Ну и наконец, pursuePuck. Это состояние не относится ни к атаке, ни к защите и помогает игрокам следовать за шайбой в тех случаях, когда оно никому не принадлежит.

Обновим состояние idle

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

На изображении ниже вы можете наблюдать переходные состояния, в которые будут попадать игроки из idle:

Состояние idle и его переходы

Состояние idle и его переходы

Если команда спортсмена имеет при себе шайбу, то нужно переходить к атаке. Если же шайба у противников, то нужно переходить к состоянию stealPuck. В случае, когда шайба бесхозная и игрок находиться достаточно близко к ней, он переходит к состоянию pursuePuck. Вот и вся логика. А теперь приведем код:

Как вы могли заметить, методы attack(), stealPuck() и pursuePuck() объявлены, но не реализованы. Давайте реализуем их!

Следование за шайбой — pursuePuck

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

Состояние pursuePuck и три его перехода

Состояние pursuePuck и три его перехода

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

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

А вот код состояния pursuePuck:

Обратите внимание на 6 строчку из приведенного выше кода. Она отвечает за то, чтобы игроки не оставались слишком близко друг к другу во время активного состояния pursuePuck, поскольку это будет выглядеть не естественно.

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

 

 

 

Проведение атаки

После того, как игрок получил шайбу, он должен стремиться к воротам соперника и забить гол. Это и будет целью состояния attack.

Состояние attack и его переходы

Состояние attack и его переходы

Атака имеет два переходных состояния: pursuePuck и stealPuck. Игроки, будучи в состоянии attack, должны будут бежать к воротам противников. Давайте реализуем это:

Разберем приведенный код. Если у шайбы есть владелец, причем это игрок сам, то он несется к воротам противника (строка 13). Если же шайба принадлежит команде игрока, но он не является лидером, то следует бежать за нашим форвардом и помогать ему (строка 18). Обратите внимание на метод mBoid.separation() в строке 19, который мы использовали чуть ранее. На изображении ниже вы можете увидеть, как игроки помогают своему лидеру.

Помощь игроков своему лидеру

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

В случае, когда шайбой владеют противники, мы переводим игрока в состояние stealPuck. А если она никому не принадлежит, то игрок должен ее подобрать. Это делается переводом в состояние pursuePuck.

А теперь давайте посмотрим на результат:

 

 

 

Улучшение поддержки атаки

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

Присмотритесь на последний наш результат. В случае, когда какой-нибудь игрок A находится к чужим воротам ближе, чем лидер, то игрок A начинает вести себя немного неестественно, пока не окажется позади форварда.

Этот недостаток с легкостью можно исправить путем проверки положения игрока — находится он позади лидера или опережает его:

А теперь взглянем на полученный результат. Игроки ведут себя на порядок реалистичнее!

 

 

 

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

Отбор шайбы

Последним состоянием атаки является stealPuck, который становится активным, когда шайба принадлежит соперникам. Цель — отобрать шайбу для проведения собственной атаки.

Состояние stealPuck и его переходы

Состояние stealPuck и его переходы

А теперь перейдем к реализации:

Алгоритм предельно прост. Если шайба принадлежит команде игрока, то мы переходим к состоянию attack (строка 11). Если же она у команды противника, то игрок попытается перехватить ее. Однако, обратите внимание. Мы не можем передавать нашему игроку координаты противника, поскольку это приведет к тому, что наш хоккеист будет просто его преследовать. Именно поэтому игрок будет предсказывать то положение, в котором окажется противник в ближайшее время, и будет стремиться перехватить своего соперника. Все это реализовано при помощи «поведения преследования» (pursue behavior) в строке 19.

А вот и результат. Обратите внимание на то, как игроки отбирают шайбу у соперников.

 

 

 

Улучшение отбора шайбы

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

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

Все оставшиеся игроки останутся на своих местах, поскольку они слишком далеко. Целесообразно их перевести в состояние защиты, однако, мы пока его не реализовали. В следующем уроке мы вернемся сюда и дополним наш код.

Взгляните на полученный результат. Теперь игроки более адекватно ведут себя при отборе шайбы.

 

 

 

Избежание вражеских защитников

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

Мы можем использовать «избежание коллизий» (collision avoidance) для того, чтобы игроки могли уклоняться от противников. Вот, как это будет выглядеть:

Уклонение от противников

Для реализации уклонений нам следует добавить лишь одну строку (14 строка) в наш код:

Ниже вы сможете посмотреть на работу обновленного состояния attack. Противники обездвижены для большей наглядности.

 

 

 

Вывод

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

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

Перевод статьи «Create a Hockey Game AI Using Steering Behaviors: Attack»