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


stdio.h используется для операций с файлами, и мы включаем stdarg.h



страница10/27
Дата17.11.2018
Размер7.85 Mb.
ТипУрок
1   ...   6   7   8   9   10   11   12   13   ...   27
stdio.h используется для операций с файлами, и мы включаем stdarg.hдля того чтобы мы могли вывести переменные на экран, типа счета и текущей стадии.

 

// Этот код сделан Jeff Molofee в 2000



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

 

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



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

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

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

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

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

 

HDC       hDC=NULL;  // Частный контекст устройства GDI



HGLRC     hRC=NULL;  // Контекст текущей визуализации

HWND      hWnd=NULL; // Декриптор нашего окна

HINSTANCE hInstance; // Копия нашего приложения

 

Теперь мы задаем наши булевские переменные. Переменная vline отслеживает все 121 вертикальную линию, которые составляют нашу игровую сетку. 11 линий вдоль и 11 вверх и вниз. Переменная hline отслеживает все 121 горизонтальную линию, которые составляют игровую сетку. Мы используем переменную ap для отслеживания, действительно ли клавиша “A” нажата.


Значение переменной filled равно ЛОЖЬ, пока сетка не закрашена и равно ИСТИНА, когда она закрашена. Назначение переменной gameover довольно очевидно. Если gameover равно ИСТИНА, то игра закончена, иначе Вы все еще играете. Переменная anti отслеживает сглаживание (antialiasing). Если anti равно ИСТИНА, сглаживание объектов ВКЛЮЧЕНО. Иначе оно выключено. Переменные active и fullscreen отслеживают, была ли программа свернута или нет, и запущена программа в полноэкранном режиме или оконном режиме.

 

bool    vline[11][10];     // Отслеживает вертикальные линии



bool    hline[10][11];     // Отслеживает горизонтальные линии

bool    ap;                // Клавиша 'A' нажата?

bool    filled;            // Сетка закрашена?

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

bool    anti=TRUE;         // Сглаживание?

bool    keys[256];         // Массив для манипуляций с клавиатурой

bool    active=TRUE;       // Флаг активности окна, по умолчанию=TRUE

bool    fullscreen=TRUE;   // Флаг полноэкранного режима, по умолчанию=TRUE

 

Теперь мы определяем наши целые переменные. Переменные loop1 и loop2 будут использоваться для разных целей, например: для проверки точек на нашей сетке, для проверки попадания противника в нас и для случайного размещения объектов на сетке. Вы увидите loop1/loop2 в действии позже. Переменная-счетчик delay используется, чтобы замедлить перемещение плохих парней. Если delay больше чем некоторое значение, враги двигаются, и delayсбрасывается в ноль.



 

Переменная adjust - особенная переменная! В нашей программе есть таймер, но он используется только для проверки, если ваш компьютер слишком быстр. Если это так, то delay создана, чтобы замедлить компьютер. На моей плате GeForce, программа выполняется безумно гладко, и очень быстро. После проверки этой программы на моем PIII/450 с Voodoo 3500TV, я заметил, что она выполняется чрезвычайно медленно. Проблема состоит в том, что мой код синхронизации, только замедляет игру. Но не ускоряет ее. Поэтому я ввел новую переменную, называемуюadjust (коррекция). Переменная adjust может принимать любое значение от 0 до 5. Объекты в игре перемещаются с различными скоростями в зависимости от значения adjust. Маленькие значения задают более гладкое перемещение, чем выше значение, тем они быстрее двигаются (граница после значений выше, чем 3). Это был единственно действительно простой способ сделать игру выполняемой на медленных системах. На одну вещь обратите внимание, независимо от того, как быстро объекты перемещаются, быстродействие игры никогда не будет больше чем, я ее назначил. Так присваивание переменной adjust значения равного 3, безопасно для быстрых и медленных систем.

 

Переменной lives присвоено значение 5, поэтому Вы начинаете игру с 5 жизнями. Переменная level - внутренняя переменная. В игре она используется, для того чтобы отслеживать уровень сложности. Это не тот уровень, который Вы увидите на экране. Переменной level2 присваивается то же самое значение, как и level, но ее значение необратимо увеличивается в зависимости от вашего навыка. Если Вы прошли уровень 3, переменная level замрет на значении 3. Переменная level - внутренняя переменная, характеризующая сложность игры. Переменная stageотслеживает текущую стадию игры.



 

int    loop1;              // Общая переменная 1

int    loop2;              // Общая переменная 2

int    delay;              // Задержка для Противника

int    adjust=3;           // Настройка скорости для медленных видеокарт

int    lives=5;            // Жизни для игрока

int    level=1;            // Внутренний уровень игры

int    level2=level;       // Уровень игры для отображения

int    stage=1;            // Стадия игры

 

Теперь мы создадим структуру для слежения за объектами в нашей игре. Мы имеем точное положение по X (fx) и точное положение по Y (fy). Эти переменные будут передвигать игрока и противников по сетки сразу на несколько пикселей. Они служат для создания плавного перемещения объекта.



 

Затем мы имеем x и y. Эти переменные будут отслеживать, в каком узле сетки находится наш игрок. Есть 11 точек слева направо и 11 точек сверху вниз. Поэтому переменные x и y могут принимать любое значение от 0 до 10. Именно поэтому мы нуждаемся в точных значениях. Если бы мы стали перемещать игрока с одного из 11 узлов по горизонтали, или с одного из 11 узлов по вертикали на другой соседний узел, то наш игрок быстро бы прыгал по экрану, а не плавно двигался между ними.

 

Последняя переменная spin будет использоваться для вращения объектов относительно оси Z.



 

struct    object           // Структура для игрока

{

  int  fx, fy;             // Точная позиция для передвижения



  int  x, y;               // Текущая позиция игрока

  float  spin;             // Направление вращения

};

 

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



 

В первой строке ниже создается структура для нашего игрока. По существу мы даем нашему игроку структуру со значениями fxfyxy и spin. Добавив эту строку, мы сможем обратиться к позиции игрока x при помощи записиplayer.x. Мы можем изменять вращение игрока, добавляя число к player.spin.

 

Вторая строка немного отличается. Поскольку мы можем иметь до 9 противников на экране одновременно, мы должны создать вышеупомянутые переменные для каждого противника. Мы делаем для этого массив из 9 противников. Позиция x первого противника будет enemy[0].x. Позиция второго противника будет enemy[1].x, и т.д.



 

Последняя строка создает структуру для нашего специального элемента. Специальный элемент - песочные часы, которые будут появляться на экране время от времени. Мы должны следить за значениями x и y песочных часов, но так как песочные часы не двигаются, мы не должны следить за точными позициями. Вместо этого мы будем использовать точные переменные (fx и fy) для других целей.

 

struct  object  player;                // Информация о игроке



struct  object  enemy[9];              // Информация о противнике

struct  object  hourglass;             // Информация о песочных часах

 

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



 

Вначале мы создаем целое число размером 64 бита, которое называется frequency (частота). В эту переменную будет помещено значение частоты таймера. Когда я вначале написал эту программу, я забыл включить эту переменную. Я не понимал то, что частота на одной машине не может соответствовать частоте на другой. Моя большая ошибка! Код выполнился прекрасно на 3 системах в моем доме, но когда я проверил его на машине моих друзей, игра работала ОЧЕНЬ быстро. Частота – говорит о том, как быстро часы обновляются. Хорошая вещь для слежки :).

 

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



 

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

 

Переменная performance_timer может быть равной или ИСТИНА или ЛОЖЬ. Если программа находит высокоточный таймер, переменная performance_timer будет равна ИСТИНА, и синхронизация использует высокоточный таймер (намного более точный, чем мультимедийный таймер). Если высокоточный таймер не найден, переменнаяperformance_timer будет равна ЛОЖЬ и мультимедийный таймер используется для синхронизации.



 

Последние 2 переменные - целые переменные по 64 бита, которые содержат время запуска высокоточного таймера и время, которое прошло с момента запуска высокоточного таймера.

 

Название этой структуры - " timer", как Вы можете увидеть внизу структуры. Если мы хотим знать частоту таймера, мы можем теперь проверить timer.frequency. Отлично!



 

struct                    // Создание структуры для информации о таймере

{

  __int64       frequency;                 // Частота таймера



  float         resolution;                // Точность таймера

  unsigned long mm_timer_start;            // Стартовое значение мультимедийного таймера

  unsigned long mm_timer_elapsed;          // Прошедшее время мультимедийного таймера

  bool          performance_timer;         // Использовать высокоточный таймер?

  __int64       performance_timer_start;   // Стартовое значение высокоточного таймера

  __int64       performance_timer_elapsed; // Прошедшее время высокоточного таймера

} timer;                  // Структура по имени таймер

 

Следующая строка кода - наша таблица скорости. Объекты в игре будут двигаться с разной скоростью в зависимости от значения adjust (коррекции). Если adjust - 0, объекты будут перемещаться на один пиксель одновременно. Если значение adjust - 5, объекты переместят на 20 пикселей одновременно. Таким образом, увеличивая значениеadjust, скорость объектов увеличится, делая выполнение игры более быстрой на медленных компьютерах. Однако при больших значениях adjust игра будет выглядеть более дерганой.



 

Массив steps[] - только таблица для поиска. Если adjust равно 3, мы ищем число в позиции 3 массива steps[]. В позиции 0 хранится значение 1, в позиции 1 хранится значение 2, в позиции 2 хранится значение 4, и в позиции 3 хранится значение 5. Если adjust равно 3, наши объекты перемещались бы на 5 пикселей одновременно. Понятен смысл?

 

int    steps[6]={ 1, 2, 4, 5, 10, 20 }; // Значения шагов для работы



                                        // на медленных видеокартах

 

Затем мы создаем память для двух текстур. Мы загрузим фон сцены, и картинку для шрифта. Затем мы определяем переменную base, для списка отображения шрифта точно так же как, мы делали в других уроках со шрифтами. Наконец мы объявляем WndProc().



 

GLuint    texture[2];          // Память для текстур

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

 

LRESULT  CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Объявление для WndProc



 

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

 

Вначале сбросим все переменные структуры таймера в ноль. Это присвоит всем переменным в нашей структуре таймера значение ноль. После этого, мы проверим наличие высокоточного таймера. Здесь знак '!' означает НЕТ. Если таймер есть, то частота будет сохранена в timer.frequency.



 

Если нет высокоточного таймера, код между скобками будет выполнен. В первой строке переменнойperformance_timer присваивается ЛОЖЬ. Это говорит нашей программе, что нет никакого высокоточного счетчика. Во второй строке мы получаем стартовое значение для мультимедийного таймера от timeGetTime(). Мы задаемtimer.resolution в 0.001f, и timer.frequency к 1000. Поскольку еще не прошло время, мы присваиваем прошедшему времени время запуска.

 

void TimerInit(void) // Инициализация нашего таймера (Начали)



{

  memset(&timer, 0, sizeof(timer)); // Очистка нашей структуры

 

  // Проверим доступность высокоточного таймера



  // Если доступен, то частота таймера будет задана

  if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency))

  {

    // Нет высокоточного таймера



    timer.performance_timer  = FALSE;       // Установим флаг высокоточного таймера в ЛОЖЬ

    timer.mm_timer_start  = timeGetTime();  // Текущее время из timeGetTime()

    timer.resolution  = 1.0f/1000.0f;       // Точность равна 0.001f

    timer.frequency    = 1000;              // Частота равна 1000

    timer.mm_timer_elapsed  = timer.mm_timer_start; // Прошедшее время равно текущему

  }


 

Если есть высокоточный таймер, следующий код будет выполнен вместо этого. В первой строке захватывается значение запуска высокоточного таймера, и помещается в performance_timer_start. Затем мы присваиваем переменной performance_timer значение ИСТИНА так, чтобы наша программа знала, что есть доступный высокоточный таймер. После этого, мы, вычисляем точность таймера, используя частоту, которую мы получили, когда проверяли наличие высокоточного таймера в коде выше. Мы делим единицу на эту частоту, чтобы получить точность. Последнее что мы сделаем, будет присвоение прошедшему времени значения стартового времени.

 

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



 

  else


  {

    // Высокоточный таймер доступен, используем его вместо мультимедийного таймера

    // Взять текущее время и сохранить его в performance_timer_start

    QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start);

    timer.performance_timer    = TRUE;        // Установить флаг наличия таймера в TRUE

    // Вычислить точность таймера, используя частоту

    timer.resolution    = (float) (((double)1.0f)/((double)timer.frequency));

    // Присвоить прошедшему времени текущее время

    timer.performance_timer_elapsed  = timer.performance_timer_start;

  }


}

 

Раздел кода выше инициализирует таймер. Код ниже читает таймер и возвращает время, которое прошло в миллисекундах.



 

Вначале определим переменную в 64 бита под именем time. Мы будем использовать эту переменную, чтобы получить текущее время. Следующая строка проверяет, доступен ли высокоточный таймер. Если performance_timerравен ИСТИНА, то код после условия выполнится.

 

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



 

Если мы не используем высокоточный таймер, код после инструкции else будет выполнен. Там в значительной степени делается тоже самое. Мы захватываем текущее время с помощью timeGetTime() и вычитаем из него наше значение при запуске. Мы умножаем на точность и затем на 1000, чтобы преобразовать результат из секунд в миллисекунды.

 

float TimerGetTime()           // Взять время в миллисекундах



{

  __int64 time;                // time содержит 64 бита

 

  if (timer.performance_timer) // Есть высокоточный таймер?



  {

    // Захват текущего значения высокоточного таймера

    QueryPerformanceCounter((LARGE_INTEGER *) &time);

    // Вернем текущее время минус начальное время, умноженное на точность и 1000 (для миллисекунд)

    return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.0f;

  }


  else

  {


    // Вернем текущее время минус начальное время, умноженное на точность и 1000 (для миллисекунд)

    return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.0f;

  }

}

 



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

 

Левый верхний угол экрана – это 0 по оси X и 0 по оси Y. Поэтому, устанавливая player.x в 0, мы помещаем игрока на левый край экрана. Устанавливая player.y в 0, мы помещаем нашего игрока на верхний край экрана.



 

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

 

void ResetObjects(void)      // Сброс Игрока и Противников



{

  player.x=0;                // Сброс позиции игрока X на левый край экрана

  player.y=0;                // Сброс позиции игрока Y на верх экрана

  player.fx=0;               // Установим точную позиции X

  player.fy=0;               // Установим точную позиции Y

 

Далее мы даем противникам случайное начальное размещение. Количество противников, выведенное на экран, будет равно текущему значению уровня, умноженному на текущую стадию. Помните, что максимальное значение уровня может равняться трем, и максимальное число стадий в уровне тоже трем. Так что мы можем иметь максимум 9 противников.



 

Чтобы быть уверенными, что мы даем всем видимым противникам новую позицию, мы организуем цикл по всем видимым противникам (стадия, умноженная на уровень). Мы устанавливаем для каждого противника позицию xравную 5 плюс случайное значение от 0 до 5 (максимальное случайное значение может быть всегда число, которое Вы зададите минус 1). Поэтому враги могут появляться на сетке, где-то от 5 до 10. Затем мы даем врагу случайное значение по оси Y от 0 до 10.

 

Мы не хотим, чтобы враг двигался из старой позиции к новой случайной позиции, поэтому мы должны быть уверены, что точные значения по x (fx) и y (fy) равны значениям по x и y, умноженные на ширину и высоту каждой ячейки на экране. Каждая ячейка имеет ширину 60 и высоту 40.



 

  for (loop1=0; loop1<(stage*level); loop1++) // Цикл по всем противникам

  {

    enemy[loop1].x=5+rand()%6;                // Выбор случайной позиции X



    enemy[loop1].y=rand()%11;                 // Выбор случайной позиции Y

    enemy[loop1].fx=enemy[loop1].x*60;        // Установка точной X

    enemy[loop1].fy=enemy[loop1].y*40;        // Установка точной Y

  }


}

 

Код AUX_RGBImageRec не изменился, поэтому я опускаю его. В LoadGLTextures() мы загрузим две наши текстуры. Сначала картинку шрифта (Font.bmp) и затем фоновое изображение (Image.bmp). Мы конвертируем оба изображения в текстуры, которые мы можем использовать в нашей игре. После того, как мы построили текстуры, мы очищаем память, удаляя растровую информацию. Здесь нет ничего нового. Если Вы читали другие уроки, Вы не должны иметь никаких проблем, в понимании кода.



 

int LoadGLTextures()                       // Загрузка растра и конвертирование его в текстуры

{

  int Status=FALSE;                        // Индикатор статуса



  AUX_RGBImageRec *TextureImage[2];        // Память для текстур

  memset(TextureImage,0,sizeof(void *)*2); // Указатель в NULL

 

  if   ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&  // Загрузка фонта



     (TextureImage[1]=LoadBMP("Data/Image.bmp")))      // Загрузка фона

  {


    Status=TRUE;                           // Установка статуса в TRUE

 

    glGenTextures(2, &texture[0]);         // Создание текстуры



 

    for (loop1=0; loop1<2; loop1++)        // Цикл из 2 текстур

    {

      glBindTexture(GL_TEXTURE_2D, texture[loop1]);



      glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop1]->sizeX, TextureImage[loop1]->sizeY,

        0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop1]->data);

      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

    }

 

    for (loop1=0; loop1<2; loop1++)        // Цикл из 2 текстур



    {

      if (TextureImage[loop1])             // Если текстура существует

      {

        if (TextureImage[loop1]->data)     // Если изображение текстуры существует



        {

          free(TextureImage[loop1]->data); // Освободить память текстуры

        }

        free(TextureImage[loop1]);         // Освободить структуру изображения

      }

    }


  }

  return Status;                           // Возврат статуса

}

 

Код ниже создает список отображения шрифта. Я уже делал урок со шрифтом из текстуры. Весь код, делит изображение Font.bmp на 16 x 16 ячеек (256 символов). Каждая ячейка размером 16x16 станет символом. Поскольку я задал ось Y направленную вверх, поэтому, чтобы происходил сдвиг вниз, а не вверх, необходимо вычесть наши значения по оси Y из значения 1.0f. Иначе символы будут инвертированны :). Если Вы не понимаете то, что происходит, возвратитесь, и читайте урок по шрифтам из текстур.



 

GLvoid BuildFont(GLvoid)                     // Создаем список отображения нашего шрифта

{

  base=glGenLists(256);                      // Создаем списки



  glBindTexture(GL_TEXTURE_2D, texture[0]);  // Выбираем текстуру шрифта

  for (loop1=0; loop1<256; loop1++)          // Цикл по всем 256 спискам

  {

    float cx=float(loop1%16)/16.0f;          // X координата текущего символа



    float cy=float(loop1/16)/16.0f;          // Y координата текущего символа

 

    glNewList(base+loop1,GL_COMPILE);              // Начинаем делать список



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

        glTexCoord2f(cx,1.0f-cy-0.0625f);          // Точка в текстуре (Левая нижняя)

        glVertex2d(0,16);        // Координаты вершины (Левая нижняя)

        glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);  // Точка на текстуре (Правая нижняя)

        glVertex2i(16,16);       // Координаты вершины (Правая нижняя)

        glTexCoord2f(cx+0.0625f,1.0f-cy);          // Точка текстуры (Верхняя правая)

        glVertex2i(16,0);        // Координаты вершины (Верхняя правая)

        glTexCoord2f(cx,1.0f-cy);                  // Точка текстуры (Верхняя левая)

        glVertex2i(0,0);         // Координаты вершины (Верхняя левая)

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

      glTranslated(15,0,0);      // Двигаемся вправо от символа

    glEndList();                 // Заканчиваем создавать список отображения

  }                              // Цикл для создания всех 256 символов

}

 



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

 

GLvoid KillFont(GLvoid)               // Удаляем шрифт из памяти



{

  glDeleteLists(base,256);            // Удаляем все 256 списков отображения

}

 

Код 


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


Поделитесь с Вашими друзьями:
1   ...   6   7   8   9   10   11   12   13   ...   27


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

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