Загрузка...
 
Печать

Создание игровых террейнов(external link): Тайловая графика(external link)

(Game terrain creating: Tile-based graphics)


Одним из ключевых элементов при разработке многих игр является создание террейн-движка (terrain engine). Принимая во внимание, что действие большинства игр (стратегия, RPG, экшн и т.д.) разворачивается на твёрдой поверхности, значение террейна (т.е. местности, земной поверхности, геологической локации) трудно переоценить. Существует множество методов прорисовки террейна, наиболее старым и классическим является рендеринг на основе плиток (tile-based rendering; далее - тайловый рендеринг).

Основы создания террейн-движка на основе тайлов (плиток)

Сперва немного теории.

Что такое тайл (плитка)?

Рис. 1 Примеры террейновых тайлов
Рис. 1 Примеры террейновых тайлов

Тайл представляет собой строительный блок террейна.1 Строительные блоки, как и тайлы, сами по себе ничего не делают. Но, собрав несколько тайлов воедино, они уже будут иметь определённое значение. Для понятия принципа действия тайлов достаточно рассмотреть обычную мозаику вроде картонных пазлов. Каждый элемент мозаики сам по себе ничего не значит. Но все вместе они образуют законченную картину. Тайлы в тайловом террейн-движке работают по тому же принципу. Только холстом здесь выступает земная поверхность (пусть и виртуальная), а кистями (элементами мозаики) - террейные плитки (terrain tiles) (См. Рис.1).
На Рис.1 изобржены тайлы леса, водной поверхности, скалистые горы, травянистая равнина и пустыня. Даже с таким ограниченным набором тайлов можно создать терейн, скажем, ограниченный со всех сторон горами.

Зачем нужны тайлы?

В игрокодинге значение тайлов трудно переоценить. Вот их преимущества:

  • Экономия оперативной памяти
  • Повторное использование повторяющихся графических элементов
  • Возможность создания динамического контента (например рандомно генерируемых карт).

Но обо всём по порядку.

Экономия оперативной памяти

Возьмём, к примеру, карту типичной 2D RTS размером 100 тайлов в ширину и 100 в высоту.

Image

Казалось бы, ничего особенного: обычная карта размером 100х100 тайлов. В общей сложности для создания карты задействовано 10 000 тайлов. А теперь представь, что вместо тайлов ты решил применить для прорисовки тайлов один огромный битмап (= растровое изображение).
Сперва рассчитаем объём памяти требуемый для тайловой карты. Мы просто перемножим количество тайлов в карте (10 000) на размер одного тайла:
100 тайлов в ширину х 100 тайлов в высоту = 10 000 тайлов.
64 пиксела в ширину х 64 пиксела в высоту = 4096 пикселей на каждый тайл.
10 000 тайлов х 4096 пикселей х 1 байт (8 бит) = 40 960 000 байт (при 256-цветной палитре).
10 000 тайлов х 4096 пикселей х 4 байта (32 бита) = 163 840 000 байт (при 32-битном цвете).
Получаем, что при отрисовке терейна (карты) с помощью одного битмапа размером 100 на 100 обычных тайлов потребуется 163 мегабайта памяти! Даже если снизить цветность до 256 цветов карта всё равно будет "весить" не меньше 41 мегабайта. Даже по сегодняшним меркам 163 мегабайта для карты из 2D стратегии - это слишком много.
А теперь рассчитаем объём памяти, требуемый для прорисовки терейна (карты) составленной при помощи 10 000 тайлов (100 х 100):
100 тайлов в ширину х 100 тайлов в высоту = 10 000 тайлов.
64 пиксела в ширину х 64 пиксела в высоту = 4096 пикселей на каждый тайл.
100 тайлов х 4096 пикселей (на каждый тайл) х 4 байта на каждый пиксел (32-битный цвет) = 1 638 400 байт.
10 000 тайлов х 1 байт на каждый тайл = 10 000 байт.
10 000 байт + 1 638 400 байт = 1 648 400 байт.
То есть, из набора тайлов (tileset), содержащего 100 различных тайлов, можно создать карту размером 100 х 100, затратив на это менее двух мегабайт памяти. Даже при наборе, содержащим 1000 различных тайлов, объём карты не превысит 20 мегабайт.
В итоге получаем, что главная причина применения тайлов в игрокодинге - экономия системных ресурсов и, в частности, системной памяти.

Повторное использование повторяющихся графических элементов

  • Очень важный аспект в игрокодинге, т.к. объём рабочего времени художников (как и разработчиков) обычно сильно ограничен.
Рис.2 Пример простой карты
Рис.2 Пример простой карты

На Рис.2 мы видим поле с травой и камнями. При дальнейшейм рассмотрении можно заметить, что многие камни очень схожи друг с другом. Теперь представь, что художник должен вручную отрисовывать каждый камень на карте. Это займёт кучу времени. Большинство гейм-дизайнеров не скрывают, что чаще им приходится многократно применять ранее наработанные зарисовки, нежели с нуля создавать игровой контент. Ведь кучу времени можно потратить и на более интересные занятия.
По сути карта на Рис.2 состоит всего из двух тайлов с камнями. Используя тайлы, геймдизайнер лишь "разбросал" эти готовые патерны с камнями по поверхности терейна. Это в разы упростило задачу художника, т.к. надо было всего лишь разместить тайлы в разных участках карты.

Возможность создания динамического контента (например рандомно генерируемых карт)

Динамический контент необходим для многих игр. Возьмём, к примеру, генератор случайных карт (random map generator). Одной из первых игр, впервые применившей генератор случайных карт была небезызвесвтная Civilization. Здесь игрок уазывал тип желаемой карты и игра генерирует её буквально "на лету". Используя тайлы, создатели Civilization просто применили ряд алгоритмов для более или менее адекватного распределния тайлов на игровой карте. Если бы карта представляла собой один большой битмап, то его рандомная генерация превратилаяь бы в сущий кошмар как для программиста так и для компьютера. Предаставь, каково это, прорисовывать каждый отдельный пиксел программно. В случае с набром тайлов, содержащим траву и камни, программист задаёт генератор случайных чисел (random number generator) для случайного размещения камней на игровой карте (терейне). Именно тайлы позволяют создавать динамический контент быстро и просто.

Как создают тайлы?

Создание тайлов - очень трудоёмкая и кропотливая работа. Конечно, многие творческие личности очень любят это знаятие.

Выбери размеры тайла (tile dimensions)

Сперва вооружись хорошим графическим редактором. В идеале - Adobe Photoshop(external link). Подойдёт и триальная версия программы, т.к. редактирование тайлов - относительно нехитрая операция с точки зрения графического дизайна. Многое можно сделать даже в обычном MS Paint.
Следующий шаг - выбери желаемый размер тайла. Для неизометрических наборов тайлов обычно применяют размеры, кратные числу 2: 32х32, 64х64, 128х128 и т.д. Выбор размера тайла зависит от ситуации. Каки-то определёных стандартов не существует. Но лучше исходить из следующих соображений:

  • Сколько тайлов потребуется игре?
  • Сколько памяти будет выделено для игровой графики?
  • Сколько тайлов одновременно будет видно во вьюпорте игры?

Два первых вопроса сильно взаимосвязаны друг с другом, т.к. число тайлов напрямую зависит от объёма памяти, необходимого для их хранения. Если игра требует набор в 1000 тайлов, то больших размеров лучше избегать. Если тайлов не больше сотни, то их можно сделать побольше.
Последний вопрос связан со способностью движка показывать в одном кадре тайлы без тормозов. Часть экранного пространства будет отведена под пользовательский интерфейс (User Interface, UI), т.е. под контрольную панель со статистикой, выбором построек и др. данными.

Рис.3 Пример простой карты размером 10х10 тайлов с интерфейсом игрока
Рис.3 Пример простой карты размером 10х10 тайлов с интерфейсом игрока

Оставшуюся часть занимает тайловая карта (tile map) размером 10х10 тайлов. Для того, чтобы подсчитать макс. размер тайла, необходимо минимальное игровое разрешение разделить на различные значения тайла. Максимальный размер тайла вычисляется по след. формулам:
Ширина экрана (Screen Width) / Требуемое число тайлов
Высота экрана (Screen Height) / Требуемое число тайлов
При разрешении экрана 800х600 пикселей макс. размер тайла составит (800/10) = 80 пикселей в ширину. Если подогнать это к правилу кратности двум, то макс. размер тайла лучше выбрать 64х64 пикселей.
Если такой рзмер покажется тебе слишком маленьким, то прикинь, сколько тайлов одновременно будет видно в кадре в каждый момент времени. На самом деле это зависит от того, сколько юнитов одновременно ты планируешь выводить на экран (макс. количество). Если в игре планируется контроль игроком многочисленной армии юнитов, то лучше выбирать размер плитки поменьше. Если наоборот, юнитов будет совсем немного (Warcraft III), то лучше выбирать размер плитки побольше.

Идентифицируй необходимые тайлы

Рис.4 Пример набора тайлов (tileset)
Рис.4 Пример набора тайлов (tileset)
Рис.5 Порядок вывода тайлов на экран (2D проекция)
Рис.5 Порядок вывода тайлов на экран (2D проекция)

Когда ты выяснил размеры тайла, можно сразу переходить к их созданию. С чего же начать? Начинай с тайла земной поверхности (ground tile), и на его основе рисуй остальные. Например в играх Age of Empires и Age of Wanders основные блоки террейна представляют собой траву (grass), грязь (dirt) и даже снег (snow).
Значит и ты начинай с базовых тайлов травы, грязи, снега и т.д., постепенно добавляя различные вариации. На сплошном футбольном газоне играть скучно. Если, например, в качестве базового тайла выбрана трава, то создай на её основе другие тайлы, добавив камней, деревьев, воды или даже грязевых луж (См. Рис.4).

Покажем ли тайлы?

Отображение тайлов в теории выглядит очень просто, но на деле это очень трудоёмкий процесс. Речь идёт о размещении тайлов на единой сетке (grid).
Существует 3 основных способа вывода отображения тайлов на экране:

  • 2D
  • Изметрическая проекция (Isometric projection)
  • 3D


Отображение в 2D-сетке

  • Самый простой способ.

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

int x,y;
// Показываем сверху вниз
for(y=0; y<10; y++)
{
  // Показываем слева направо
  for(x=0; x<10; x++)
  {
    // Твоя функция показа здесь
    DisplayTile(x,y);
  }
}

В вышеприведённом коде есть 2 замкнутых цикла (loop). Первый инкриментирует (с каждым повторением увеличивает на единицу) вертикальную позицию тайла. Второй цикл - горизонтальную позицию тайла. Разместив цикл горизонтальной прорисовки внутри цикла вертикальной прорисовки, получаем отрисовку всей сетки (см. Рис.5).
Обрати внимание, что в верхнем левом углу стоит номер ячейки 1, а в нижнем правом - 100.
Естесственно, что функци. DisplayTile() ещё предстоит написать. Но до этого необходимо подумать о том, как будет хранится тайловая карта (tilemap, она же сетка, грид). Самый распространённый способ - представить тайлмэп в виде одного большого массива (array). Для демонстрации данного метода рассмотрим следующий код:

...
// Global tile map array
int g_iTileMap[100];  // 10*10 = 100 точек (ячеек) необходимо

// Прототип функции отображения тайла
void vDisplayTile(int x, int y);

void main()
{
  int x,y;

  // Сверху вниз
  for(y=0; y<10; y++)
  {
    // Слева направо
    for (x=0; x<10; x++)
      {
       // Показываем тайл
       vDisplayTile(x,y);
      }
  }
}

void vDisplayTile(int x, int y)
{
  int iTile;
  int tileWidth = 64;
  int tileHeight = 64;
  int mapWidth = 10;

  // Подсчитываем значение тайла
  // в данной ячейке с координатами x и y
  iTile = g_iTileMap[(x + (y * mapWidth))];

  // Показываем битмап на экране
  // Данная функция является псевдокодом!
  // Её необходимо заменить реальной функцией.
  DrawBitmap(iTile, (x * tileWidth), (y * tileHeight));
}
...

Рис.6 Расчёт положения выбранного тайла в массиве тайлов.
Рис.6 Расчёт положения выбранного тайла в массиве тайлов.

В функции main(), приведённой выше, программа выполняет цикл, итерируя тайлы один за другим. В конце вызывается функция vDisplayTile(). Внутри цикла функция vDisplayTile() идёт код вычисления местоположения тайла в массиве тайлов (tile array). К координате x (x-position) прибавляется координата y (y-position), умноженная на ширину экрана (тайловой сетки) (см. Рис.6).

Как рассчитать текущую позицию в массиве?
Для получения начальной позиции тайла в массиве сначала смотрим его x-координату. Допустим нам нужно найти позицию в массиве тайлов для ячейки грида с координатами 0,5. На Рис.6 мы начинаем отсчёт с тайла, расположенного в левом верхнем углу, и перемещаемся на 5 тайлов вправо. Так мы пришли в ячейку, помеченную "A".
Теперь необходимо к этой координате прибавить y-координату тайла, умноженную на ширину карты в текущей позиции. Размер игровой карты (сетки, грида) составляет 10 тайлов в ширину. Поэтому прибавляем 10*5 (y-координата) к текущей позиции. Следуй за стрелкой вправо, а затем вниз и окажешься в ячейке, помеченной "B". Таким образом можно вывести след. формулу:
X(A) + (Y*MapWidth) = array position (B)
На Рис.6 массив карты (map array) изображён в виде сетки (=грида), но в реальности он представляет собой вполне линейный кусок памяти. Сетка здесь применена для лучшей наглядности. Главным преимуществом массива карты является то, что он в точности соответствует тому, что пользователь видит на экране. Здесь даже нет сложных связных списков (linked lists) с необходимостью их последующей трассировки. Просто один простой массив.

Как подсчитать видимую позицию (visual position)?
Следующей рассмотрим функцию DrawBitmap() из приведённого выше псевдокода. Она представляет собой функцию, которая содержит в себе (resembles) все вызовы графических процедур (graphics calls), применяемые для отображения 2D-битмапа (растрового изображения).
В вызове этой воображаемой функции есть 3 параметра:

  • Графический тайл (tile graphic). Здесь назначается растровое изображение (битмап), выводимое на экран. В реальной реализации можно обращаться напрямую к графическому файлу, либо указать номер (индекс) из заранее заготовленного списка отображения (display list).
  • экранная x-координата (on-screen x-position). Устанавливает x-координату ячейки, где будет отображаться данный тайл.
  • экранная y-координата (on-screen y-position). Устанавливает y-координату ячейки, где будет отображаться данный тайл.

В случае с нашим примером x- и y- координаты для тайла 5,5 буду следующими:
(5*64), (5*64) или 320, 320 (пикселей от начала отсчёта экранного пространства).
В результате получаем формулу подсчёта x- и y- (пиксельных) координат тайла:
X-координата = X-позиция тайла в сетке * Ширина тайла (Tile width)
Y-координата = Y-позиция тайла в сетке * Высота тайла (Tile height)

Отображение изометрических (isometric, т.н. 2.5D) тайлов
Здесь применяются более сложные методы, т.к. необходимо заранее подготовить специальные тайлы. Вообще, процесс создания 2D и 3D тайлов во многом схожи, т.к. и те и другие (чаще всего) имеют квадратную форму.
Изометрические тайлы можно встретить во множестве известных RTS-играх: Age of Empires, Civilization, Command&Conquer: Tiberium Sun и др. Главное преимущество изометрических тайлов состоит в том, что они выглядят как трёхмерные, не используя при этом настоящую 3D-графику.

Источники:


1. Todd Barron. Strategy Game Programming with DirectX 9.0. - Wordware Publishing Inc. 2003

Contributors to this page: slymentat .
Последнее изменение страницы Четверг 06 / Декабрь, 2018 03:24:06 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108