Видеоигры приносят вам либо радость, либо огорчения. Это зависит от вашего отношения к ним. Однако программирование видеоигр не только интересно, но и полезно. Фактически, одним из лучших способов обогащения является разработка удачных видеоигр. Хорошая игра, объединяющая логику с живой графикой, доставит большое удовольствие игроку. Лучшие из видеоигр, включающие элементы искусственного интеллекта, позволяют компьютеру вести диалог с игроком и "осмысленно" реагировать на ввод данных.

В этой главе вы ознакомитесь с некоторыми основами техники программирования видеоигр, что позволит вам разрабатывать собственные игры. Вы научитесь "оживлять" различные объекты на экране вашего терминала. Разработка видеоигр явится для вас отправной точкой. Многие принципы, используемые при разработке видеоигр, будут полезны для вас и увеличат ваш интерес к работе. Для использования программ, приводимых в качестве примера в этой главе, необходим компьютер IBM PC или другой, совместимый с ним, в состав которого входят адаптеры CGA, EGA или VGA. Многие из функций, используемых в данной главе, рассматривались в главе 4. Поэтому, если вы еще не изучили главу 4, то вам придется сделать это сейчас.

Спрайты

Многие видеоигры, в которых игрок управляет объектами, атакующими другие объекты, управляемые программой или защищающимися от них, включают два класса активных объектов: среду (представляющую для нас маломеняющееся поле игры) и спрайты. СПРАЙТ - это небольшой подвижный объект, который движется по полю видеоигры по определенным правилам с заданной целью. Например, когда космический корабль стреляет фотонными торпедами, изображение торпеды реализуется спрайтом. В рамках данной главы под спрайтом будем понимать фигуру, определенную некоторыми замкнутыми отрезками (многоугольник). Хотя, в общем случае спрайт может изображаться любым образом, например, в виде окружности. В примерах, рассматриваемых в данной главе, определять спрайт будем в виде двумерного массива целых чисел. Например, спрайт, состоящий из 4 отрезков может быть описан следующим массивом

int sprite [4][4];

Первая размерность массива определяет количество отрезков спрайта, а вторая - координаты конечных точек отрезков (подобный способ описания объектов подробно рассмотрен в главе 4). Начальные и конечные координаты отрезков задаются в следующей последовательности:

start_x, start_y, end_x, end_y

Отрезок, входящий в спрайт, с координатами конечных точек 0,0 и 0,10 может быть описан следующим массивом:

sprite[0][0] = 0;   /* start_x */

sprite[0][1] = 0;   /* start_y */

sprite[0][2] = 0;   /* end_x */

sprite[0][3] = 10;  /* end_y */

Поле игры

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

Мультипликация на экране

Ключевым и наиболее впечатляющим моментом видеоигры является мультипликация. Мультипликация - основной отличительный признак видеоигр. Основной метод мультипликации прост: уничтожить изображение предмета и создать его вновь, но с некоторым небольшим смещением. Скорость этого процесса должна быть очень высокой. Это может быть обеспечено путем непосредственного доступа к видеопамяти дисплея, возможность которого описана в главе 4.

Для повышения качества изображения, быстродействия операций уничтожения и повторного изображения объекта используется операция "НЕ-ИЛИ" для двоичного кода каждой точки объекта на экране. Этот способ обеспечивает возможность быстрого перемещения спрайта по экрану, не меняя его цвет и размеры, и фактически не уничтожая в памяти терминала данные о его изображении.

Программа, отображающая на экране терминала спрайт, представляет собой некоторую модификацию функции display_object() из главы 4.

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

 

register int i;

for(i=0; i

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc | 128);

 

Как вы могли убедиться, функция display_object() рисует все линии объекта, используя приведенную в главе 4 функцию line(). Заметим, что значение номера цвета складывается по схеме "ИЛИ" с числом 128 в команде установки старших битов. Это приводит к тому, что в функции mempoint(), используемой в функции line() для помещения изображения каждой точки, выполняется сложение по схеме "НЕ-ИЛИ" двоичного кода. Это позволяет спрайту всегда оставаться видимым независимо от собственного цвета и цвета фона.

Для демонстрации мультипликации введите в ваш компьютер следующую программу. Эта программа позволит вам перемещать спрайт (в виде маленького крестика размером 6x6 точек растра) по экрану, используя клавиши управления курсором. Если ваш компьютер не включает функцию bioskey(), то просмотрите главу 1 для определения версии компилятора, которая вам необходима.

#include "dos.h"

#include "stdio.h"

void mode(), line();

void mempoint(), palette();

void display_object(),update_object();

unsigned char read_point();

int sprite[2][4] =

3,0,3,5,

0,3,5,3

;

main()

 

union k

char c[2];

int i;

 key;

int deltax=0,deltay=0;

mode(4); /*m установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

display_object(sprite,2,1);

do

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

break;

case 77: /* вправо */

deltay= 1;

break;

case 72: /* вверх */

deltax= -1;

break;

case 80: /* вниз */

deltax= 1;

break;

case 71: /* вверх и влево */

deltay= -1;

deltax= -1;

break;

case 73: /* вверх и вправо */

deltay= 1;

deltax= -1;

break;

case 79: /* вниз и влево */

deltay= -1;

deltax= 1;

break;

case 81: /* вниз и вправо */

deltay= 1;

deltax= 1;

break;

 

/* стирание текущей позиции спрайта */

display_object(sprite,2,1);

if (is_legal(sprite,deltax,deltay,2))

update_object(sprite,deltax,deltay,2);

/* перезапись спрайта в новую позицию */

displey_object(sprite2,1);

   while (key.c[0]!='q');

getchar();

mode(2);

 

/* Выбор палитры */

void palette(pnum)

int pnum;

 

union REGS r;

r.h.bh = 1; /* код 4-го графического режима */

r.h.bl = pnum;

r.h.ah = 11;

int86(0x10, &r, &r);

 

/* Выбор режима */

void mode(mode_code)

int mode_code;

 

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10, &r, &r);

 

/* Изображение линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

 

register int t,distance;

int x=0,y=0,delta_x,delta_y;

int incx,incy;

/* Вычисление расстояния в обоих направлениях                                                     */

delta_x=endx-startx;

delta_y=endy-starty;

/* определение направления шага,

шаг вычисляется либо по вертикальной, либо по горизонтальной

линии                                                     */

if (delta_x>0) incx=1;

else  if (delta_x==0) incx=0;

else  incx= -1;

if (delta_y>0) incy=1;

else  if (delta_y==0) incy=0;

else  incy= -1;

/* определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* Изображение линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

x+=delta_x;

y+=delta_y;

if (x>distance)

x-=distance;

startx+=incx;

 

if (y>distance)

y-=distance;

starty+=incy;

 

 

 

/* Запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

 

union mask

char c[2];

int i;

 bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/*  установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

if (!xor)   /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

 

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

 

 

/* чтение байта из оперативной памяти CGA/EGA */

unsigned char read_point(x,y)

int x,y;

 

union mask

char c[2];

int i;

 bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=3; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return 0;

/*  установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ bit_mask.i<<=2*(3-bit_position);

/* определение требуемого байта в памяти терминала */

index=x*40+(y>>4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

t=*(ptr+index) & bit_mask.c[0];

t>>=2*(3-bit_position);

return t;

 

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

 

register int i;

for(i=0; i

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc|128);

 

/* Смещение (параллельный перенос) объекта в направлении,

определенном x и y

*/

void update_object(ob, x, y, sides)

int ob[][4];                                  /* объект */

int x, y;                                        /* направление смещения */

register int sides; /* количество сторон объекта */

 

sides--;

for(; sides>=0; sides--)

 

ob[sides][0] += x;

ob[sides][1] += y;

ob[sides][2] += x;

ob[sides][3] += y;

 

 

/* Определение допустимости перемещения объекта.

Возвращает 1, если перемещение допустимо, 0- в противном случае

*/

void is_legal(ob, x, y, sides)

int ob[][4];                                        /* объект */

int x, y;                                              /* шаг перемещения */

int sides;                                          /* число сторон объекта */

 

if(x==0 && y==0)

return 1;                         /* пустое перемещение всегда допустимо*/

sides--;

for(; sides>=0; sides--)

 

/* контроль выхода за допустимую область */ if(ob[sides][0]+x>199 || ob[sides][1]+y>319)

return 0;

if(ob[sides][2]+x<0 || ob[sides][3]+y<0)

return 0;

 

return 1;

 

Рассмотрим кратко, как работает эта программа. Клавиши управления курсором (клавиши со стрелками и клавиши , , и ) определяют положение спрайта. При нажатии клавиши спрайт смещается на одну точку растра в указанном направлении. Клавиши-стрелки управляют горзонтальными и вертикальными перемещениями, остальные - диагональными. Функция is_legal() определяет возможность дальнейшего перемещения спрайта в выбранном направлении. Если возможен выход спрайта за пределы границ экрана, то такое перемещение запрещается. Все остальные функции этой программы работают, как описано в главе 4.

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

Мультипликация спрайта

Передвижение спрайта по экрану составляет только половину возможностей его "оживления". В основном спрайт будет использоваться на экране для того, чтобы создавать иллюзию движения. Например, спрайт, который выглядит подобно человеку, может передвигать ногами, как будто он идет. Этот тип "оживления" является наиболее впечатляющим (и наиболее легким). Для обеспечения такой возможности разрабатываются два или более вариантов спрайта, отличие между которыми заключается в том, что некоторые из частей спрайта отличаются от первоначального его варианта. Программа последовательно меняет варианты спрайта в процессе его движения по экрану.

В качестве примера изменим программу main(), как это показано ниже, и добавим в нее второй спрайт. Второй спрайт отображает крестик ("+"), повернутый под углом в 45 градусов. Если вы запустите программу, то будет создаваться впечатление, что крестик вращается в процессе передвижения по экрану. Переменная swap используется для выбора типа текущего спрайта.

int sprite2[2][4] =

0,0,5,5,

0,5,5,0

;

main()

 

union k

char c[2];

int i;

 key;

int deltax=0,deltay=0; /* направление движения */

int swap=0; /* тип спрайта */

mode(4); /* установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

display_object(sprite,2,1);

do

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

break;

case 77: /* вправо */

deltay= 1;

break;

case 72: /* вверх */

deltax= -1;

break;

case 80: /* вниз */

deltax= 1;

break;

case 71: /* вверх и влево */

deltay= -1;

deltax= -1;

break;

case 73: /* вверх и вправо */

deltay= 1;

deltax= -1;

break;

case 79: /* вниз и влево */

deltay= -1;

deltax= 1;

break;

case 81: /* вниз и вправо */

deltay= 1;

deltax= 1;

break;

 

/* стирание текущей позиции спрайта */

if(!swap) displey_object(sprite,2,1);

else displey_object(sprite2,2,1);

if (is_legal(sprite,deltax,deltay,2))

update_object(sprite,deltax,deltay,2);

update_object(sprite2,deltax,deltay,2);

 

swap= !swap; /* смена типа спрайта */

/* перезапись спрайта в новую позицию */

if (!swap) displey_object(sprite,2,1);

else displey_object(sprite2,2,1);

   while (key.c[0]!='q');

getchar();

mode(2);

Организация данных в видеоиграх

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

Контроль границ

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

Изменение цвета.

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

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

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

Табло счета активного противника

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

Разработка видеоигры

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

Описание игры

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

Cчет определяется путем фиксации игрового времени: после каждой прошедшей секунды добавляется одно очко тому, кто находится в роли догоняемого. Счет непрерывно отображается в углу экрана. Игра заканчивается, когда один из игроков набирает 999 очков. Для удобства игра может быть закончена путем нажатия клавиши .

Игрок управляет движением спрайта посредством клавиш управления курсором. Игровое поле в данном случае не создается самой программой игры. Для этих целей используются программы рисования ("программы-художники"), описанные в главе 4. Поэтому, вы можете самостоятельно создавать различную среду игры.

Использование цвета и граничные условия.

Игра "салочки" использует программирование в цвете для идентификации различных объектов. Например, вы можете сделать спрайт игрока зеленым, спрайт компьютера - желтым, а границы поля игры - красными. В соответствии с этим подходом, нет необходимости хранить отдельные массивы данных в разделяемой области программы, т.к. подпрограммы могут просто контролировать содержимое видеопамяти. В данном случае, так же, значительно упрощается процесс ограничения области движения спрайтов красной линией. Для этой цели необходимо немного изменить функцию is_legal(), описанную ранее, как это показано ниже.

/* Определение допустимости перемещения объекта.

Возвращает 1, если перемещение допустимо, 0 - в противном случае

*/

void is_legal(ob, x, y, sides)

int ob[][4];                                        /* объект */

int x, y;                                              /* шаг перемещения */

int sides;                                     /* число сторон объекта */

 

if(x==0 && y==0)

return 1;                    /* пустое перемещение всегда допустимо*/

sides--;

for(; sides>=0; sides--)

 

/* контроль выхода за допустимую область */ if(ob[sides][0]+x>199 || ob[sides][1]+y>319)

return 0;

if(ob[sides][2]+x<0 || ob[sides][3]+y<0)

return 0;

/* контроль препятствий */

if(read_point(ob[sides][0]+x, ob[sides][1]+y)==2)

return 0;

if(read_point(ob[sides][2]+x, ob[sides][3]+y)==2)

return 0;

 

return 1;

 

Напомним коды различных цветов: желтый - 1, красный - 2, зеленый - 3, черный (фон) - 0.

Описание спрайта.

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

_________________________________________________________________

Вид спрайта на стр. 187 не может быть воспроизведен имеющимися средствами. (Ред. пер. И.Бычковский.)

_________________________________________________________________

В программе существует два типа спрайта. Во втором спрайте ноги "человека" сжаты вместе. Быстрая смена между изображениями двух типов спрайта создает иллюзию бегущего человека.

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

int human[4][4] = /* это ваш спрайт */

 

1,     6,     6,     6,

4,     2,     3,     9,

9,     1,     6,     6,

9,     11,    6,     6

;

int human2[4][4] =

 

1,     6,     6,     6,

4,     2,     3,     9,

9,     3,     6,     6,

9,     9,     6,     6

;

int computer[4][4] = /* это  спрайт компьютера */

 

180,     6,     185,     6,

183,     2,     182,     9,

188,     1,     185,     6,

188,     11,    185,     6

;

int computer2[4][4] =

 

180,     6,     185,     6,

183,     2,     182,     9,

188,     3,     185,     6,

188,     9,     185,     6

;

Тело главной программы

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

int directx,directy; /* направление  */

main()

 

union k

char c[2];

int i;

 key;

int deltax=0,deltay=0;

int swaph=0,swapc=0;

int it=COMPUTER;

long htime,ctime,starttime,curtime; /* таймер счета */

int count;

mode(4); /* установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

load_pic(); /* ввод игрового поля */

time(&starttime); /* установка времени */

htime=ctime=0;

display_object(human,4,1);

display_object(computer,4,3);

count=0;

/* главный цикл игры */

do

/* вычисление текущего счета  */

time(&curtime);

if (it==COMPUTER) htime+=curtime-starttime;

else ctime+=curtime-starttime;

time(&starttime);

show_score(it,htime,ctime);

if (bioskey(1))  /* если нажата клавиша */

directx=directy=IDLE; /* устанавливает

направление перемещения */

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

directy=LEFT;

break;

case 77: /* вправо */

deltay=1;

directy=RIGHT;

break;

case 72: /* вверх */

deltax= -1;

directx=UP;

deltax= -1;

directx=UP;

break;

case 80: /* вниз */

deltax=1;

directx=DOWN;

break;

case 71: /* вверх и влево */

deltay= -1;

directy=LEFT;

deltax= -1;

directx=UP;

break;

case 73: /* вверх и вправо */

deltay=1;

directy=RIGHT;

deltax= -1;

directx=UP;

break;

case 79: /* вниз и влево */

deltay= -1;

directy=LEFT;

deltax=1;

directx=DOWN;

break;

case 81: /* вниз и вправо */

deltay=1;

directy=RIGHT;

deltax=1;

directx=DOWN;

break;

 

 

/* смена типа спрайта игрока  */

if (!swaph) display_object(human,4,1);

else display_object(human2,4,1);

if (is_legal(human,deltax,deltay,4))

update_object(human,deltax,deltay,4);

update_object(human2,deltax,deltay,4);

 

/* проверяет: попался ли убегающий */

if (!count && tag(human,computer)) 

it= !it; /* смена амплуа */

count=6;

 

swaph= !swaph; /* смена фигуры имитирующей бег */

/* изображение "человека" в новой позиции */

if (!swaph) displey_object(human,4,1);

else displey_object(human2,4,1);

if (!swapc) display_object(computer,4,3);

else display_object(computer2,4,3);

/* генерация движения спрайта компьютера */

if (it==COMPUTER)

it_comp_move(computer,computer2,human,4);

else not_it_comp_move(computer,computer2,directx,directy,4);

if (!count && tag(human,computer))

it= !it;

count=6;

/* компьютер догоняет; изменение координаты Х на 2

так, чтобы быстрей стать догоняемым

*/

if (is_legal(computer, 2, 0, 4))

 

update_object(computer, 2, 0, 4); update_object(computer2, 2, 0, 4);

 

else

 

update_object(computer, -2, 0, 4);

update_object(computer2, -2, 0, 4);

 

 

swapc = !swapc; /* заменить тип спрайта */

/* вывод на экран спрайта компьютера */

if(!swapc) display_object(computer, 4, 3);

else                      display_object(computer2, 4, 3);

if(count) count--;

 

while (key.c[0] !='q' && htime<999 && ctime<999);

mode(2);

if(ctime>htime)

printf("Компьютер выиграл!");

else

printf("Вы победили!");

 

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

Переменная htime содержит значение счета игрока, а ctime - компьютера. Переменные swapc и swaph предназначены для указания типа спрайта. Переменные deltax и deltay содержат изменения значений координат после очередного нажатия клавиш игроком. Глобальные переменные directx и directy содержат координаты спрайта, управляемого игроком. Значения этих величин используются компьютером для генерации перемещения своего спрайта. Переменная­признак it содержит информацию о том, кто в данный момент находится в режиме догоняющего. Она может принимать одно из двух значений, описанных в макроопределении #define: COMPUTER или HUMAN.

Главная   программа   работает                                циклически.                 На   экране

отображается  текущий  счет.  После  этого  проверяется,  была ли

нажата  какая-либо  клавиша.   Если   клавиша   была  нажата,  то

определяется ее  код и производится  заданное перемещение спрайта

игрока.  Обратите  внимание  на  то,  что в  данной программе нет

режима ожидания  нажатия клавиши игроком.  Поэтому,  не смотря на

то,  что игрок  не  нажимает  клавиш,  компьютер  продолжает свою

работу,  и спрайт игрока продолжает указанное  перемещение до тех

пор,  пока не будет нажата другая клавиша. Такое движение спрайта

обеспечивает достаточно высокую динамичность игры.

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

Рассмотрим некоторые программы, используемые в этой игре.

Программа генерации движения спрайта компьютера.

Если спрайт компьютера находится в режиме догоняющего, то для генерации очередного его кванта движения используется функция it_comp_move(). В основном компьютер повторяет стратегию движения пользователя. Движение его спрайта отклоняется из-за того, что он должен обходить объекты-препятствия. Однако спрайт компьютера может игнорировать некоторые объекты, что позволяет выровнять баланс игры.

Приведем текст функции it_comp_move().

/* Генерация движения спрайта компьютера, когда

он в роли догоняющего */

void it_comp_move(ob1, ob2, human, sides)

int ob1[][4], ob2[][4], human[][4], sides;

 

register int x, y, d; /* d = direction */

static skip = 0;

skip++;

if(skip==3)

 

skip=0;

return;

/* уменьшение времени реакции компьютера */

 

x = 0;

y = 0;

/* движение к игроку */

if(human[0][0]

x = -1;

else

if(human[0][0]>ob1[0][0])

x = 1;

if(human[0][1]

y = -1;

else

if(human[0][1]>ob1[0][1])

y = 1;

if(is_legal(ob1, x, y, sides))

 

update_object(ob1, x, y, sides);

update_object(ob2, x, y, sides);

 

else

 

if(x && is_legal(ob1, x, 0, sides))

 

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

 

else

if(is_legal(ob1, 0, y, sides))

 

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

 

 

 

Заметим, что эта функция меняет положение спрайта в 3 раза медленнее, чем это возможно. Делается такое замедление с целью снижения быстродействия компьютера до уровня человека.

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

/* Генерация движения спрайта компьютера, когда

он выступает в роли убегающего */

void it_comp_move(ob1, ob2, human, sides)

int ob1[][4], ob2[][4], human[][4], sides;

 

register int x, y, d; /* d = direction */

static skip = 0;

skip++;

if(skip==3)

 

skip=0;

return;

/* уменьшение времени реакции компьютера */

 

x = 0;

y = 0;

/* движение к игроку */

if(human[0][0]

x = -1;

else

if(human[0][0]>ob1[0][0])

x = 1;

if(human[0][1]

y = -1;

else

if(human[0][1]>ob1[0][1])

y = 1;

if(is_legal(ob1, x, y, sides))

 

update_object(ob1, x, y, sides);

update_object(ob2, x, y, sides);

else

 

if(x && is_legal(ob1, x, 0, sides))

 

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

 

else

if(is_legal(ob1, 0, y, sides))

 

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

 

 

 

/* генерация движения спрайта компьютера, когда

он убегает                    */

void not_it_comp_move(ob1, ob2, dx, dy, sides)

int ob1[][4], ob2[][4];

int dx, dy; /* направление последнего перемещения

"человека" */

int sides;

 

register int x, y, d;

static skip = 1;

skip++;

if (skip==3)

 

skip = 0;

return;

/* уменьшение времени реакции компьютера в 3 раза */

 

x = 0;

y = 0;

/* перемещение в противоположном направлении */

x = -dx;

y = -dy;

if (is_legal(ob1, x, y, sides))

 

updаte_object(ob1, x, y, sides);

updаte_object(ob2, x, y, sides);

 

else

 

if (x && is_legal(ob1, x, 0, sides))

 

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

 

else if (is_legal(ob1, 0, y, sides)) 

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

 

 

 

Эта функция так же как и предыдущая, работает с 3-кратным замедлением.

Программа контроля касания спрайтов.

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

/* Проверяет есть ли контакт между спрайтами */

tag(ob1, ob2)

int ob1[][4], ob2[][4];

 

register int i;

/* для смены амплуа необходимо, чтобы спрайты

имели хотя бы одну общую точку растра */

for (i= -1; i<2; i++)

if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)

return 1;

return 0;

 

Вы можете внести изменения в функцию tag() и установить свои правила контроля режимов спрайтов.

Полный текст программы игры TAG.

В данном разделе приведен текст программы игры TAG, похожей на русские "салочки". Вы можете ввести ее в свой компьютер, если он снабжен графическим адаптером.

/* Пример мультипликации игры "салочки"

Объектом в игре является "человек", который догоняет другого "человека".

Ваш "человек"- зеленый,"человек" компьютера­желтый. Все, что окрашено в красный цвет, пересекать нельзя.

Для смены ролей догоняющего и догоняемого необходимо, чтобы "люди" пересеклись хотя бы в одной точке растра  */

#define COMPUTER 0

#define HUMAN 1

#define IDLE 0

#define DOWN 1

#define UP -1

#define LEFT -1

#define RIGHT 1

#include "dos.h"

#include "stdio.h"

#include "math.h"

#include "time.h"

void mode(), line();

void mempoint(), palette(), xhairs();

void goto_xy(),show_score();

void display_object(),update_object();

void it_comp_move(),not_it_comp_move(); void save_pic(), load_pic(); unsigned char read_point();

int human[4][4] =  /* ваш спрайт */ 1,6,6,6,

4,2,3,9,

9,1,6,6,

9,11,6,6

;

int human2[4][4] =

1,6,6,6,

4,2,3,9,

9,3,6,6,

9,9,6,6

;

int computer[4][4] =  /* спрайт компьютера */

180,6,185,6,

183,2,182,9,

188,1,185,6,

188,11,185,6

;

int computer2[4][4] =

180,6,185,6,

183,2,182,9,

188,3,185,6,

188,9,185,6

;

int directx,directy; /* направление  */

main()

 

union k

char c[2];

int i;

 key;

int deltax=0,deltay=0;

int swaph=0,swapc=0;

int it=COMPUTER;

long htime,ctime,starttime,curtime;

int count;

mode(4); /* установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

load_pic(); /* ввод игрового поля */

time(&starttime); /* установка времени */

htime=ctime=0;

display_object(human,4,1);

display_object(computer,4,3);

count=0;

/* главный цикл игры */

do

/* вычисление текущего счета  */

time(&curtime);

if (it==COMPUTER) htime+=curtime-starttime;

else ctime+=curtime-starttime;

time(&starttime);

show_score(it,htime,ctime);

if (bioskey(1))  /* если нажата клавиша */

directx=directy=IDLE; /* устанавливает

направление перемещения */

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

directy=LEFT;

break;

case 77: /* вправо */

deltay=1;

directy=RIGHT;

break;

case 72: /* вверх */

deltax= -1;

directx=UP;

deltax= -1;

directx=UP;

break;

case 80: /* вниз */

deltax=1;

directx=DOWN;

break;

case 71: /* вверх и влево */

deltay= -1;

directy=LEFT;

deltax= -1;

directx=UP;

break;

case 73: /* вверх и вправо */

deltay=1;

directy=RIGHT;

deltax=-1;

directx=UP;

break;

case 79: /* вниз и влево */

deltay= -1;

directy=LEFT;

deltax=1;

directx=DOWN;

break;

case 81: /* вниз и вправо */

deltay=1;

directy=RIGHT;

deltax=1;

directx=DOWN;

break;

 

 

/* смена типа спрайта игрока  */

if (!swaph) displаy_object(human,4,1);

else displey_object(human2,4,1);

if (is_legal(human,deltax,deltay,4))

update_object(human,deltax,deltay,4);

update_object(human2,deltax,deltay,4);

 

/* проверяет: попался ли убегающий */

if (!count && tag(human,computer))

it=!it; /* смена амплуа */

count=6;

 

swaph= !swaph; /* смена фигур, имитирующих бег */

/* вывод "человека" в новой позиции */

if (!swaph) displаy_object(human,4,1);

else displаy_object(human2,4,1);

if (!swapc) displаy_object(computer,4,3);

else displаy_object(computer2,4,3);

/* генерация движения спрайта компютера */

if (it==COMPUTER)

it_comp_move(computer,computer2,human,4);

else not_it_comp_move(computer,computer2,directx,directy,4);

if (!count && tag(human,computer))

it= !it;

count=6;

/* компьютер догоняет; изменение координаты Х на 2

так, чтобы быстрей стать догоняемым */

if(is_legal(computer, 2, 0, 4))

 

update_object(computer, 2, 0, 4); update_object(computer2, 2, 0, 4);

 

else

 

update_object(computer, -2, 0, 4);

update_object(computer2, -2, 0, 4);

 

 

swapc = !swapc; /* заменить тип спрайта */

/* вывод на экран спрайта компьютера */

if(!swapc) display_object(computer, 4, 3);

else                      display_object(computer2, 4, 3);

if(count) count--;

 

while (key.c[0] !='q' && htime<999 && ctime<999);

getchar();

mode(2);

if(ctime>htime)

printf("Компьютер выиграл!");

else

printf("Вы победили!");

 

/* Вывод на экран терминала счета */

void shou_score(it, htime, ctime)

int it;

long htime, ctime;

 

goto_xy(24, 6);

if(it==COMPUTER)

printf("ВЫ:%ld", htime);

else

printf("вы:%ld", htime);

goto_xy(24, 26);

if(it==HUMAN)

printf("Я:%ld", ctime);

else

printf("я:%ld", ctime);

 

/* Выбор палитры */

void palette(pnum)

int pnum;

 

union REGS r;

r.h.bh = 1; /* код 4-го графического режима */

r.h.bl = pnum;

r.h.ah = 11;

int86(0x10, &r, &r);

 

/* Выбор режима */

void mode(mode_code)

int mode_code;

 

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10, &r, &r);

 

/* изображение линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

 

register int t,distance;

int x=0,y=0,delta_x,delta_y;

int incx,incy;

/* вычисление расстояния в обоих направлениях                                                      */

delta_x=endx-startx;

delta_y=endy-starty;

/* определение  направления  шага,  шаг  вычисляется  либо   по

вертикальной,  либо  горизонтальной  линии */

if (delta_x>0) incx=1;

else if (delta_x==0) incx=0; else incx=-1;

if (delta_y>0) incy=1;

else  if (delta_y==0) incy=0;

else  incy=-1;

/* определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* изображение линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

x+=delta_x;

y+=delta_y;

if (x>distance)

x-=distance;

startx+=incx;

 

if (y>distance)

y-=distance;

starty+=incy;

 

 

 

/* запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

 

union mask

char c[2];

int i;

 bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/*  установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

if (!xor)   /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

 

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

 

 

/* чтение байта из оперативной памяти CGA/EGA */

unsigned char read_point(x,y)

int x,y;

 

union mask

char c[2];

int i;

 bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=3; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return 0;

/*  установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ bit_mask.i<<=2*(3-bit_position);

/* определение требуемого байта в памяти терминала */

index=x*40+(y>>4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

t=*(ptr+index) & bit_mask.c[0];

t>>=2*(3-bit_position);

return t;

 

/* загрузка изображения */

void load_pic()

 

char fname[80];

FILE *fp; register int i,j;

char far *ptr=(char far *) 0xB8000000; /* точка в памяти   CGA  */

char far *temp;

unsigned char buf[14][80]; /* содержит образ экрана */

temp=ptr;

/* сохранение верхних строк текущего содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;j+=2)

buf[i][j]=*temp;

buf[i][j+1]=*(temp+8152);

*temp=0; *(temp+8152)=0;/*чистка позиций экрана*/

temp++;

 

goto_xy(0,0);

printf("Имя файла:");

gets(fname);

if (!(fp=fopen(fname,"rb")))

goto_xy(0,0);

printf("Файл не может быть открыт ");

temp=ptr;

/* восстановление содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;j+=2)

*temp= buf[i][j];

*(temp+8125)=buf[i][j+1];

temp++;

 

return;

 

/* загрузка изображения из файла */

for (i=0;i<8152;i++)

*ptr=getc(fp); /* четный байт */

*(ptr+8125)=getc(fp); /* нечетный байт */

ptr++;

 

fclose(fp);

 

/* поместить курсор в заданное положение */

void goto_xy(x,y)

int x,y;

 

r.h.ah=2; /* адресация курсора */

r.h.dl=y; /* координата столбца */

r.h.dh=x; /* координата строки */

r.h.bh=0; /* видео-страница */

int86(0x10,&r,&r);

 

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

 

register int i;

for(i=0; i

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc|128);

 

/* Смещение (параллельный перенос) объекта в направлении,

определенном x и y

*/

void update_object(ob, x, y, sides)

int ob[][4];                                  /* объект */

int x, y;                                        /* направление смещения */

register int sides; /* количество сторон объекта */

 

sides--;

for(; sides>=0; sides--)

 

ob[sides][0] += x;

ob[sides][1] += y;

ob[sides][2] += x;

ob[sides][3] += y;

 

 

/* Определение  допустимости перемещения объекта. Возвращает

1, если перемещение допустимо, 0 - в противном случае */ is_legal(ob, x, y, sides)

int ob[][4];                                  /* объект */

int x, y;                                        /* шаг перемещения */

int sides;                                     /* число сторон объекта */

 

if(x==0 && y==0)

return 1;                            /* пустое перемещение всегда допустимо*/

sides--;

for(; sides>=0; sides--)

 

/* контроль выхода за допустимую область */

if(ob[sides][0]+x>199 || ob[sides][1]+y>319)

return 0;

if(ob[sides][2]+x<0 || ob[sides][3]+y<0)

return 0;

if(read_point(ob[sides][0]+x, ob[sides][1]+y)==2)

return 0;

if(read_point(ob[sides][2]+x, ob[sides][3]+y)==2)

return 0;

 

return 1;

 

/* генерация движения спрайта компьютера, когда он догоняет */

void it_comp_move(ob1,  ob2,  human, sides)

int ob1[][4],ob2[][4], human[][4], sides;

 

register int x, y, d; /* d = direction */

static skip = 0;

skip++;

if(skip==3)

 

skip=0;

return;

/* уменьшение времени реакции компютера */

 

x = 0;

y = 0;

/* движение к игроку */

if(human[0][0]

x = -1;

else

if(human[0][0]>ob1[0][0])

x = 1;

if(human[0][1]

y = -1;

else

if(human[0][1]>ob1[0][1])

y = 1;

if(is_legal(ob1, x, y, sides))

 

update_object(ob1, x, y, sides);

update_object(ob2, x, y, sides);

 

else

 

if(x && is_legal(ob1, x, 0, sides))

 

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

 

else

if(is_legal(ob1, 0, y, sides))

 

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

 

 

 

/* генерация движения спрайта компьютера, когда

он убегает                    */

void not_it_comp_move(ob1, ob2, dx, dy, sides)

int ob1[][4], ob2[][4];

int dx, dy; /* направление последнего перемещения

"человека" */

int sides;

 

register int x, y, d;

static skip = 1;

skip++;

if (skip==3)

 

skip = 0;

return;

/* уменьшение времени реакции компьютера в 3 раза */

 

x = 0;

y = 0;

/* перемещение в противоположном направлении */

x = -dx;

y = -dy;

if (is_legal(ob1, x, y, sides))

 

updаte_object(ob1, x, y, sides);

updаte_object(ob2, x, y, sides);

 

else

 

if (x && is_legal(ob1, x, 0, sides))

 

updаte_object(ob1, x, 0, sides);

updаte_object(ob2, x, 0, sides);

 

else if (is_legal(ob1, 0, y, sides))  updаte_object(ob1, 0, y, sides); updаte_object(ob2, 0, y, sides);

 

 

/*  проверяет наличие контакта между спрайтами */

tag(ob1, ob2)

int ob1[][4], ob2[][4];

 

register int i;

/* для смены амплуа необходимо, чтобы спрайты

имели хотя бы одну общую точку растра */

for (i=-1; i<2; i++)

if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)

return 1;

return 0;

 

Для использования игры вы должны создать одно или несколько игровых полей, используя функции, описанные в главе 4. Используйте красный цвет для изображения препятствий. Желтый и зеленый цвета можно использовать для фона. Эти цвета не несут нагрузки, поэтому могут использоваться в декоративных целях. На рисунках 5-1 и 5-2 показаны два варианта игровых полей в таком виде, в котором они отображаются на экране вашего терминала.

Быстродействие компьютеров, таких моделей как AT или PS/2 моделей 50, 60 или 80, вполне достаточно для данной игры. Темп игры будет несколько снижен на обычном компьютере PC. Однако вам уже известно, как может быть повышена динамичность игры.

_________________________________________________________________

Рис. 5-1 на стр. 205 имеющимися средствами воспроизведен быть не может. (Ред. пер. И.Бычковский.)

_________________________________________________________________

Рис. 5-1. Первое игровое поле видеоигры "салочки"

_________________________________________________________________

Рис. 5-2 на стр. 205 имеющимися средствами воспроизведен быть не может. (Ред. пер. И.Бычковский.)

_________________________________________________________________

Рис. 5-2. Второе игровое поле видеоигры "салочки" -->

Некоторые соображения по возможной модификации программы

Возможно, вы со временем захотите создать свою видеоигру, взяв, однако, за основу рассмотренную здесь игру TAG. В этом случае в можете, например, изменить траекторию движения спрайта компьютера заставив его двигаться вокруг какого-то объекта ("охранять" его). Интересным дополнением к программе будет возможность изменять внешний вид каждого объекта-участника игры в зависимости от каких-либо условий. Кстати, решение этой задачи не требует от вас каких-либо дополнительных усилий, так как каждое изображение спрайта можно хранить в видеопамяти, а все необходимые подпрограммы для работы с ней у вас уже есть.

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

Попробуйте добавить в программу еще один спрайт, касание которого будет приносить дополнительные очки играющим.

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