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

Вид материалаДокументы

Содержание


Гаснущее изображение
Исчезновение Изображения
Оплывание изображения
Демонстрация смены экрана
Подобный материал:
1   ...   12   13   14   15   16   17   18   19   ...   37
^

Гаснущее изображение


Этот эффект уже много лет пользуется популярностью у программистов, потому что получить его фантастически легко — для этого требуется всего несколько строк кода.

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

Поэтому программа, приведенная в Листинге 7.9 просто уменьшает каждый компонент каждого цвета в пяти регистрах до тех пор, пока все три компонента — красный, зеленый и синий — не становятся равными нулю. На этом функция завершается.

^ Исчезновение Изображения

Этого замечательного эффекта можно достичь вообще одной строчкой. Я вам продемонстрирую ее через минуту.

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

Здесь приведены две самые короткие строчки кода, которые мне удалось придумать:

for (index=0; index<=300000; index++)

Plot_Pixel_Fast(rand()%320, rand()%200, 0);

Этот код просто размещает 300000 черных пикселей по всему экрану. Почти наверняка такое большое количество пикселей разрушит все изображение на экране. (Мы могли бы рассчитать точное количество итераций, но это заняло бы лишнее время. Число 300000 выглядит вполне приемлемым.)

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

^ Оплывание изображения

Этот эффект появился в игре DOOM и быстро распространился по другим играм. Мой вариант не столь впечатляющий. Однако, этого и следовало ожидать, ведь я потратил на него всего 15 минут.

Таяние изображения осуществляется благодаря движению сверху вниз с разной скоростью 160 маленьких «червячков» под влиянием силы тяжести. И .Когда "червячки" движутся вниз, они поедают пиксели. Через некоторое время большинство из них достигает низа и растворение заканчивается. (Я подозреваю, Что и в DOOM использовалась подобная техника. Таким образом, каждая вертикальная лента масштабируется вместо простого стирания пикселей.)

^ Демонстрация смены экрана

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

Листинг 7.9. Эффекты экрана (SCREENFX.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ////////////////////////////////////////

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "graph0.h" // включаем нашу графическую библиотеку

// СТРУКТУРА.///////////////////////////////////////////////

typedef struct worm_typ

{

int у; // текущая Y-координата "червячка"

int color; // цвет "червячка"

int speed; // скорость "червячка"

int counter; // счетчик

}, worm, *worm_ptr;

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ///////////////////////////////////

unsigned int far *clock = (unsigned int far *)0x0000046C;

// указатель на внутренний таймер 18.2 "тик"/с

pcx_picture screen_fx; // наш тестовый экран

worm worms[320]; // используется при оплывании экрана

//ФУНКЦИИ /////////////////////////////////////////////////


void Timer(int clicks)

{

// эта функция использует внутренний таймер с частотой 18.2 "тик"/с

// 32-битовое значение этого таймера имеет адрес 0000:046Ch

unsigned int now;

// получим текущее время

now = *clock;

// Ожидаем до истечения указанного периода времени.

// Заметьте, что каждый "тик" имеет длительность примерно в 55 мс

while(abs(*clock - now) < clicks){}

} // конец Timer ////////////////////////////////////////////////////////////

void Fade_Lights (void)

{ // эта функция гасит свет, медленно уменьшая значения цветов

// во всех цветовых регистрах

int index,pal_reg;

RGB_color color,color_1,color_2,color_3;

for (index=0; index<30; index++)

{

for (pal_reg=l; pal_reg<255; pal_reg++)

{

// получить затемняемый цвет

Get_Palette_Register(pal_reg,(RGB_color_ptr)&color) ;

if (color.red > 5) color.red-=3;

else

color.red = 0;

if (color.green > 5) color.green-=3;

else

color.green = 0;

if (color.blue > 5) color.blue-=3;

else

color.blue = 0;

// уменьшить интенсивность цвета

Set_Palette_Register(pal_reg,(RGB_color_ptr)&color) ;

} // конец внутреннего цикла

// немного подождем

Timer(2);

} // конец внешнего цикла } // конец Fade_Lights

///////////////////////////////////////////////////////////

void Disolve(void)

{

// "растворение" экрана рисованием биллиона черных пикселей

unsigned long index;

for (index=0; index<=300000; index++, Plot_Pixel_Fast(rand()%320, rand()%200, 0));

} // конец Disolve

//////////////////////////////////////////////////////////

void Melt(void)

{

// Функция "оплавляет" экран, двигая маленьких "червячков"

// с разной скоростью вниз по экрану. Эти "червячки" меняют

// на своем пути цвет пикселей.

int index,ticks=0;

// инициализация "червячков"

for (index=0; index<160; index++)

{

worms[index].color = Get_Pixel(index,0);

worms[index].speed = 3 + rand()%9;

worms[index].у =0;

worms[index].counter = 0;

// прорисовка "червячка"

Plot Pixel_Fast((index<<1), 0, (char) worms [index].color);

Plot_Pixel_Fast((index<<1), 1, (char) worms [index].color);

Plot_Pixel_Fast((index<<1), 2, (char) worms [index].color) ;

Plot_Pixel_Fast((index<<1) + l,0, (char) worms [index].color) ;

Plot_Pixel_Fast((index<<1) + 1,1, (char) worms [index].color) ;

Plot_Pixel_Fast((index<<1) + 1,2, (char) worms [index].color);

} // конец цикла

// плавим экран

while(++ticks<1800)

{

// работа "червячков"

for (index=0; index<320; index++)

{

// пора подвинуть "червячка"

if (++worms[index].counter == worms[index].speed)

{

// обнуляем счетчик

worms[index].counter = 0;

worms[index].color = Get_Pixel(index,worms[index],y+4);

// достиг "червячок" низа экрана?

if (worms[index].у < 193)

{ Plot_Pixel_Fast ((index<<1), worms [index] .y, 0) ;

Plot Pixel Fast ((index<<3.),worms [index] .y+1,

(char)worms[index].color);

Plot_Pixel_Fast ( (index<<1) ,worms [index] .y+2,

(char)worms[index].color);

Plot Pixel Fast ((index<<1),worms [index] .y+3,

(char)worms[index].color) ;

Plot Pixel Fast ( (index<<1) +1,worms [index] .y, 0) ;

Plot_Pixel_Fast( (index<<1)+1,worms [index] .y+1,

(char)worms[index].color);

Plot Pixel Fast ( (index<<1)+l,worms [index] .y+2,

(char)worms[index].color);

Plot_Pixel_Fast ( (index<<1)+1,worms [index] .y+3,

(char)worms[index].color);

worms[index].y++;

} // конец оператора if

} // конец оператора if

} // конец цикла // ускоряем плавление

if (!(ticks % 500))

{

for (index=0; index<160; index++) worms[index].speed--;

} // конец оператора if

} // конец оператора while

} // конец Melt

// ОСНОВНАЯ ПРОГРАММА //////////////////////////////////////

void main(void)

(

int index,

done=0,

sel;

// установка видеорежима 320х200х256

Set_Mode(VGA256);

PCX_lnit((pcx_picture_ptr)&screen_fx) ;

PCX_Load("war.pcx", (pcx_picture_ptr)&screen_fx,1);

PCX_Show_Buffer((pcx_picture_ptr) &screen_fx);

PCX_Delete((pcx_picture_ptr)&screen_fx);

_settextposition(22,0);

printf('1 - Fade Lights.\n2 - Disolve.\n3 - Meltdown.");

// какой эффект хочет увидеть игрок? switch(getch())

{

case '1': // гаснущий экран {

Fade_Lights();

} break;

case '2': // растворяющийся экран {

Disolve();

} break;

case '3': // оплывающий экран {

Melt(};

} break;

} //конец оператора switch

// возврат в текстовый режим

Set_Mode(TEXT_MODE) ;

} // конец функции main

Пробовали ли вы текстурировать?

Так как основная тема всех наших разговоров — это то, как сделать трехмерную игру типа DOOM с текстурированными поверхностями, то мне следует рассказать вам о несомненно важной вещи, которую мы еще не обсуждали — об этом самом тексту рировании. Все эти темные комнаты и туннели просто обязаны иметь действительно потрясающую текстуру. Здесь есть только одна проблема, а что если вы не художник-профессионал? Как же вы их сделаете?

Вы будете приятно удивлены, узнав что достаточно внимательно посмотреть на всевозможные здания и сооружения, а затем посвятить некоторое время программе Deluxe Paint. Взгляните на рис. 7.8.



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

(Однако, пожалуйста, не размещайте водопроводные краны и ванные на стенах, как это сделано в некоторых играх, названия которых вы сами легко вспомните.)

Масштабирование растровых изображений

Люди обычно говорят: «А вот это мы оставим на сладкое...». В нашем случае мы оставили на сладкое самое сложное, что есть в этой главе. Я пытался свести всю математическую часть к минимуму, зная «любовь» программистов к тригонометрическим функциям и умножению. К сожалению, трехмерные игры содержат довольно сложные алгоритмы и математику. Масштабирование битовых образов - это именно одна из таких областей.

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

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

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



Если вы посмотрите внимательно, то увидите, что на рисунке 7.10 ровно в два раза больше пикселей, чем на рисунке 7.9.

Удвоение количества пикселей будет работать, если мы хотим увеличить, образ точнс? в два раза. Однако, если же мы захотим увеличить образ на произвольный коэффициент (как мы будем делать позже при создании нашей трехмерной игры), то нам потребуется более общий алгоритм. Я расскажу вам о восхитительно простом способе масштабирования растрового изображения. Он настолько прост, что просто кажется невозможным!

Давайте подойдем к решению данной проблемы немного с другой стороны. На время забудем об умножении на 0.5 или 2, а подумаем о растягивании и сжатии исходного образа. Другими словами, мы хотим взять исходное количество пикселей и растянуть или сжать их до требуемого количества пикселей. Рисунок 7.11 показывает графическое представление этой идеи.



Например, если мой исходный образ состоит из строк по 64 пикселя, и я хочу превратить его в образ со строкой по 100 пикселей, то я должен увеличить количество исходных пикселей как 100/64, то есть примерно в 1.5 раза.

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

Вернемся к нашему примеру. Мы хотим в результате масштабирования получить 100 пикселей, которые бы выглядели как исходные 64. Как мы уже посчитали, коэффициент масштабирования должен быть 1.5, следовательно, каждый пиксель должен быть скопирован 1.5 раза. Однако мы не можем этого сделать, так как скопировать только половину пикселя при всем желании не удастся. Испробуем наш альтернативный вариант, и сделаем примерно так:
  • Проиндексируем область значений исходного изображения от 0 до 63;
  • Проиндексируем область значений увеличенного объекта также от 0 до 63, но не целыми, а дробными значениями.

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

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

1/коэффициент масштабирования


И все! Конечно, мы должны изменять масштаб и по координате X, и по координате Y, но алгоритм можно использовать один и тот же. Более того, если объект имеет одинаковые размеры по координатам Х и Y (то есть если он имеет размеры МхМ, а не MxN), то расчеты могут быть выполнены еще быстрее.

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

Чтобы показать работу функции масштабирования объектов я написал демонстрационную программу, которая называется SCALE.С. В ней вы можете выбрать одну из четырех текстур и масштабировать ее, используя клавишу левой угловой скобки (или знака «меньше») для уменьшения и клавишу правой угловой скобки (или знака «больше») — для увеличения объекта. Обратите внимание, что масштабирование действительно замедляется при увеличении объекта, но надо помнить, что это только начало, и нам было важнее всего понять, как все это работает. Листинг 7.10 содержит текст программы. Не забудьте при компоновке программы подключить библиотеку GRAPH0.C.


Листинг 7.10. Программа, масштабирующая текстуры стен (SCALE.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ////////////////////////////////////////

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "graph0.h" // включаем нашу графическую библиотеку

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ //////////////////////////////////

sprite object;

pcx_picture text_cells;

// ФУНКЦИИ ///////////////////////////////////////////////

void Scale_Sprite(sprite_ptr sprite,float scale)

{

// эта функция масштабирует спрайт, рассчитывая количество

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

// требуемого размера

char far *work_sprite;

int work offset=0, offset,x,у;

unsigned char data;

float y_scale_index,x_scale_step,y_scale_step,x_scale_index;

// берем первый пиксель исходного изображения

y_scale_index = 0;

// рассчитываем дробный шаг

y_scale_step = sprite height/scale;

x_scale_step = sprite_width/scale;

// для простоты даем указателю иа спрайт новое имя

work_sprite = sprite->frames [sprite->curr__frame];

// расчет смещения спрайта в видеобуфере

offset = (sprite->y << 8) + (sprite->y << 6) + sprite->x;

// построчно масштабируем спрайт

for (y=0; y<(int) (scale); у++)

{

// копируем следующую строчку в буфер экрана

х_scale_index=0;

for (х=0; x<(int)scale; х++)

{

// проверка на прозрачность пикселя

// (то есть равен ли он 0), если нет - прорисовываем пиксель

if ((data=work_sprite[work_offset+(int)x_scale_index]))

double_buffer[offset+x] = data;

x_scale_index+=(x_scale_step);

} // конец внутреннего цикла (по X)

// используя дробный шаг приращения, определяем следующий

// пиксель исходного изображения

у_scale_index+=y_scale_step;

// переходим к следующей строке видеобуфера

//и растрового буфера спрайта

offset += SCREEN_WIDTH;

work_offset = sprite_width*(int)(y_scale_index);

} // конец внешнего цикла (по У)

} // end Scale_Sprite /////////////////////////////////////////

void Clear_Double_Buffer(void)

// эта функция очищает дублирующий буфер

// несколько грубо, зато работает

_fmemset(double_buffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT +1);

} // конец Clear_Double_Buffer

// ОСНОВНАЯ ПРОГРАММА /////////////////////////////////////

void main(void)

{

int index, done=0 ;

float scale=64;


// установка видеорежима 320х200х256

Set_Mode(VGA256);

// установка размера спрайта

sprite_width = sprite_height = 64 ;

// инициализация файла PCX, который содержит

// мультииликационные кадры

PCX__Init ((pcx_picture_ptr)&text_cells) ;

// загрузка файла PCX, который содержит мультипликационные кадры

PCX_Load("textures.pcx", (pcx_picture_ptr)&text_cells,1) ;

// PCX_Show_Buffer((pcx_picture_ptr)&text_cells);

Sprite_Init ( (sprite_ptr) &object, 0, 0, 0, 0 ,0, 0);

// выборка четырех интересующих нас текстур

PCX_Grap_Bitmap((pcx_picture_ptr)&text_cells,

(sprite_ptr)&object,0,0,0);

PCX_Grap_Bitmap((pcx_picture_ptr)&text_cells,

(sprite_ptr)&object,1,1,0);

PCX_Grap_Bitmap((pcx_picture_ptr)&text cells,

(sprite_ptr)&object,2,2,0) ;

PCX_Grap_Bitmap((pcx_picture_ptr)&text_cells,

(sprite_ptr)&object,3,3,0) ;

// резервируем память под дублирующий буфер

Init_Double_Buffer() ;

// позиционируем объект в центре экрана

object.curr frame =0;

object.х = 160-(sprite_width>>1);

object.у = 100-(sprite height>>1);

// очищаем дублирующий буфер

Clear_Double_Buffer();

// воспроизводим масштабированную текстуру

Scale_Sprite((sprite_ptr)&object,scale) ;

Show_Double_Buffer(double buffer) ;

_settextposition(24,0);

Printf("Q - Quit, < > - Scale, Space - Toggle.");

// главный цикл while(!done)

{

// нажал ли игрок клавишу?

if (kbhit())

{

switch(getch()) {

case '.': // увеличиваем объект

{

if (scale<180) ( scale+=4;

object.x-=2;

object.y-=2;

} // конец оператора if

} break;

case ',' : // уменьшаем объект

{

if (scale>4) {

scale-=4;

object.x+=2;

object.y+=2;

} // конец оператора if

} break;

case ' ': // смена текстуры

{

// текстуры исчерпались?

if (++object.curr_frame==4)

object. curr_frame=0 ;

} break;

case 'q': // до свидания! {

done=1;

) break;

default:break;

} // конец оператора switch

// очистка дублирующего буфера

Clear_Double_Buffer() ;

// масштабирование спрайта и прорисовка его

//в дублирующем буфере

Scale_Sprite((sprite_ptr)&object,scale);

// вывод дублирующего буфера на экран

Show_Double_Buffer(double_buffer);

_settextpostion(24,0) ;

printf("Q - Quit,"< > - Scale, Space - Toggle.");

}// конец оператора,if

} // конец оператора..while

// удаление файла PCX

PCX_Delete((pcx_picture_ptr)&text_cells);

// возврат в текстовый режим

Set_Mode(TEXT_MODE);

} // конец функции main

Повороты объектов

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

Вращение битовых объектов до сих пор еще не до конца исследованная область. Инженеры и программисты и по сей день пытаются найти наиболее эффективные алгоритмы для этой операции. Проблема заключается в том, что битовый образ - это конгломерат сотни, если не тысячи пикселей и чтобы повернуть образ, нам надо правильно произвести поворот каждой из этих точек, Обычно это связано с довольно сложными математическими преобразованиями, которые выполняются на персональном компьютере не очень-то быстро. Вы можете спросить: «А как же это сделал Крис Роберте в игре Wing Commander?» Очень просто: он заранее получил все возможные повороты всех космических кораблей с помощью программы моделирования, а потом просто занес их в гигантскую таблицу. Единственная операция, которая могла бывыполняться долго ~ масштабирование, тоже была произведена заранее, а результаты также занесены в таблицу. Я всегда действую подобным же образом и вам советую заранее создать все варианты поворотов ваших изображений, используя один из двух пакетов, либо Deluxe Paint фирмы Electronic Arts или, если вам нравится тратить деньги, 3D Studio фирмы AutoDesk. Затем поместите их в таблицу, и пусть ваша программа извлекает изображения из этой таблицы, используя угол поворота, как индекс. Отрицательная сторона этого метода - не очень рациональное использование памяти. Хранение 32 или 64 битных образов для всех возможных объектов отъедает существенный кусок памяти. Однако можно использовать частичные а не полные таблицы. Например, вы можете поместить в таблицу только кадры для одного квадранта, а для других симметричных квадрантов генерировать изображения уже во время игры. Так как поворот вокруг осей Х и Y делается очень просто — такой метод используется очень часто.

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