Функциональное описание программы WarEdit
Программа WarEdit исключительно проста для понимания. В основном, бопьшинство инструментов не слишком сложно создать и их описание только отнимает время. Единственная убийственно сложная вещь — это графический интерфейс Поскольку мы используем DOS, а не Windows, то должны создать все сами. Например, если мы хотим увидеть в программе кнопки, придется написать функции для их изображения и обслуживания. То же самое относится и ко всем прочим элементам интерфейса. Тут уж ничего не поделаешь, с этим нужно считаться.
Если вы собираетесь изготавливать инструменты с графическим интерфейсом, лучше начать с создания библиотеки, которой вы сможете доверять. Для этого вам придется написать тысячи строк программы.
Попробуем подойти к процессу дизайна с другой стороны. Посмотрим, что необходимо для воплощения конкретно WarEdit'a. Затем мы охватим несколько наиболее интересных функций.
WarEdit был разработан для создания игрового пространства Warlock, основываясь на тех текстурах и объектах, которые могут присутствовать в данной игре. Нам нужно иметь возможность загружать и сохранять уровни, а также просматривать предварительное изображение текстур. Я сознаю, что для большинства людей отдельные пиксели для представления блоков покажутся слишком мелкими, так как цвет одного пикселя трудно будет отличить от цвета другого, стоящего рядом. Приняв это во внимание, я решил создать окно детализации изображения, которое должно помочь различать близлежащие объекты. Нам понадобятся следующие функции:
§
Для загрузки PCX-файлов;
§ Для рисования спрайтов и масштабирования объектов;
§ Библиотека функций мыши;
§ Диалоговые окна;
§ Процедура определяющая положение курсора мыши в момент щелчка;
§ Функция для детализации изображения;
§ Некоторые функции ввода/вывода для загрузки и-сохранения данных;
§ Интерфейс пользователя.
Мы уже написали первые три функции. Остальные я собираюсь создавать от нуля или делая добавления к уже имеющейся программе.
Давайте начнем с последнего. Я догадывался, что на разработку алгоритма графического интерфейса у меня уйдет, по меньшей мере, несколько дней. Поэтому я решил просто нарисовать его в редакторе Deluxe Paint и загрузят в виде PCX-файла. Закончив изготовление интерфейса, я записал местоположение размеры всех окон и образов, чтобы впоследствии задать все интересующие области в виде констант. Самым сложным моментом был выбор цветов. Я решил пользовать в игре четыре различных типа стенок, причем так, чтобы каждый этих типов мог быть представлен шестью видами текстур. Поэтому для рисования стен можно выбрать четыре различных цвета: серый, красный, синий, зеленый, а для представления текстур подобрать оттенки этих основных цветов.
Зная, что пользователю может показаться трудным различать на экране каждый из оттенков, я решил создать окно детализации изображения. Небольшая площадь редактируемого игрового пространства вокруг курсора мыши захватывается и в увеличенном масштабе помещается в окно детализации. При этом каждый пиксель изображается квадратиком 3х3 пикселя.
При выборе цвета, представляющего собой определенную текстуру или объект, вы видите изображение выбранного объекта в окне предварительного просмотра. Это выполняется посредством взятия битовой карты текстуры или объекта и изменения ее масштаба до необходимого размера. Теперь цветом можно рисовать изображения. Однако когда вы рисуете объекты игрового пространства, в базу данных передается не само значение цвета. С использованием справочной таблицы цвет переводится в значение, распознаваемое программным кодом игры, как обычные данные текстуры или объекта. Листинг 15.1 содержит определения типов этих данных.
Листинг 15.1. Секция определений WarEdit.
#define walls START 64
#define NUM_WALLS 24
#define DOORS START 128
#define NUM_DOORS 4
#define SCROLLS_START 144
#define NUM_SCROLLS 4
#define potions_START 160
#define NUM_POTIONS 4
#define foods_start 176
#define num_foods 2
#define MONSTERS_START 192
#define num_monsters 2
#define wall_STONE_1 (WALLS_START+0) // Пока
только 6
#define WALL_STONE_2 (WALLS_START+1)
#define WALL_STONE_3 (WALLS_START+2)
#define WALL_STONE_4 (WALLS_START+3)
#define WALL_STONE_5 (WALLS_START+4)
#define WALL_STONE_6. (WALLS_START+5)
#define NUM_STONE_WALLS 6
#define WALL_MELT_1 (WALLS_START+6) // Пока только 6
#define WALL_MELT_2 (WALL3_START+7)
#define WALL_MELT_3 (WALLS_START+8)
#define WALL_MELT_4 (WALLS_START+9)
#define WALL_MELT__5 (WALLS_START+10)
#define WALL_MELT_6 (WALLS__START+11)
#define NUM_MELT_WALLS 6
#define WALL_OOZ_1 (WALLS_START+12) // Пока только 6
#define WALL_OOZ_2 (WALLS_START+13)
#define WALL_OOZ_3 (WALLS_START+14)
#define WALL_OOZ_4 (WALLS_START+15)
#define WALL_OOZ_5 (WALLS_START+16)
#define WALL_OOZ_6 (WALLS_START+17)
#define NUM_OOZ_WALLS 6
#define WALL_ICE_1 (WALLS_START+18) // Пока только 6
#define WALL_ICE_2 (WALLS_START+19)
#define WALL_ICE_3 (WALLS_START+20)
#define WALL_ICE_4 (WALLS_START+21)
#define WALL_ICE_5 (WALLS_START+22)
#define WALL_ICE_6 (WALLS_START+23)
#define NUM_ICE_WALLS б
#define DOORS_1 (DOORS_START+0) // Пока только 4
#define DOORS_2 (DOORS_START+1)
#define DOORS_3 (DOORS_START+2)
#define DOORS_4 (DOORS_START+3)
#define SCROLLS_1 (SCROLLS_START+0) // Пока только 4
#define SCROLLS_2 (SCROLLS_START+1)
#define SCROLLS_3 (SCROLLS_START+2)
#define SCROLLS_4 (SCROLLS_START+3)
#define РОТIONS_1 (POTIONS_START+0) // Пока только 4
#define POTIONS_2 (POTIONS_START+1)
#define POTIONS_3 (POTIONS_START+2)
#define POTIONS_4 (POTIONS_START+3)
#define FOODS_1 (FOODS_START+0) // Пока только 2
#define FOODS_2 (FOODS_START+1)
#define MONSTERS_1 (MONSTERS_START+0) // Пока только 2
#define MONSTERS_2 (MONSTERS_START+1)
#define GAME_START 255 // точка, из которой игрок
// начинает свой путь
Как можно видеть из Листинга 15.1, для каждой текстуры или объекта игрового пространства выделен некоторый диапазон значений. К примеру, все стены будут кодироваться числами от 64 до 127. В данный момент существует только 64 стены, но я оставил в программе место еще для 64 стен. Такой же подход применен и для определения всех остальных игровых объектов, так что структуру данных можно улучшить без особого труда. Когда вы рисуете карту игрового поля, то в действительности заполняете базу данных. Она представляет собой длинный массив, по способу доступа напоминающий двухмерную матрицу. Когда массив построен и уровень закончен, мы должны как-то сохранить данные. Для этого предусмотрены кнопки LOAD и SAVE. При нажатии одной из них, появляется диалоговое окно, запрашивающее подтверждение команды. Если вы выбрали YES, с помощью стандартной функции fopen() открывается файл, после чего данные сохраняются в файле или загружаются из файла. Но предварительно нужно ввести имя файла, используя строчный редактор. Этот редактор мне пришлось сделать самостоятельно. Он очень прост и понимает только алфавитно-цифровые клавиши.
Примечание
Мне пришлось написать такой редактор самостоятельно, поскольку единственный способ ввода строки, предлагаемый языком Си - это функция scanf (). Однако использование этой функции чревато неприятностями: при наборе возможны ошибки, текст может выйти за границы диалогового окна и т. д.
Давайте поговорим о диалоговом окне, о котором я только что упоминал. Оно представляет собой PCX-файл с двумя нарисованными кнопками.
Хотя кнопки, конечно же, являются обычными картинками, я точно знаю их расположение относительно левого верхнего угла. Все, что мне нужно сделать, это доверить, находится ли мышь над кнопкой и щелкнули ли вы мышью. Для осуществления этого теста я создал универсальную функцию, которой нужно передать следующие параметры:
§ Размер каждой кнопки;
§ Количество кнопок в столбце и строке;
§ Местоположение первой кнопки;
§ Расстояние между кнопками.
Эта функция будет определять, какая из кнопок нажата. (Вспомните, не существует легкого пути для получения подобных вещей. Мы все должны сделать собственноручно. У нас нет приличного менеджера окон и механизма посылающего нам сообщения при нажатии кнопок. Хотя вы можете попробовать написать его сами, поскольку в мои планы это не входит.) В Листинге 15.2 показана функция, которая обнаруживает нажатие кнопок.
Листинг 15.2. Обнаружение нажатия кнопки.
int Icon_Hit(int хо, int yo, int dx, int dy,
int width, int height,
int num_columns, int num_rows,
int mx, int my)
{
// получая геометрические установки кнопок, эта функция вычисляет,
// по которой из них щелкнули мышью
int row, column, xs,ys,xe,ye;
for (row=0; row<num_rows; row++)
{
// вычислить начало и конец Y-координаты колонки
ys = row*dy + yo;
ye = ys+height;
for(column=0; column<num_columns; column++)
{
xs = column*dx + xo;
xe = xs+width;
//Проверить, находится ли курсор мыши в области кнопки
if (mx>xs && mx<xe && my>ys && my<ye)
{
return(column + row*num_columns);
} // конец if
} // конец внутреннего цикла
} // конец внешнего цикла
return(-1); // не нажата
} // конец функции
Это все относительно редактора WarEdit. Я предлагаю вам поиграть с программой, чтобы получить представление о ней, прежде чем мы начнем разговор об улучшениях, которые могут быть внесены в редактор.
Наконец, для создания работающей версии WarEdit желательно использовать два объектных файла, называемых GRAPH0.OBJ и MOUSELIB.OBJ. Я предлагаю объединить их в библиотеку и затем связать с WEDIT.C. (Хотя, конечно, вы можете поступать, как вам больше нравится). Как вы понимаете, файл GRAPH0.OBJ получается после трансляции исходников GRAPH0.C и GRAPH0.H, a MOUSELIB.OBJ - из MOUSELIB.C, о котором уже упоминалось ранее.