Простая линия взгляда

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

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

Эту статью некоторые из вас могут посчитать интересной. Я искал какой-нибудь простой код линии взгляда (line_of_sight, LOS), и после продолжительных поисков, я нашёл несколько исходников, которые и были именно тем, что я искал. На сайте x2ftp, я обнаружил коллекцию графических дагоценностей (graphical gems collection), которая содержала программу на Си, называвшуюся Цифровое Рисование Линии (Digital Line Drawing) Paul'а Heckbert'а. Программа была описана как: "digline: нарисуйте цифровую линию из (x1,y1) в (x2,y2), вызывая определяемую пользователем процедуру на каждом пикселе. Никаких отсечений. Использует алгоритм Брезенхема (Bresenham)". Если вам интересно посмотреть оригинальный код, я могу послать его вам или вы можете найти его на сайте x2ftp.

Оригинальный код LOS, который был в моей игре только проверяет, находится ли игрок в том же секторе, что и монстр. Если это так, то монстр может увидеть игрока. Я хотел, чтобы мой код LOS был такого типа, что зная координаты x,y монстра и координаты x,y игрока, вы могли бы нарисовать линию между этими двумя точками, чтобы проверить, блокируется ли обзор монстра какими-нибудь объектами. Это то, что сейчас есть у меня. Сейчас не имеет значения как далеко игрок, если обзор монстра не заблокирован, он может видеть игрока. Позднее я изменю это, чтобы ограничить расстояние, на которое монстр может видеть, но я также хочу, чтобы некоторые монстры могли видеть дальше, чем другие.

Код, о котором я буду рассказывать, будет модификацией кода, который я использую в моей программе. Я начал с попытки использовать код с как можно меньшими по возможности изменениями. При запуске моей программы выполнение этого кода вызывало её зависание на продолжительное время. После обширной диагностики и внесения изменений я, наконец, заставил её работать. Я обнаружил, что если игрок стоит в определенной позиции относительно монстра, то код не работает. Я выяснил, что если координата y игрока меньше или равна y монстра, программа работает правильно. Если x игрока меньше чем x монстра и y игрока больше, чем y монстра, тогда монстр не видит игрока, но программа не зависает. Если же x и y игрока больше, чем x и y монстра, программа зависает.

После продолжительного вырывания на себе волос и произнесения многих красочных фраз из области программирования :+) я, наконец, нашёл, в чём проблема. Оригинальный код использовал макрос, называвшийся SGN, чтобы возвращать знак разности x и y. Он определялся как:

   #define SGN(a)(((a)<0) ? -1 : 0)

Посмотрите на описание макроса "взять знак a, как -1, так и 1 если >= 0". Я еще учусь но, похоже, что он возвращает только или -1 или 0, а не -1 или 1 как сказано в описании.

После замены макроса на следующий:

   #define SGN(a)(((a)<0) ? -1 : 1)

всё по-видимому начнёт работать.

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

/* Код линии взгляда это Boolean функция *
 * которая возвращает FALSE если         *
 * монстр не может видеть игрока и TRUE  *
 * если может                            *
 * Это x и y координаты монстра          */
BOOL los(coord mx, coord my)
{
int t, x, y, ax, ay, sx, sy, dx, dy;

   dx = d.px - mx;
   dy = d.py - my;

   /* ax & ay: абсолютные значения dx & dy        *
    * умноженные на 2 (сдвиг влево на 1)          */
   ax = abs(dx)<<1;
   ay = abs(dy)<<1;

   /* sx & sy: знак от dx & dy */
   sx = SGN(dx);
   sy = SGN(dy);

   x = mx;
   y = my;

   /* Следующее утверждение проверяет если линия                   *
    * x доминирует над y или наоборот и соответственно зацикливает */
   if(ax > ay)
   {
      /* Цикл по X */
      /* t = обсолютное от y минус абсолютное от x деленное *
         на 2 (сдвиг вправо на 1)                           */
      t = ay - (ax >> 1);
      do
      {
         if(t >= 0)
         {
            /* если t больше либо равно 0 тогда *
             * добавить знак от dy к y          *
             * вычесть абсолютное от dx из t    */
            y += sy;
            t -= ax;
         }

         /* добавить знак dx к x                 *
          * добавить абсолютное значение dy к t  */
         x += sx;
         t += ay;

         /* проверить, если мы в позиции игрока */
         if x == d.px && y == d.py)
         {
            /* возвращает, что чудовище может видеть игрока */
            return TRUE;
         }
      /* продолжаем цикл, пока зрение чудовища не заблокировано */
      while(sight_blocked(x,y) == FALSE);

      /* Заметьте: sight_blocked - функция возвращает true       *
       * если объект по x,y координатам блокирует взгляд монстра */

      /* цикл завершается т.к. взгляд монстра блокируется *
       * возвращаем FALSE: монстр не видит игрока         */
      return FALSE;
   }
   else
   {
      /* Цикл по Y */
      t = ax - (ay >> 1);
      do
      {
         if(t >= 0)
         {
            x += sx;
            t -= ay;
         }
         y += sy;
         t += ax;
         if(x == d.px && y == d.py)
         {
            return TRUE;
         }
      }
      while(sight_blocked(x,y) == FALSE);
      return FALSE;
   }
}

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

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



Автор: Steve Register.
Источник: Line of Sight.
Перевел: Дмитрий О. Бужинский [Bu], 18.05.2005.