AI в Roguelike-играх - Общий путь как это сделать

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

Данная статья была написана для RLNews Darren'а Hebden'а

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

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

Сначала нам нужно определить простой класс "Существо" (Creature).

 class Creature {
   friend class Controller;

 public:
   void SetAIModule(AI_Algorithm*);

 private:
   AI_Algorithm* m_ai;
 };

Класс "Контроллер" (Controller) оперирует всеми взаимодействиями между Существом и его классом AI.

 class Controller {
 public:
   Controller(Creature&);

   // Метод MoveBy представлен как простой пример. Другие методы
   // могут включать кастование, надевание предметов и т.п.
   void MoveBy(int delta_x, int delta_y);

 private:
   Creature& m_creature;
 };

Класс "Алгоритм AI" (AI_Algorithm) является абстрактным базовым классом для всех прочих. DoAIAction (Выполнить действие AI) - единственный общий метод между классами. Когда наступает очередь хода существа, просто вызовём m_ai->DoAIAction();

 class AI_Algorithm {
 public:
   AI_Algorithm(Controller&);

   virtual void DoAIAction() = 0; // pure virtual method

 protected:
   Controller m_controller;
 };

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

 class AI_GoToXY : public AI_Algorithm {
 public:
   AI_GotoXY(Controller&);
   AI_GotoXY(Controller&, int x, int y);

   void SetDestination(int x, int y);

   // Перекрытый метод DoAIAction() вычисляет короткий путь (если он уже
   // не вычислен) и перемещает существо на один шаг по этому пути.

   virtual void DoAIAction();

 protected:

   // Класс, который генерирует и хранит путь к точке назначения.
   // Этот класс будет рассмотрен в отдельной статье.

   Path m_path;
 };

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

 struct XYPoint { int x,y; };

 class AI_WanderRandomly : public AI_Algorithm {
 public:
   AI_WanderRandomly(Controller&);

   // Если сущетсво не имеет назначения, метод DoAIAction() случайно
   // выбирает (x,y) позицию, и посылает ее в GoToXY модуль
   // через m_gotoxy.SetDestination(x,y).

   virtual void DoAIAction();

 protected:

   virtual XYPoint GetRandomLocation() const;

 private:
   // Вместо агрегации, частное наследование также может быть использовано.

   AI_GoToXY m_gotoxy;

   int m_destination_x, m_destination_y;
 };

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

 typedef std::vector<XYPoint> Polygon;

 class AI_PatrolArea : public AI_WanderRandomly {
 public:
   AI_PatrolArea(Controller&, const Polygon&);

   // Альтернативно:

   // AI_PatrolArea(Controller&, XYPoint top_left, XYPoint bottom_right);
   // AI_PatrolArea(Controller&, XYPoint center, int radius);

 protected:

   // Перекрытый метод от AI_WanderRandomly случайно выбирает (x,y)
   // позицию из области патрулирования.

   virtual XY GetRandomLocation() const;

 private:
   Polygon m_area;
 };

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

 class AI_ChaseCreature : public AI_Algorithm {
 public:
   AI_ChaseCreature(Controller&);
   AI_ChaseCreature(Controller&, Creature&);

   void SetTarget(Creature&);

   // DoAIAction проверяет, что путь нуждается в обновлении (через
   // NeedUpdate() метод) и использует созданный путь.

   virtual void DoAIAction();

 protected:

   // Метод NeedUpdate возвращает 'true' если путь нуждается в пересчете.
   // Этот метод берет в расчет дистанцию до
   // существа-цели as well as the distance the target has moved since
   // the last update.

   bool NeedUpdate() const;

 private:
   Creature* m_target;
   AI_GoToXY m_gotoxy;
 };

Класс "Оценка риска" (EstimateRisk) - просто вспомогательный класс для других классов AI. Он (как-нибудь!), оценивает риск при атаке преследуемого существа.

 class AI_EstimateRisk : public AI_Algorithm {
 public:
   AI_EstimateRisk(Controller&);
   AI_EstimateRisk(Controller&, Creature&);

   void SetTarget(Creature&);

   int GetRiskFactor() const;

   virtual void DoAIAction();
 };

Класс "Охраняемая область" (GuardArea) просто прокладывает путь подобно классу "Область патрулирования" (PatrolArea) пока в пределах области патруля не будет замечен нарушитель. В этой точке класс оценивает риск (агрессивность создания может быть скорректирована через метод "Определение максимального риска" (SetMaxRisk())) и может атаковать нарушителя, используя класс "Преследование существа" (ChaseCreature).

 class AI_GuardArea : public AI_Algorithm {
 public:
   AI_GuardArea(Controller&, Polygon&);

   void SetMaxRisk(int);

   // Move along the path (f_patrol) until and each step check the
   // creature's line of sight for intruders.

   virtual void DoAIAction();

 private:
   AI_PatrolArea f_patrol;
   AI_ChaseCreature f_attack;
   AI_EstimateRisk f_risk;
 };

Объединяя существующие классы подобно тому как это сделано выше, можно относительно просто создавать любой тип класса от "AI_Тырить снаряжение" (AI_ScavengeEquipment) до "AI_Умри всё живое" (AI_KillEverything). Полный класс AI должен объединять более чем дюжину подклассов наподобие "Случайное шатание" (WanderRandomly), "Поиск еды" (SearchFood), "Оборона жилища" (DefendHome) и т.п. чтобы имитировать действия реального, живого существа. Для моей собственной roguelike-игры я надеюсь создать различные существа, использующие разные типы классов AI так, чтобы каждый тип существ имел определённое поведение (от трусливого сталкера (stalker) до агрессивного охотника).

Если у вас есть любые вопросы, идеи, комментарии и т.п. свободно пишите мне. Разработчик порталов (http://www.jyu.fi/~shang/portals)



Автор: Sami Hangaslammi.
Источник: Roguelike AI - Doing It The Generic Way.
Перевел: Дмитрий О. Бужинский [Bu], 19.05.2005.