Расширяемый AI существ

Материал из Клуб любителей рогаликов
Перейти к: навигация, поиск

Введение

AI монстров в большинстве игр оставляет желать лучшего... Монстры являются или слишком глупыми, слишком предсказуемыми или настолько неинтересными, что игра становится бесконечной, повторяющей "hack and slash". Я не возражаю против бессмысленного насилия, конечно. Но было бы приятно думать, что некоторые талантливые программисты могли бы сделать что-то немного более внушительное и атмосферное.

Что я собираюсь предложить, это - систему AI на плагинах, которая могла бы сделать создание некоторых интересных монстров немного более легким.

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

Что хорошо в этой модели? Вот - некоторые моменты:

  • Позволяет бесконечное разнообразие дружественного и недружелюбного поведения
  • Легкая расширяемость
  • Относительно небольшое внешнее кодирование, так что Вы можете сосредоточиться на AI непосредственно
  • После создания, монстры могут сами заботиться о себе
  • Никаких огромные switch-выражений (Ура!!)

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

AI Модель

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

Основное укороченное определение класса:

 // это - определение на наш основной класс мобильного объекта (существа)
 class Mobile extends .... implements ..... {

   // это ключевая строка.... ссылка на текущий объект AI
   public AI ai;

   public void setAI(AI newAI) {
     ai = newAI
     // предшествующий объект AI будет автоматически убран

     // если требуется - некоторый другой код
   }

   // Все прочие поля и методы
 }

 // И это - родовой объект AI, от которого будут наследоваться все прочие
 class AI extends Object implements ...... {

   public void action(Mobile m) {

     // Обработка

   }

   // здесь прочие функции AI, эффекты например, психология и т.п..
 }

Всякий раз, когда наступает очередь специфического существа двигаться, Вы только делаете вызов

 monster.ai.action(monster);

Это инструктирует объект работать, и заставляет монстра исполнять любое положенное поведение.

Хорошо, это - основная теория, теперь мы идем дальше:

Пример реального мира

Пока так хорошо..., но какова конечная выгода? Хорошо, что мы достигли быстрого и простого определения поведения существа. Более важно для реализма и пользы интересного AI, то, что это позволяет нам изменять поведение существ динамически, как диктуют обстоятельства.

Мы можем сделать это, создавая подклассы объекта AI, унаследовав все базовое поведение и добавляя определенное поведение, которое мы желаем. Самый очевидный AI, для реализации - это стандартный монстр, который только перемещается к Герою и автоматически нападает.

 class AttackPlayerAI extends AI {
   // перекрываем базовый метод
   public void action (Mobile m) {
     // вызываем стандартную функцию перемещения чудовища, если игрок - видим
     if m.canSee(Hero)
     {
       m.moveTowards(Hero.x,Hero.y);
     } else {
       // Просто притаиться здесь и не делать ничего
     }
   }
 }

Если мы теперь хотим создать кобольда в локации (10,10) с этим простым поведение, мы только пишем:

  Mobile m = new Kobold(10,10);
  m.setAI(new AttackPlayerAI());

И вот... один особенно недружелюбный кобольд был только что рожден.

Как второй пример, этот класс реализует существо, которое ходит между двумя точками, например охранник, патрулирующий стену замка.

 class GuardAI extends AI {
   // здесь хранятся координаты целевых тайлов (tx1,ty1) и (tx2,ty2)
   int tx1;
   int ty1;
   int tx2;
   int ty2:
   // direction==1 направляет перемещение к цели 1 иначе к цели 2

   int direction  

   // Конструктор для AI для передвижения от (x1,y1) к (x2,y2)
   public GuardAI (int x1, iny y1, int x2, int y2) {
     tx1=x1;
     ty1=y1;
     tx2=x2;
     ty2=y2;
     direction=2; // запуск перемещения от 1st до 2nd цели
   } 


   // Перекрытие базового метода
   public void action (Mobile m) {
     // определение координат текущей цели
     int tx = (direction==1) ? tx1 : tx2;
     int ty = (direction==1) ? ty1 : ty2;

     // перемещение
     m.moveTowards(tx,ty)

     // изменение направления, если цель достигнута
     if ((m.x==tx)&&(m.y==ty)) {
       direction = (direction==1) ? 2 : 1;
     }
   }
 }

Теперь, чтобы создать охрану, которая патрулирует между (10,10) и (20,5), нужно использовать:

  g = new Guard(10,10);
  g.setAI ( new WanderAI(10,10,20,5) );

Если игрок нападает на охранника, тогда нужно сделать охранника враждебным существом, представленным другим AI. Это делается следующим образом:

  g.setAI ( new AttackPlayerAI() );

где AttackPlayerAI - стандартный потомок AI, который превращает существо во врага к игроку...

Расширяемость

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

 class HostileGuardAI extends GuardAI {

   // перекрываем предыдущую реализацию
   public void action(Mobile m) {

     if (m.canSee(Hero)) {
       // Изменяем AI.
       m.setAI (new AttackPlayerAI()); 
       Hero.message("The " + m.name() + " yells with rage!");
     } else {
       // вызываем унаследованный метод для продолжения патрулирования....
       super.action(m);
     }
   }
 }

Действительно приятная вещь в этой системе то, что она почти бесконечно расширяема. Вы можете делать новый объект AI для почти любого типа монстра. Только несколько идей для объектов AI:

CowardlyMageAI ()

Ожидать и кастовать заклинание на игрока, ЕСЛИ нет никаких существ между ним и сердитым героем, иначе бежать к самому близкому запасному выходу.

SleepAI (int time, AI wakeup)

Спит период времени, затем переключается на новый объект AI. Находка для гипноза, например, который делает существо дружественный после пробуждения.

LurkAI (int dist, AI whatnext)

Ждет на том же самом месте, пока игрок не окажется в пределах указанного расстояния. Тогда делает что-то иное, например убегает, нападает, или предлагает торговать.....

FleeAI ()

Убегающий! Этот объект AI удостоверяется, что существо остается так далеко от игрока насколько возможно. Вы могли бы дать существу ценный предмет, и заставить его прятать+искать повсюду в темнице.

SequenceAI (AI firstAI, int time, AI secondAI)

Использовать firstAI определенное количество периодов времени, тогда переключиться на secondAI. Вложенные SequenceAI объекты могут использоваться, чтобы создать комплексное поведение.

Список возможностей - бесконечен...

Думая об этом некоторое время, я думаю что психологические эффекты некоторых заклинаний нужно вероятно передать объекту AI, который может иметь дело с ними подходящим способом. Например, UndeadAI мог бы фильтровать любую панику или эффекты замешательства. Если необходимо, AI мог бы реагировать, устанавливая новый объект AI. Например тот, кто первоначально враждебен гоблину, и поражен паникой, должен присовить объект FleeAI.

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

Расширения

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

Так как каждое существо содержит только *ссылку* на объект AI, приходит естественно мысль, что несколько существ могли бы разделить один и тот-же объект AI.

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

Но Вы можете создать специальный набор AI "Группы", который бы позволил группе монстров действовать совместно, со встроенным стайным инстинктом и сотрудничеством в сражениях. В основном, Вы бы построили единственный AI, и назначили его на всех монстров в группе, которая тогда получила бы совместное поведение.

  AI ai = new HositlePackAI ();
  Mobile m1 = new Orc(10,10);
  Mobile m2 = new Orc(10,10);
  Mobile m3 = new Goblin(10,10);
  Mobile m4 = new Goblin(10,10);
  Mobile m5 = new Troll(10,10);

  m1.setAI(ai);
  m2.setAI(ai);
  m3.setAI(ai);
  m4.setAI(ai);
  m5.setAI(ai);

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

Вывод

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

Счастливого Кодирования!



Автор: Mike Anderson.
Источник: Plug-in Monster AI, Copyright © 5.03.1999.
Перевел: Серж В. Ждановских [Alchemist], 07.10.2005.