Написать пост

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

Аватар Типичный программист

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

Несколько слов о защите

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

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

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

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

Сочетание нападения и защиты

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

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

Состояние defend будет фундаментом, основой всего процесса обороны. Находясь в этом режиме, игроки будут двигаться к бортику, попутно пытаясь отобрать у противника шайбу.

Состояние patrol является дополнением к defend. Оно будет выводить игроков из состояния покоя, будет держать их в постоянном движении и патрулировании некоторых зон, что приведет к лучшему результату.

Описание состояния defend

Состояние defend предельно простое. Когда оно активно, каждый спортсмен будет двигаться к своему изначальному положению на катке. Мы уже использовали эту координату, описанную в поле mInitialPosition класса Athlete, в методе prepareForMatch.

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

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

Реализация состояния defend

defend имеет четыре переходных состояния:

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

Три из них (team has the puck, close to opponent leader и puck has no owner) отвечают за атакующие действия игроков. Состояние in position будет срабатывать тогда, когда игрок вернулся в свою начальную позицию.

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

			class Athlete {
    // (...)
  
    private function defend() :void {
        var aPuckOwner :Athlete = getPuckOwner();
         
        // плавно перемещаемся к стартовой позиции
        mBoid.steering = mBoid.steering + mBoid.arrive(mInitialPosition);
         
        // есть ли у шайбы владелец?
        if (aPuckOwner != null) {
            // да, есть
            if (doesMyTeamHasThePuck()) {
                // шайба у моей команды, время перейти в атаку
                mBrain.popState();
                mBrain.pushState(attack);
                 
            } else if (Utils.distance(aPuckOwner, this) < 150) {
                // шайба у противника, он рядом, поэтому
                // пытаемся отобрать шайбу
                mBrain.popState();
                mBrain.pushState(stealPuck);
            }
        } else {
            // шайба никому не принадлежит; нет смысла переходить в защиту
            // надо подобрать шайбу
            mBrain.popState();
            mBrain.pushState(pursuePuck);
        }
    }
      
    // (...)
}
		

Пока активно состояние defend, метод arrive создаст силу, которая будет толкать игрока к его начальной позиции (mInitialPosition). Затем мы проверяем, есть ли у кого-нибудь шайба, а если есть, то у кого: у игрока нашей команды или противника. Также мы проверяем расстояние между нашим игроком и противником с шайбой. В зависимости от этих данных мы переводим игрока в подходящее состояние.

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

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

//

 

На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

 

 

Патрулирование

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

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

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

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

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

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

А вот и, собственно, реализация этого состояния:

			class Athlete {
    // (...)
  
    private function patrol() :void {
        mBoid.steering = mBoid.steering + mBoid.wander();
         
        // я далеко ушел от своей стартовой позиции?
        if (Utils.distance(mInitialPosition, this) > 10) {
            // да, надо остановить патрулирование и вернуться
            // к своей стартовой позиции
            mBrain.popState();
            mBrain.pushState(defend);
        }
    }

    // (...)
		

А теперь посмотрим на игру:

//

 

На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

 

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

Собираем все вместе

В прошлом уроке мы реализовали состояние stealPuck. Однако там возникала ситуация, когда дальнейшая попытка отобрать шайбу у противника теряла смысл. Нам следовало перейти в защиту, но на тот момент состояние defend не было реализовано.

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

Давайте дополним код:

			class Athlete {
    // (...)
  
    private function stealPuck() :void {
        // есть ли у шайбы владелец?
        if (getPuckOwner() != null) {
            // да, есть
            if (doesMyTeamHasThePuck()) {
                // шайба у моей команды
                // время переходить в атаку
                mBrain.popState();
                mBrain.pushState(attack);
            } else {
                // шайба у противников
                var aOpponentLeader :Athlete = getPuckOwner();
                 
                // соперник с шайбой рядом со мной?
                if (Utils.distance(aOpponentLeader, this) < 150) {
                    // да, он достаточно близок; 
                    // пытаемся предугадать его положение
                    // и стремимся перехватить его
                    mBoid.steering = mBoid.steering + mBoid.pursuit(aOpponentLeader.boid);
                    mBoid.steering = mBoid.steering + mBoid.separation(50);
                     
                } else {
                    // нет, соперник далеко; переходим в защиту
                    // и надеемся, что его перехватит другой игрок
                    mBrain.popState();
                    mBrain.pushState(defend);
                }
            }
        } else {
            // шайба никому не принадлежит; нет смысла ее у кого то отбирать
            // надо подобрать шайбу
            mBrain.popState();
            mBrain.pushState(pursuePuck);
        }
    }
      
    // (...)
}
		

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

//

 

На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

 

 

Вывод

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

На этом все! Мы создали полную систему искусственного интеллекта для нашей игры!

Ссылки

Спрайт: Хоккейный Стадион на GraphicRiver
Спрайты: Хоккеисты от Taylor J Glidden

Перевод статьи “Create a Hockey Game AI Using Steering Behaviors: Defense”

Следите за новыми постами
Следите за новыми постами по любимым темам
4К открытий4К показов