Урок Инициализация в Windows я начинаю это пособие непосредственного с кода, разбитого на секции, каждая из которых будет подробно комментироваться. Первое, что вы должны сделать это создать проект в Visual C++



страница20/27
Дата17.11.2018
Размер7.85 Mb.
ТипУрок
1   ...   16   17   18   19   20   21   22   23   ...   27
    Навигация по данной странице:
  • InitObject()
WindowProc(). Я также добавил int mouse_xmouse_y, чтобы сохранить положение курсора мыши. В NeHeGL.h две следующие строки кода были добавлены: extern int mouse_x; & extern int mouse_y;.

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

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

 

#include // заголовочный файл для Windows



#include    // заголовочный файл для стандартного ввода/вывода

#include   // заголовочный файл для манипуляций с переменными аргументами

#include    // заголовочный файл для библиотеки OpenGL32

#include   // заголовочный файл для библиотеки GLu32

#include     // для генерации псевдослучайных чисел

#include "NeHeGL.h"  // заголовочный файл для NeHeGL

 

В уроке 1 я рассказал, как правильно подключить библиотеки OpenGL. В Visual C ++ надо выбрать Project / Settings / Link. И добавить в строчку "Object/library modules" следующую запись: OpenGL32.lib, GLu32.lib и glaux.lib. Если транслятор не сможет подключить нужную библиотеку, то это заставит его извергать ошибку за ошибкой. Именно то, что вы и не хотите! Это все обостряется, когда Вы включили библиотеки в режим отладки, и пробуете скомпилировать ваш в режиме без отладочной информации (release)... еще больше ошибок. Есть много людей, которые просматривают код. Большинство из них плохо знакомы с программированием. Они берут ваш код, и пробуют компилировать его. Они получают ошибки, удаляют код и ищут дальше.



Код ниже сообщит транслятору, что нужно прикомпоновать нужные библиотеки. Немного больше текста, но намного меньше головной боли, в конечном счете. В этом уроке, мы используем библиотеки OpenGL32, GLU32 и WinMM (для проигрывания звука). В этом уроке мы будем загружать TGA файлы, поэтому мы не нуждаемся в библиотеке glaux.

 

#pragma comment( lib, "opengl32.lib" )  // Найти OpenGL32.lib во время линкования



#pragma comment( lib, "glu32.lib" )     // Найти GLu32.lib во время линкования

#pragma comment( lib, "winmm.lib" )     // Найти WinMM во время линкования

 

В трех строках ниже происходит проверка определения компилятором CDS_FULLSCREEN (используется в переключении видеорежима в функции ChangeDisplaySettings). Если это не так, то мы вручную задаем CDS_FULLSCREEN значение 4. Некоторые компиляторы не определяют CDS_FULLSCREEN и возвратят сообщение об ошибки, если CDS_FULLSCREEN используется! Чтобы предотвратить сообщение об ошибке, мы проверяем, был ли CDS_FULLSCREEN определен и если нет, то мы вручную задаем его. Это сделает жизнь немного проще для каждого. (Примечание переводчика: здесь ошибка, это определение нужно перенести в файл NeHeGL.cpp, так как там используется функция ChangeDisplaySettings).



Затем мы объявляем DrawTargets, и задаем переменные для нашего окна и обработки клавиатуры. Если Вы не знаете, как надо определять переменные, то пролистайте MSDN глоссарий. Имейте в виду, я не преподаю C/C++, купите хорошую книгу по этому языку, если Вам нужна справка не по GL коду!

 

#ifndef    CDS_FULLSCREEN     // CDS_FULLSCREEN не определен



#define    CDS_FULLSCREEN 4   // компилятором. Определим его,

#endif                        // чтобы избежать ошибок

 

void DrawTargets();           // декларация



 

GL_Window*  g_window;

Keys*    g_keys;

 

В следующем разделе кода задаются наши пользовательские переменные. Переменная base будет использоваться для наших списков отображения для шрифта. Переменная roll  будет использоваться, чтобы перемещения земли и создания иллюзии прокрутки облаков. Переменная level (уровень) используется по прямому назначению (мы начинаем с уровня 1). Переменная miss отслеживает, сколько объектов не было сбито. Она также используется, чтобы показать боевой дух игрока (если не было промахов, то это означает высокий боевой дух). Переменная killsследит за тем, сколько целей было поражено на каждом уровне. В переменной score сохраняется общее число попаданий в объекты, и переменная game будет использоваться, чтобы сообщить о конце игры!



Последняя строка определяет нашу функцию сравнения. Функция qsort требует последний параметр с типом (const *void, const *void)..

 

// Наши пользовательские переменные



GLuint   base;    // Список отображения для шрифта

GLfloat  roll;    // Прокрутка облаков

GLint    level=1; // Текущий уровень

GLint    miss;    // Пропущенные цели

GLint    kills;   // Счетчик поражений для уровня

GLint    score;   // Текущий счет

bool     game;    // Игра окончена?

 

typedef int (*compfn)(const void*, const void*); // Определение нашей функции сравнения



 

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

Краткое описание переменных... Переменная rot определяет направление, в котором мы хотим вращать объект. Переменная hit равна ЛОЖЬ, если в объект еще не попали. Если объект был поражен или вручную помечен как пораженный, значение hit будет ИСТИННА.

Переменная frame используется, чтобы циклически повторять кадры анимации взрыва объекта. Поскольку frameувеличивается, то и произойдет смена текстуры взрыва. Далее мы остановимся на этом подробнее.

Чтобы следить за тем, в каком направлении наш объект перемещается, мы имеем переменную называемую dir. Переменная dir может принимать одно из 4 значений: 0 - объект перемещается влево, 1 - объект перемещает вправо, 2 – объект перемещается вверх и, наконец, 3 - объект перемещается вниз.

Переменная texid может иметь любое значение от 0 до 4. Ноль задает текстуру BlueFace (голубая рожа), 1 - текстуру Bucket (ведро), 2 – текстуру Target (мишень), 3 – Coke (банка кока-колы), и 4 - текстура Vase (ваза). Позже в коде загрузке текстуры, Вы увидите, что первые 5 текстур – изображения целей.

Обе переменные x и y используются для позиционирования объекта на экране. Переменная x задает позицию объекта по оси X, а переменная y задает позицию объекта по оси Y.

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

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

 

struct objects {



  GLuint  rot;       // Вращение (0-нет, 1-по часовой, 2-против)

  bool  hit;         // В объект попали?

  GLuint  frame;     // Текущий кадр взрыва

  GLuint  dir;       // Направление объекта (0-лево, 1-право, 2-вверх, 3-низ)

  GLuint  texid;     // ID текстуры объекта

  GLfloat  x;        // X позиция

  GLfloat y;         // Y позиция

  GLfloat  spin;     // Вращение

  GLfloat  distance; // Расстояние

};

 



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

 

typedef struct         // Создаем структуру



{

  GLubyte  *imageData; // Данные изображения

  GLuint  bpp;         // Цветность изображения в битах на пиксель.

  GLuint  width;       // Ширина изображения

  GLuint  height;      // Высота изображения

  GLuint  texID;       // ID текстуры используемый для выбора текстуры

} TextureImage;        // Имя структуры

 

Следующий код задает место для хранения наших 10 текстур и 30 объектов. Если Вы хотите добавить еще объектов в игру, то проверьте, что Вы изменили значение от 30 на то число объектов, которое Вы хотите.



 

TextureImage textures[10]; // Место для хранения 10 текстур

 

objects  object[30];       // Место для хранения 30 объектов



 

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

Затем я задаю ширину и высоту каждого объекта в последней строке кода. Чтобы получить ширину банки кока-колы (coke), я буду использовать вызов size[3].w. Для голубой рожи (Blueface) - 0, ведро (Bucket) - 1, мишень (Target) - 2, банка кока-колы (Coke) - 3 и ваза (Vase) - 4. Ширина задается w. Понятно?

 

struct dimensions {        // Размеры объекта



  GLfloat  w;              // Ширина объекта

  GLfloat h;               // Высота объекта

};

 

// Размеры для объектов:Blueface,     Bucket,      Target,       Coke,         Vase



dimensions size[5] = { {1.0f,1.0f}, {1.0f,1.0f}, {1.0f,1.0f}, {0.5f,1.0f}, {0.75f,1.5f} };

 

Следующий большой раздел кода загружает наше TGA изображение и конвертирует ее в текстуру. Это - тот же самый код, так который я использовал в уроке 25, если Вы нуждаетесь в детальном описании его, обратитесь к этому уроку.



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

 

bool LoadTGA(TextureImage *texture, char *filename)   // Загрузка TGA файла в память



{   

  GLubyte    TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0}; // Заголовок несжатого TGA

  GLubyte    TGAcompare[12]; // Используется для сравнения заголовка TGA

  GLubyte    header[6];      // Первые 6 полезных байт заголовка

  GLuint     bytesPerPixel;  // Число байт на пиксель в файле TGA

  GLuint     imageSize;      // Используется для сохранения размера изображения

  GLuint     temp;           // Временная переменная

  GLuint     type=GL_RGBA;   // Режим GL по-умолчанию RBGA (32 BPP)

 

  FILE *file = fopen(filename, "rb"); // Открыть TGA файл



 

  // Если файл существует и 12 байт прочитаны и заголовок совпал и прочитаны

  // следующие 6 байт, то все нормально, иначе не удача

  if( file==NULL ||

    fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) ||

    memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0 ||

    fread(header,1,sizeof(header),file)!=sizeof(header))

  {


    if (file == NULL)          // Файл не существует? *Добавлено Jim Strong*

      return FALSE;            // вернуть FALSE

    else                       // иначе

    {


      fclose(file);            // Закрыть файл

      return FALSE;            // вернуть FALSE

    }

  }


 

  texture->width  = header[1] * 256 + header[0]; // Определить ширину (highbyte*256+lowbyte)

  texture->height = header[3] * 256 + header[2]; // Определить высоту (highbyte*256+lowbyte)

   


   if( texture->width <=0 ||             // Ширина меньше или равна чем 0

       texture->height <=0 ||            // Высота меньше или равна чем 0

       (header[4]!=24 && header[4]!=32)) // TGA 24 или 32 бита?

  {


    fclose(file);              // Если не так, то закрыть файл

    return FALSE;              // вернуть FALSE

  }

 

  texture->bpp = header[4];             // Получить число бит на пиксель (24 или 32)



  bytesPerPixel = texture->bpp/8;       // Разделить на 8, чтобы получить байт на пиксель

                                        // Вычислить размер памяти для данных TGA

  imageSize = texture->width*texture->height*bytesPerPixel;

 

  texture->imageData=(GLubyte *)malloc(imageSize); // Выделить память для данных TGA



 

  if( texture->imageData==NULL ||       // Память выделена?

       // Прочитано верное число байт?

      fread(texture->imageData, 1, imageSize, file)!=imageSize)

  {

    if(texture->imageData!=NULL) // Если память выделена



       free(texture->imageData); // Освободить память

 

    fclose(file);                // Закрыть файл



    return FALSE;                // вернуть FALSE

  }


 

  for(GLuint i=0; i

  {                  // Смена местами первого и третьего байтов ('R'ed и 'B'lue)

    temp=texture->imageData[i]; // Временно сохраняем значение

    texture->imageData[i] = texture->imageData[i + 2]; // Третий байт на место первого

    texture->imageData[i + 2] = temp; // Первый байт на место третьего

  }

 

  fclose (file); // Закрыть файл



 

  // Построить текстуру из данных

  glGenTextures(1, &texture[0].texID); // Генерировать ID текстуры OpenGL

 

  glBindTexture(GL_TEXTURE_2D, texture[0].texID); // Привязка текстуры



  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  // Линейная

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  // фильтрация

 

  if (texture[0].bpp==24) // Если TGA 24 бита



  {

    type=GL_RGB;          // Тогда 'type' равен GL_RGB

  }

 

  glTexImage2D(GL_TEXTURE_2D, 0, type,



               texture[0].width, texture[0].height,

               0, type, GL_UNSIGNED_BYTE, texture[0].imageData);

 

  return true; // Текстура построена, вернуть True



}

 

Код вывода шрифта из 2D текстуры тот же самый, который я использовал в предыдущих уроках. Однако есть несколько небольших изменений. Первое, что Вы можете заметить это то, что мы генерируем только 95 списков отображения. Если Вы посмотрите на текстуру шрифта, Вы увидите, что там есть только 95 символов. Второе, что Вы можете заметить то, что мы делим на 16.0f для получения cx, и мы делим на 8.0f для cy. Это делается, потому что текстура шрифта широкая (256 пикселей), а высота равна половине ширины (128 пикселей). Поэтому, вычисляяcx, мы делим на 16.0f, и, вычисляя cy, мы делим на 8.0f.



Если Вы не понимаете код ниже, возвратитесь к уроку 17. Код вывода шрифта подробно объясняется в уроке 17!

 

GLvoid BuildFont(GLvoid)              // Построить наш список отображения для фонта



{

  base=glGenLists(95);                // Создание 95 списков отображения

  glBindTexture(GL_TEXTURE_2D, textures[9].texID); // Привязка нашей текстуры фонта

  for (int loop=0; loop<95; loop++)   // Цикл по спискам

  {

    float cx=float(loop%16)/16.0f;    // X позиция текущего символа



    float cy=float(loop/16)/8.0f;     // Y позиция текущего символа

 

    glNewList(base+loop,GL_COMPILE);  // Начало построения списка



      glBegin(GL_QUADS);              // Использовать четырехугольник для символа

        // Координаты текстуры / вершин (Низ Лево)

        glTexCoord2f(cx,          1.0f-cy-0.120f); glVertex2i(0,0);

        // Координаты текстуры / вершин (Низ Право)

        glTexCoord2f(cx+0.0625f, 1.0f-cy-0.120f);  glVertex2i(16,0);

        // Координаты текстуры / вершин (Верх Право)

        glTexCoord2f(cx+0.0625f, 1.0f-cy);         glVertex2i(16,16);

        // Координаты текстуры / вершин (Низ Лево)

        glTexCoord2f(cx,         1.0f-cy);         glVertex2i(0,16);

      glEnd();                        // Конец построения нашего четырехугольника (символ)

      glTranslated(10,0,0);           // Сдвиг вправо на символ

    glEndList();                      // Завершение построения списка отображения

  }                                   // Цикл по всем символам

}

 



Код вывода строки такой же, как в уроке 17, но был изменен, чтобы дать возможность нам вывести счет, уровень и мораль на экран (переменные, которые непрерывно изменяются).

 

GLvoid glPrint(GLint x, GLint y, const char *string, ...) // Здесь печать



{

  char       text[256];           // Место для строки

  va_list    ap;                  // Указатель на список аргументов

 

  if (string == NULL)             // Если нет текста



    return;                       // то ничего не делаем

 

  va_start(ap, string);           // Разбор строки из переменных



      vsprintf(text, string, ap); // Конвертирование символов в числа

  va_end(ap);                     // Значения сохраняются в текст

 

  glBindTexture(GL_TEXTURE_2D, textures[9].texID);   // Выбор нашей текстуры шрифта



  glPushMatrix();                 // Сохранить матрицу вида модели

  glLoadIdentity();               // Сброс матрицы вида модели

  glTranslated(x,y,0);            // Позиционирование текста (0,0 – низ лево)

  glListBase(base-32);            // Выбор набора символов

  glCallLists(strlen(text), GL_UNSIGNED_BYTE, text); // Нарисовать текст списками

  glPopMatrix();                  // Восстановить матрицу

}

 

Этот код будет вызываться позже из функции qsort. Он нужен для сравнения расстояний в двух структурах и возвращает значение -1, если расстояние в первой структуре было меньше чем во второй, и значение 1, если расстояние в первой структуре больше чем во второй, и значение 0, если расстояние в обеих структурах равно.



 

int Compare(struct objects *elem1, struct objects *elem2) // Функция сравнения

{

   // Если расстояние в первой структуре меньше чем во второй



   if ( elem1->distance < elem2->distance)

      return -1; // Вернуть –1

   // Если расстояние в первой структуре больше чем во второй

   else if (elem1->distance > elem2->distance)

      return 1;  // Вернуть 1

   else          // Иначе (Если расстояние равно)

      return 0;  // Вернуть 0

}

 



В коде функции InitObject() мы инициализируем каждый объект. Мы начинаем, с установки rot в 1. При этом объект будет вращаться по часовой стрелке. Затем мы инициализируем анимацию взрыва для кадра 0 (мы не хотим, чтобы взрыв был показан с половины анимационной последовательности). Затем мы устанавливаем hit в ЛОЖЬ, что означает, что объект еще не был сбит или подвергся самоуничтожению. Для выбора текстурного объекта, переменной texid присваивается случайное значение от 0 до 4. Ноль - текстура blueface, и 4 - текстура vase. Это даст нам один из 5 случайных объектов.

Переменной distance будет присвоено случайное число от -0.0f до -40.0f (4000/100 равно 40). Когда мы будем рисовать объект, мы смещаем его на 10 единиц в экран. Поэтому, когда объект нарисован, он будет нарисован от -10.0f до -50.0f единиц в глубине экрана (и не близко и не далеко). Я делю случайное число на 100.0f, чтобы получить более точное значение с плавающей запятой.

После того как мы задали случайное значение дистанции до объекта, мы задаем объекту случайное значение по y. Мы не хотим, чтобы объект был ниже чем -1.5f, иначе он будет под землей, и мы не хотим, чтобы объект был выше, чем 3.0f. Поэтому диапазон наших случайных чисел не может быть больше чем 4.5f (-1.5f+4.5f=3.0f).

Для вычисления позиции x, мы используем довольно хитрый способ. Мы берем наше расстояние, и мы вычитаем 15.0f из него. Затем мы делим результат на 2 и вычитаем 5*level. Наконец, мы вычитаем случайное число от 0.0f до 5 умноженное на текущий уровень. Мы вычитаем 5*level и случайное число от 0.0f до 5*level так, чтобы наш объект появлялся дальше от экрана на более высоких уровнях. Если бы мы не делали этого, объекты появились бы один за другим, при этом попасть в них было бы труднее, чем в этом варианте.

Наконец мы выбираем случайное направление (dir) от 0 (слева) до 1 (направо).

Вот пример, чтобы вам было все это понятно. Скажем, что расстояние равно -30.0f, а текущий уровень равен 1:

 

object[num].x=((-30.0f-15.0f)/2.0f)-(5*1)-float(rand()%(5*1));


object[num].x=(-45.0f/2.0f)-5-float(rand()%5);
object[num].x=(-22.5f)-5-{давайте скажем 3.0f};
object[num].x=(-22.5f)-5-{3.0f};
object[num].x=-27.5f-{3.0f};
object[num].x=-30.5f; 

Запомним, что мы сдвигаем на 10 единиц в экран прежде, чем мы выводим наши объекты, и расстояние в примере выше равно -30.0f. Можно с уверенностью сказать, что наше фактическое расстояние вглубь экрана будет равно -40.0f. Используя код перспективы из файла NeHeGL.cpp, с уверенностью можно будет предположить, что, если расстояние равно -40.0f, левый край экрана будет -20.0f, и правый край будет +20.0f. В коде выше наше значение xравно -22.5f (т.е. за левым краем экрана). Затем мы вычитаем 5 и наше случайное значение 3, которое гарантирует, что объект начнет перемещаться за экраном (с -30.5f) - это означает, что объект должен будет переместить приблизительно на 8 единиц вправо прежде, чем он появится на экране.

GLvoid InitObject(int num)     // Инициализация объекта

{

  object[num].rot=1;           // Вращение по часовой



  object[num].frame=0;         // Сброс кадра взрыва в ноль

  object[num].hit=FALSE;       // Сброс статуса попадания в объект

  object[num].texid=rand()%5;  // Назначение новой текстуры

  object[num].distance=-(float(rand()%4001)/100.0f);  // Случайная дистанция

  object[num].y=-1.5f+(float(rand()%451)/100.0f);     // Случайная Y позиция

  // Случайное начальная X позиция, основанная на расстоянии объекта

  // и случайном числе для задержки (Положительное значение)

  object[num].x=((object[num].distance-15.0f)/2.0f)-(5*level)-float(rand()%(5*level));

  object[num].dir=(rand()%2);  // Взять случайное направление

 

Теперь мы проверим, в каком направлении объект собирается лететь. Код ниже проверяет, перемещается ли объект влево. Если это так, то мы должны изменить вращение так, чтобы объект вращался против часовой стрелки. Мы делаем это, изменяя значение 


Каталог: forum
forum -> Лечение гепатозов
forum -> Система ведения овцеводства в крестьянско-фермерских и личных хозяйствах населения
forum -> Yaesuft-857 (ft-897) – переходник для подключения cat интерфейса к микрофонному разъему трансивера
forum -> Васильев Владимир Юрьевич
forum -> tl-wr1043ND Беспроводной гигабитный маршрутизатор серии n copyright & trademarks
forum -> Цели и задачи Контакта rtf docx
forum -> Назовите не менее трех результатов революции 1905- 1907гг. Приведите не менее трех положений, отражающих значение революции для отечественной истории начала 20 века


Поделитесь с Вашими друзьями:
1   ...   16   17   18   19   20   21   22   23   ...   27


База данных защищена авторским правом ©vossta.ru 2019
обратиться к администрации

    Главная страница