Galaxy2D Tutorials Галактика 2D
FAQ о клетках

1. Введение

 

Было много отзывов о первой версии этого документа и было много просьб о дополнительной информации, в этой версии FAQ я отвечу на все.

 

Этот FAQ по программированию графики, подобной, что использовалось в играх U6 и U7 от Origin. Многие из методов нацелены на системы с ограниченной памятью и пониженным быстродействием; но этот документ также включает альтернативные методы и предложения для более быстродействующих систем. Здесь только обзор методов, но имеются и другие методы.

 

2. Многослойные карты

 

Это основной трюк, так как с помощью многослойной карты можно сделать  множество эффектов, по сравнению с однослойной картой.

 

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

 

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

 

2.1 Прозрачность

 

В процедуре прозрачности необходимо копировать на экран только не прозрачные пиксели, как правило, прозрачные пиксели – это пиксели картинки со значением цвета равным нулю. Таким образом, можно накладывать одно изображение на другое. Например, наложить на траву изображение замка.

 

2.2 Многослойность

 

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

 

Вот как я использую эти три слоя:

 

Имя слоя

Типы клеток

Базовый

Трава, земля, грязь, кирпичи, камни, двери, вода...

Отделка

Деревья, камни, предметы ...

Объекты

Мечи, Предметы, Люди, Монстры, Ключи...

 

Простая структура объявляет карту с тремя слоями (C код)

 

        #define SIZE 128

        typedef struct {

                 unsigned char base[SIZE][SIZE];

                 unsigned char frng[SIZE][SIZE];

                 unsigned char obj[SIZE][SIZE];

        } maptype;

 

Или другое определение ... (чтобы адресовать слои непосредственно) (C код)

 

        typedef unsigned char maptype[3][SIZE][SIZE];

 

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

 

Слой Отделки позволяет, например, помещать одно и тоже изображение дерева на разные клетки, при этом дерево может стоять и на земле и на траве или даже на воде ;). Это мощный инструмент для создания клеточной графики.

 

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

 

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

 

3. Ограничение проходимости на карте

 

На основном слоя я обычно имею атрибут 'walkability' (проходимость). Это позволяет применить быстрый простой способ проверить как должен двигаться объект по данной местности, а также помогает управлять другими специальными особенностями.

 

В каждой позиции карты есть байт, который служит как для задания индекса клетки, так и для задания проходимости, я использую набор из 128 клеток, которые можно условно разделить так:

 

0-63

Обычная клетка, проходимые клетки, земля, трава

64-127

Обычная клетка, непроходимые клетки, стены

128-191

Специальные клетки, группа 1

192-255

Специальные клетки, группа 2

 

При выводе на экран, я беру индекс клетки, как значение от 0 до 127. Затем надо разделить это число на 64, и получаем значение от 0 до 3, которое оценивается таким образом:

 

0

Проходимость - OK

1

Проходимость - не OK

2

Происходит что-то особенное при попадании на эту клетку – группа 1

3

Происходит что-то особенное при попадании на эту клетку – группа 2

 

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

 

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

 

4. Исчезновение крыши над клеткой

 

Этот эффект может быть реализован при помощи простого выделения секций ваших базовых клеток (например, возьмем клетки 48-63) как тип «Потолок». Обычно, все клетки типа «потолок» закрыты сверху. Когда происходит отрисовка и дело доходит до клетки «потолок», затем вы проверяете видит ли игрок эту клетку. Если не видит, то надо отрисовать «крышу», которая соответствует данному «потолку». Если игрок видит клетку, то надо отрисовать базу, отделку и объекты, как обычно. Этот метод можно использовать только для крыш, не применяя его к постройкам.

 

5. Эффект наклона в слое «отделка»

 

Мне кажется красивым иметь эффект наклона в моих проектах, это привносит реализм (это когда стена занимает не только свою клетку, а слегка наклонена и попадает на клетку рядом).  Если у вас достаточно памяти, то лучший пути этого достигнуть, сделать 4 слой на вашей карте, и назвать его слой «наклона» или чем-то подобным (он может быть использован также и для крыш). Но поскольку большинство систем не имеют памяти для четвертого слоя, я рассмотрю метод с минимумом памяти.

 

Отрисуем основную часть ваших наклоненных стен, как ваш базовый слой, затем используем слой отделки для сохранения дополнительных бит, которые наклоняют клетку. Вы делаете специальную проверку, чтобы понять клетка на слое отделки наклонена или нет. Если наклонена, то вы рисуете слой объектов и игрока до рисования наклоненной клетки; и если нет, то сохраняется нормальный порядок: база-отделка-объект.

 

Это как раз то место, где слой «наклона» использовать проще. Это позволяет пропустить проверку и рисовать все в нормальном порядке, поскольку вначале рисуется нормальный слой отделки, а потом уже наклон.

 

6. Общие замечания о слое Объектов

 

Слой объектов в моих проектах – это тот же самый массив, что и любой другой на карте, состоит из байтов. Мне этого вполне достаточно для моих нужд. Каждое число этого слоя – это  индекс уникального объекта, ноль означает, что на этой клетке нет объекта. Я разделил весь диапазон на разные категории объектов, например, 1-127 – это монстры и люди, 128-255 для не анимированных объектов. В любом случае, я имею «разумность» (так же как и проходимость) назначенную на разные группы объектов.

 

Обычно я разбиваю группы по 16 объектов, что просто для вычисления. Ниже я привел пример такой разбивки в порядке понижения «разумности».

 

Номер группы

Индекс

Какое поведение имеет объект ...

0

0-15

люди  ... блуждающие бесцельно ...

1

16-31

люди / монстры, которые боятся игрока.

2

32-47

Спокойные монстры, блуждающие бесцельно пока не атакованы, после чего их индекс переключается в 3 группу

3

48-63

Те же самые монстры, которые теперь безумны!

4-5

64-95

Нормальные монстры, они подбираются к игроку...

6

96-111

Плохие монстры, они атакуют игрока

7

112-127

пули и стрелы, которыми стреляют в монстров

 

 

 

8

128-143

Ключи, или другие предметы, которые открывают двери

9

144-159

объекты типа оружие

10

160-175

Броня и т.п. ...

11

176-191

Деньги, или другая добыча.

12-13

192-223

Нормальные, простые объекты, подобно книгам и свечам.

14

224-239

Другие объекты из категории объектов

15

240-255

Объекты, которые сохраняют другие объекты, такие как сумки, сундуки, рюкзаки.

 

Все это только общая схема разбиения на группы объектов. Можно использовать «разумность», чтобы следить за поведением объектов. Таким образом, чтобы что-то сделать с объектом можно просто проверить его номер группы!

 

В больших проектах будет не достаточно 255 значений, тогда можно использовать числа с большим диапазоном, например от 0 до 65535. Этого должно хватить для объектов любой игры, которую я когда-либо запускал!

 

7. Несколько объектов на одной клетке

 

Этот вопрос возник при обсуждении моих методов. Если, честно я ограничился использованием только одного объекта на клетке, но, тем не менее, я придумал два способа, как преодолеть это ограничение. Самый простой метод, это выделить группу объектов, или отдельный номер для этого. Затем согласно этому номеру и координатам этой клетки, вы можете иметь списки объектов, которые надо отображать в этой клетке. Например, возьмем группу клеток 15, и координаты клетки x=14, y=14, тогда надо иметь примерно такую структуру:

 

typedef struct {

int x, y;

int type;

int * objs;

int count_objs;

} Add_Objs;

 

Иметь массив таких структур:

 

Add_Objs add_objs[MAX_ADD_OBJS];

 

Если вы отрисовываете эту клетку, то вам надо найти в массиве add_objs соответствующую запись и по ней вывести эти дополнительные объекты. Это можно использовать, например, если несколько сокровищ лежит на одной клетке.

 

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

 

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

 

8. Красивые эффекты

 

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

 

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

 

Другой эффект может оживить ваши клетки. Для этого нужен массив картинок, которые назначаются одной клетке, вместо одной картинки, затем надо организовать цикл смены этих картинок. Например, это можно использовать для слоя «Отделки», для реализация огня, дыма. Анимация людей или монстров также хорошее использование этого трюка.

 

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

 

Этот же эффект может быть достигнут с применением одной палитры, создавая ссылочные палитры. Для этого необходимо сортировать вашу палитру и создать перекрестные палитры для каждого уровня яркости. Взяв каждый цвет в палитре, и затемнив его на нужный уровень, затем надо найти в палитре ближайший подходящий цвет и сохранить номер этого цвета в перекрестной палитре. Эти перекрестные палитры могут быть вычислены  заранее и сохранены на диск, поэтому нет необходимости вычислять эти палитры в игре. Затем при рисовании затемненной клетки (для этого можно использовать вашу процедуру DrawTile, передавая туда соответствующий параметр) можно найти, используя соответствующую перекрестную палитру нужный номер цвета для каждого пикселя картинки и нарисовать это перекрестное значение на экране. Этот метод лучше, чем предыдущий, так как он позволяет получать впечатляющие оттенки и цвета, но его не достаток в том, что он медленнее. Так же можно использовать световые карты, которые представляют собой картинки, в которых находятся не цвета, а уровни яркости (затенение или осветление).

 

Любым из рассмотренных методов можно делать эффекты огня, дыма, облаков и воды. Так же можно сделать эффект тумана используя затенение светло серым цветом. Так же можно комбинировать эти эффекты.

 

9. Плавная загрузка новых секций карт

 

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

 

        Части Карты в Памяти.

        /-------+-------+-------\

        |       |       |       |

        |       |       |       |

        |       |       |       |

        +-------+-------+-------+

        |       | Здесь |       |

        |       | Игрок |       |

        |       |       |       |

        +-------+-------+-------+

        |       |       |       |

        |       |       |       |

        |       |       |       |

        \-------+-------+-------/

 

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

 

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

 

10. Переносимость и скорость против размера

 

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

 

При создании любого проекта, в общем, всегда хорошая идея состоит в том, чтобы сделать код насколько возможно переносимым. Обобщено это означает, что надо использовать стандартные функции и избегать использования кода специфического для данной системы или компилятора. Я столкнулся с этим недавно, купив новый 32-разрядный компилятор (раньше у меня был 16 разрядный компилятор), и мне пришлось просмотреть весь код полностью, чтобы он заработал с новым компилятором. Одна из основных проблем была в том, что число 'int' в 32-разрядных компиляторах имеет размер 32 бита, а в 16-разрядных компиляторах 16 бит. Для того чтобы решить эту проблему я перестал использовать ‘int’, а вместо этого использую ‘short’ (16 бит) и ‘long’ (32 бита), так как эти типы имеют одинаковый размер в обоих компиляторах.

 

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

 

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

 

Если бы при создании кода я потратил немного времени для обеспечения переносимости, то я бы избежал пустой траты времени позже, когда пересматривал код …

 

РАЗМЕР против СКОРОСТИ – это бесконечная борьба. И хотя компьютеры становятся все быстрее и имеют больше памяти, но часто невозможно выделить достаточно памяти, чтобы улучшить быстродействие программы, и всегда необходим некий компромисс между размером необходимой памяти и скоростью выполнения программы. Поэтому программисту всегда надо решать, чем пожертвовать или памятью, чтобы увеличить быстродействие, или быстродействием, чтобы получить дополнительную память. В этом документе описано несколько методов, которые помогут вам выбрать этот компромисс, и найти золотую середину.

 

11. Последние мысли и идеи.

 

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

 

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

 

На данный момент есть одна Shareware игра, которая использует похожий метод, но не настолько усложненный как мой. Мой проект включает в себя несколько континентов, генетические алгоритмы, три уровня, работа с палитрой и другие эффекты.

 

Надеюсь, вы получили базовые понятия для создания клеточных игр. Со мной можно связаться по адресу: gtaylor@oboe.aix.calpoly.edu

 

Приложение 1: Ограничение просмотра

 

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

 

Карта:                       Дисплей:

*************                *********    [ S – секретная дверь

*...*.......*    ===         *.......*          и комнату за ней

*...S.......*                S.......*          не видно ]

*************                *********

 

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

 

Tilefaq 1.2 Copyright 1995 Greg Taylor. All rights reserved.

PMG  18 января 2006 (c)  Сергей Анисимов