Листинг 8.3. Последний Шаттл (FINVYREN.C).

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

#include

#include

#include

#include

#include

#include

#include

#include

#tinclude

#include

#include

#include

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

// прототипы /////////////////////////////////////

void Create_Scale_Data_X(int scale, int far *row) ;

void Create_Scale_Data_Y(int scale, int *row);

void Build_Scale_Table(void);

void Scale_Sprite(sprite_ptr sprite,int scale);

void Clear_Double_Buffer(void);

void Timer(int clicks);

void Init_Stars(void) ;

void Move_Stars(void);

void Draw_Stars(void) ;

// ОПРЕДЕЛЕНИЯ /////////////////////////////////////////////

#define NUM_STARS 50 // количество звезд на небе

#define MAX_SCALE 200 // максимальный размер спрайта

#define SPRITE_X_SIZE 80 // размеры текстуры спрайта

#define SPRITE_y_SIZE 48

// СТРУКТУРЫ /////////////////////////////////////////////// // это звезда

typedef struct star_typ

{

int x,y; // позиция звезды

int xv,yv; // скорость звезды

int xa,ya; // ускорение звезды

int color; // цвет звезды

int clock; // число "тиков", которое звезда существует

int acc_time; // количество "тиков" до ускорения звезды

int acc_count; // счетчик ускорения

} star, *star_ptr;

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

unsigned int far *clock = (unsigned int far *)0х0000046CL;

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

sprite object;

// обобщенный спрайт, который содержит кадры корабля

pcx_picture text_cells; // файл PCX с кадрами корабля

int *scale_table_y[MAX_SCALE+l] ;

// таблица предварительно рассчитанных коэффициентов масштабирования

int far *scale_table_x[MAX_SCALE+l];

// таблица предварительно рассчитанных коэффициентов масштабирования

star star_field[NUM_STARSj; // звездное небо

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

void Timer(int clicks)

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

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

unsigned int now;

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

now = *clock;

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

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

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

} // конец Timer

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

void Init_Stars(void)



{

// эта функция инициализирует структуру данных звезд

// при старте программы

int index,divisor;

for (index=0; index

{

star_field[index].x = 150 + rand() % 20;

star_field[indexl.у = 90 + rand() % 20;

if (rand()%2==1)

star_field[index].xv = -4 + -2 * rand() % 3;

else

star_field[index].xv = 4 + 2 * randO % 3;

if (rand()%2==1)

star_field[index].yv = -4 + -2 * rand() % 3;

else

star_field[index].yv = 4 + 2 * randO % 3;

divisor = 1 + rand()%3;

star_field[index].xa = star_field[index].xv/divisor;

star_field[index] .ya = star_field [index] .yv/divisor;

star_field[index].color = 7;

star_field[index].clock = 0;

star_field[index].acc_time = 1 + rand() % 3;

star_field[index].acc_count = 0;

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

} // конец Init Stars

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

void Move_Stars(void) {

// Эта функция перемещает звезды и проверяет, не вышла ли звезда

// за пределы экрана. Если да, звезда создается вновь.

int index,divisor;

for (index=0; index

{

star_field[index].x += star_field[index].xv;

star field[index].y += star_field[index].yv;

// проверка выхода звезды за пределы экрана

if(star_field[index].x>=SCREEN_WJDTH || star_field[index].x<0 ||

star_field[index].y>=SCREEN_HEIGHT || star_field[index].y<0)

{

// восстановление звезды

star_field[index].x = 150 + rand() % 20;

star_field[index].у = 90 + randf) % 20;

if (rand()%2==l)

star_field[index].xv = -4 + -2 * rand() % 3;

else

star_field[index] .xv = 4 + 2 * rand() % 3;

if (rand()%2==l)

star_field[index].yv = -4 + -2 * rand() % 3;

else

star_field[index].yv = 4 + 2 * rand() % 3;

divisor = 1 + rand()%3;

star_field[index].xa = star_field[index].xv/divisor;

star_field [index] .ya = star_field.[index] .yv/divisor;

star_field[index].color = 7;

star_field[index].clock = 0;

star_field[index].acc_time = 1 + rand() % 3;

star field[index].ace_count = 0;

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

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

if (++star_field[index].acc_count==star_field[index].асc_time)

{



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

star_field[indexl.acc_count=0;

// ускоряем

star_field[index].xv += star field[index].xa;

star_field[index].yv += star_field[index].ya;

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

//не пора ли изменить цвет звезды

if (++star_field[index].clock > 5)

{

star_field[index].color = 8;

} // конец оператора if (> 5)

else

if (star_field[index].clock > 10)

{

star_field[index].color =255;

} // конец оператора if (> 10)

else

if (star_field[index].clock> 25)

{

star_field[index].color = 255;

} // конец оператора if (> 25)

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

} // конец Move_Stars

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

void Draw_Stars(void)

{

// эта функция рисует звезды в дублирующем буфере

int index;

for (index=0; index

{

Plot_Pixel_Fast_D(star_field[index].х,star_field[index].y, (unsigned char)star_field[index].color) ;

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

} // конец Draw_Stars

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

void Create_Scale_Data_X (int scale, int far *row)

{

// эта функция масштабирует полосу текстуры всех возможных

// размеров и создает огромную таблицу соответствий

int х;

float x_scale_index=0, х_scale_step;

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

// для отображения на результирующее изображение за цикл

x_scale_step = (float) (sprite_width)/(float)scale;

x_scale_index+=x_scale_step;

for (x=0; x

// помещаем данные в массив для последующего использования

row[x] = (int) (x_scale_index+.5);

if (row[x] > (SPRITE_X_SIZE-1)) row[x] = (SPRITE_X_SI2E-1);

// рассчитываем следующий индекс

х_scale_index+=x_scale_step;

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

} // конец Create Scale_Data X

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

void Create_Scale_Data_Y(int scale, int *row)

{

// эта функция масштабирует полосу текстуры всех возможных

// размеров и создает огромную таблицу соответствий

int у;

float y_scale_index=0, y_scale step;

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

// для отображения на результирующее изображение за цикл

y_scale_step = (float)(sprite_height)/(float)scale;

y_scale_index+=y_scale_step;

for (у=0; y

{ // помещаем данные в массив для последующего использования

row[y] = ((int)(y_scale_index+.5)) * SPRITE_X_SIZE;

if (row[y] > (SPRITE_Y_SIZE-1)*SPRITE_X_SIZE) row[y]] = (SPRITE_Y_SIZE-1)*SPRITE_X_SIZE;

// рассчитываем следующий индекс

y_scale_index+=y_scale_step;

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

} // конец Create_Scale_Data_Y ////////////////////////////////////////

void Build_Scale_Table(void)

{

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

// коэффициентов масштабирования для всех возможных размеров

// от 1 до 200 пикселей

int scale;

// резервируем память

for (scale=1; scale<=MAX_SCALE; scale++)

{

scale_table_y[scale] = (int *)malloc(scale*sizeof(int)+1);

scale_table_x[scale] = (int far *)_fmalloc(scale*sizeof(int)+1);

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

// создаем Таблицу масштабирования для осей Х и У

for (scale=l; scale<=MAX_SCALE; scale++)

{

// рассчитываем коэффициент для данного масштаба

Create_Scale_Data_Y (scale, (int *)scale_table_y[scale]);

Create_Scale_Data_X (scale, (int far *)scale_table x[scale]);

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

} // конец Build_Scale_Table

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

void Scale_Sprite(sprite_ptr sprite,int scale)

{

// эта функция масштабирует спрайт (без отсечения). Масштабирование

// производится с использованием заранее рассчитанной таблицы,

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

// столбец. Затем другая таблица используется для учета

// масштабирования этих столбцов по оси Х

char far *work_sprite; // текстура спрайта

int *row_y; // указатель на масштабированные

// по оси У данные (заметьте, что

// это ближний указатель)

int far *row_x;. // указатель на масштабированные

// по оси Х данные (заметьте, что

// это дальний указатель)

unsigned char pixel; // текущий текстель

int x, // рабочие переменные

y,

column, work_offset, video_offset, video_start;

// если объект слишком мал, то и рисовать его не стоит

if (scale

// рассчитываем необходимые для масштабирования данные

row_y = scale_table_y[scale];

row_x = scale_table_x[scale];

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

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

// рассчитываем начальное смещение

video_start = (sprite->y

// изображение рисуется слева направо и сверху вниз

for (х=0; x

{

// пересчитываем адрес следующего столбца

video_offset = video_start + x;

// определяем, какой столбец должен быть отображен,

// исходя из индекса масштабирования по оси Х

column = row_x[x];

// Наконец рисуем столбец обычным образом

for (y=0; y

{

// проверка на "прозрачность"

pixel = work_sprite[work_offset+column];

if (pixel)

double_buffer[video_offset] = pixel;

// индекс следующей строки экрана и смещение в

// области хранения текстуры

video_offset += SCREENJHDTH;

work_offset = row_y[y];

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

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

} // конец Scale_Sprite

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

void Clear_Double_Buffer(void)

{

// угадали что это?

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

} // конец Clear_Double_Buffer

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

void main(void)

{

// корабль помещается в космическое пространство и игрок

// может перемещать его среди звезд

int done=0, // флаг выхода

scale=64,

direction=6; // направление корабля (текущий кадр)

float sсale_distance = 24000,

view distance = 256,

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

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

х=0, // позиция корабля в пространстве

у=0,

z=1024,

xv=0,zv=0, // скорость корабля в плоскости Х-Z

angle=180,

// угол поворота корабля

ship_speed=10; // величина скорости корабля

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

_setvideomode(_MRES256COLOR) ;

// все спрайты будут иметь этот размер

sprite_width = 80;

sprite_height = 48;

// создание таблицы соответствия для подсистемы масштабирования

Build_Scale_Table();

// инициализация файла PCX, который содержит все кадры PCX_Init((pcx_picture_ptr)&text_cells);

// загрузка файла PCX, который содержит все кадры

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

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

Init_Double_Buffer();

// инициализация звездного неба

Init_Stars() ;

// установка направления и скорости корабля

angle=direction*30+90;

xv = (float)(ship_speed*cos(3.14159*angle/180));

zv = (float)(ship_speed*sin(3.14159*angle/180));

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

// загрузка 12 кадров космического корабля

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)Sobject,3,0,1) ;

PCX_Grap_Bitmap((pcx_picture_ptr)&text cells,

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

PCX_Grap_Bitmap((pcx_picture_ptr)&text cells,

(sprite_ptr)&object,5,2,1) ;

PCX_Grap_Bitmap ((pcx_picture_ptr) &text_cells,

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

PCX_Grap_Bitmap ((pcx_picture_pfcr) stext_cells,

(sprite_ptr)&object,7,1,2);

PCX_Grap_Bitmap((pcx picture ptr)&text cells,

(sprite_ptr)Sobject,8,2,2);

PCX_Grap_Bitmap((pcx_picture_ptr)&text_cells,

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

PCX_Grap_Bitmap((pcx_picture_ptr)&text_dells,

(sprite_ptr)&object,10,1,3) ;

PCX_Grap_Bitmap ((pcx_picture_ptr) &text_cells,

(sprite_ptr)&object,11,2,3);

// инициализация положения корабля

object.curr_frame = 0;

object.x = 0;

object.у =0;

Clear_Double_Buffer() ;

// отслеживание действий игрока и прорисовка корабля

while(!done) {

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

if (kbhit()) {

switch(getch()) {

case '4' // повернуть корабль влево

{

if (++direction==12)

{

direction==0;

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

} break;

case'6': // повернуть корабль вправо

{

if (--direction < 0)

{

direction=11;

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

} break;

case '8' : // ускорить корабль

{

if (++ship_speed > 20) ship_speed=20;

} break; case '2': // замедлить корабль

{

if (--ship_speed < 0)

ship_speed=0;

} break;

case 'q': // выход из программы

{

done=l;

} break;

default: break;

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

// векторы направления и скорости

angle=direction*30+90;

xv = (float)(ship_speed*cos(3.14159*angle/180));

zv = (float)(ship_speed*sin(3.14159*angle/180)};

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

// переместить корабль

x+=xv;

z+=zv;

// ограничить по ближней плоскости

if (z<256)

Z=256;

// рассчитать размер изображения

scale = (int)( scale_distance/z );

// на основании размера изображения

// рассчитать проекции Х и Y

object.х = (int) ((float)x*view_distance / (float)z) + 160 - (scale>>1);

object.у = 100 - (((int) ( (float) y*view_distance / (float)z) + (scale>>1)) );

// ограничить рамками экрана if (object.х < 0)

object.х = 0;

else if (object.x+scale >= SCREEN_WIDTH)

object.x = SCREEN_WIDTH-scale;

if (object.у < 0) object.у = 0;

else

if (object.y+scale >= SCREEN_HEIGHT)

object.у = SCREEN_HEIGHT-scale;

// выбрать нужный кадр

object.curr_frame = direction;

// очистить дублирующий буфер

Clear_Double_Buffer() ;

Move_Stars();

Draw_Stars();

// масштабировать спрайт до нужного размера

Scale_Sprite((sprite_ptr)&object,scale) ;

Show_Double_Buffer(double_buffer);

// вывести на экран информацию для игрока

_settextposition(23,0) ;

printf("Position=(%4.2f,%4.2f,%4.2f) ",x,y,z) ;

// немного подождать

Timer(1) ;

} // конец while

// удалить файл

PCX PCX_Delete((pcx_picture_ptr)&text_cells) ;

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

_setvideomode(_DEFAULTMODE);

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

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

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

Оцифровка объектов и моделирование

Это самая приятная часть. Я чувствовал себя Джорджем Лукасом, создавая макеты и их изображения для этой главы. Хотя это и крайне увлекательное занятие, оно не столь уж и простое, как многие могли бы подумать. (Это касается тебя, Марк!)

Существует три основных подхода к построению моделей и образов для игр:

§ Рисовать их;

§ Моделировать их с помощью трехмерных программ типа 3D Studio;

§ Оцифровывать кадры, полученные с помощью видеокамеры.

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

Теоретически дискретизация миниатюрного макета - довольно простое занятие. Купил видеокамеру или одолжил ее у кого-нибудь, зарядил - и вперед, начинай снимать объект. Это заблуждение! Существует миллион деталей, о которых вы не должны забывать. Освещение, фокус, программное обеспечение, цветовая палитра, разрешение и т. д. - все должно быть сделано, как надо. В своей работе я использовал Creative Labs Video Blaster SE (Special Editions), вместе с Microsoft Video for Windows, Adobe Photostyler и Deluxe Animation фирмы Electronic Art. Кроме этого, я использовал очень дорогую видеокамеру, которую одолжил у своей хорошей знакомой, пообещав упомянуть ее в этой книге. (Диона, я знаю, что на Олимпиаде 1996 года золотая медаль будет твоей!) Наконец, собрав все необходимое, я построил маленькую студию с голубым экраном, вращающейся платформой, освещением и приспособлением для определения угла поворота модели.

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

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

Создание съемочного мини-павильона

Вам необходимо:

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

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

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

Фон

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


6628264531448942.html
6628313951060054.html
    PR.RU™