Если Вы читаете данную статью, то Вас заинтересовал движок игры - виртуальный конструктор "Творец лабиринтов" (саму игру и её исходный код Вы можете скачать прямо с гугл-диска). Движок написан на достаточно популярном языке программирования C++ в среде Visual C++. Графический движок реализован на базе платформы OpenGL.
OpenGL (Open Graphics Library) — спецификация, определяющая независимый от языка программирования платформонезависимый программный интерфейс для написания приложений, использующих двумерную и трёхмерную компьютерную графику. Включает более 300 функций для рисования сложных трёхмерных сцен из простых примитивов.
Итак вооружайтесь Visual C++ открывайте проект программы (файл "dvigok.dsp") и вперед постигать самую увлекательную науку, позволяющую почувствовать себя творцом - программирование!
В этой статье я разобью программный код на секции и опишу каждую из них.
Чтобы создать свое виртуальное пространство мы прежде всего должны проинициализировать графику с помощью OpenGL и описать работу с устройствами ввода-вывода информации (Этому будет посвящена отдельная статья, обучающая С++ и OpenGL)...
Открываем основной файл программы - это "proj0000.cpp". Первое с чего начинается любая программа на С++ - это подключение библиотек, описывающих классы используемых нами в программе функций:
| #include <windows.h> // Заголовочный файл для Windows #include <gl\gl.h> // Заголовочный файл для OpenGL32 библиотеки #include <gl\glu.h> // Заголовочный файл для GLu32 библиотеки #include <math.h> #include <commctrl.h> #include <gl\glaux.h> // Заголовочный файл для GLaux библиотеки #include "Top.h" #include <windowsx.h> #include <stdlib.h> #include <conio.h> #include <string.h> #include <io.h> #include <stdio.h> #include <stdarg.h> // заголовочный файл для манипуляций с переменными аргументами #include "glModelUt.h" |
Используются стандартные библиотеки Visual C++ кроме "Top.h" и "glModelUt.h" . Первая из них написана нами и содержит С++ код некоторых 3d объектов, которые, для примера, не реализованы в виде подгружаемых 3d моделей. То есть Вы можете на их примере понять как можно не прибегая к использованию сложных сторонних программ типа 3dsMAX создавать программно простые модели используя только команды OpenGL.
Но об этом позже... Благодаря библиотеке стороннего разработчика "glModelUt.h" возможно подгружать 3d модели формата "*.ASE" из файлов, созданных в 3d редакторах (её.рассматривать мы не будем).
Следующим этапом как и в любой программе с++ происходит объявление переменных:
| GLuint list_num; GLuint list1_num; GLuint list2_num; GLuint list3_num; int b; int fg; int zem=1; int lab=1; int speed=0; int l; int q=1; bool gp; // G Нажата? ( Новое ) GLuint filter; // Используемый фильтр для текстур GLuint fogMode[]= { GL_EXP, GL_EXP2, GL_LINEAR }; // Хранит три типа тумана GLuint fogfilter; // Тип используемого тумана GLfloat fogColor[4]= {0.5f, 0.5f, 0.5f, 1.0f}; // Цвет тумана int vihod=0; bool blend; // Смешивание НЕТ/ДА? (НОВОЕ) bool bp; // B Нажата? ( Новое ) int labirint[12][12]; static HGLRC hRC; // Постоянный контекст рендеринга static HDC hDC; // Приватный контекст устройства GDI GLfloat xrot; // Вращение X GLfloat yrot; // Y GLfloat zrot; // Z int Width, Height; // Ширина и высота экрана в пикселах, необходима для процедуры управления float VertAngle, HorizAngle; // Вертикальный и горизонтальный угол обзора float CameraX,CameraY,CameraZ; // Текущее положение точки наблюдателя float pX=-k+1,pY=8*k,zX=0,zY=7*k; int I=0,J=7; BOOL keys[256]; // Массив для процедуры обработки клавиатуры GLuint texture[25]; // Память для пяти наших текстур GLuint loop; // Общая переменная цикла |
Далее описываются используемые функции. Первая из них - это функция чтения из файла "Map.hryx" матрицы лабиринта в двумерный массив и сохранение параметров игры в соответствующие переменные:
| void karta(void) { FILE *fModel; char szString[128]; fModel=fopen("Map.hryx","rb"); int i=0; for(i=0;i<12;i++) { int tp; FindStr("MAP",fModel); sscanf(szString,"MAP %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",&(labirint[i][0]),&(labirint[i][1]),&(labirint[i][2]),&(labirint[i][3]),&(labirint[i][4]),&(labirint[i][5]),&(labirint[i][6]),&(labirint[i][7]),&(labirint[i][8]),&(labirint[i][9]),&(labirint[i][10]),&(labirint[i][11])); } FindStr("FOG",fModel); //cчитать из файла состояние тумана sscanf(szString,"FOG %d",&fg); FindStr("FOGFILTER",fModel); //cчитать из файла тип тумана sscanf(szString,"FOGFILTER %d",&fogfilter); FindStr("ZEM",fModel); //cчитать из файла тип текстуры земли sscanf(szString,"ZEM %d",&zem); FindStr("LAB",fModel); //cчитать из файла тип текстуры лабиринта sscanf(szString,"LAB %d",&lab); FindStr("SPEED",fModel); //cчитать из файла скорость игры sscanf(szString,"SPEED %d",&speed); fclose(fModel); } |
Дополняя эту функцию Вы можете сделать доступными к редактированию в файле "Map.hryx" игры любую переменную, какую пожелаете!
Далее идут две функции: 1) позволяет открыть и прочитать файлы с картинками (с расширением " *.BMP"); 2) используя первую функцию загружает картинки из файлов, конвертирует их в текстуры и сохраняет их в массив типа: texture[номер текстуры].
| AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image { FILE *File=NULL; // File Handle if (!Filename) // Make Sure A Filename Was Given { return NULL; // If Not Return NULL } File=fopen(Filename,"r"); // Check To See If The File Exists if (File) // Does The File Exist? { fclose(File); // Close The Handle return auxDIBImageLoad(Filename); // Load The Bitmap And Return A Pointer } return NULL; // If Load Failed Return NULL } int LoadGLTextures() // Загрузка картинки и конвертирование в текстуру { int Status=FALSE; // Индикатор состояния AUX_RGBImageRec *TextureImage[25]; // Создать место для текстуры memset(TextureImage,0,sizeof(void *)*25); // Установить указатель в NULL if ((TextureImage[0]=LoadBMP("textures//0.bmp")) && // Текстура эмблемы (TextureImage[1]=LoadBMP("textures//1.bmp")) && // Первая маска (TextureImage[2]=LoadBMP("textures//SKY_0000.bmp")) && // Первое изображение (TextureImage[3]=LoadBMP("textures//SKY_0001.bmp")) && // Вторая маска (TextureImage[4]=LoadBMP("textures//SKY_0002.bmp")) && (TextureImage[5]=LoadBMP("textures//SKY_0003.bmp")) && (TextureImage[6]=LoadBMP("textures//SKY_0004.bmp")) && (TextureImage[7]=LoadBMP("textures//SKY_0005.bmp")) && (TextureImage[8]=LoadBMP("textures//8.bmp")) && (TextureImage[9]=LoadBMP("textures//9.bmp")) && (TextureImage[10]=LoadBMP("textures//10.bmp")) && (TextureImage[11]=LoadBMP("textures//11.bmp")) && (TextureImage[12]=LoadBMP("textures//menu.bmp")) && (TextureImage[13]=LoadBMP("textures//ogon//flame1.bmp")) && (TextureImage[14]=LoadBMP("textures//ogon//flame2.bmp")) && (TextureImage[15]=LoadBMP("textures//ogon//flame3.bmp")) && (TextureImage[16]=LoadBMP("textures//ogon//flame4.bmp")) && (TextureImage[17]=LoadBMP("textures//ogon//flame5.bmp")) && (TextureImage[18]=LoadBMP("textures//ogon//flame6.bmp")) && (TextureImage[19]=LoadBMP("textures//ogon//flame7.bmp")) && (TextureImage[20]=LoadBMP("textures//ogon//flame8.bmp")) && (TextureImage[21]=LoadBMP("textures//Font.bmp")) && (TextureImage[22]=LoadBMP("textures//Mask1.bmp")) && (TextureImage[23]=LoadBMP("textures//Untitled.bmp")) && (TextureImage[24]=LoadBMP("textures//Image1.bmp"))) { Status=TRUE; // Задать статус в TRUE glGenTextures(25, &texture[0]); // Создать 25 текстур for (loop=0; loop<25; loop++) // Цикл по всем текстурам { glBindTexture(GL_TEXTURE_2D, texture[loop]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data); } } for (loop=0; loop<25; loop++) // Цикл по всем пяти текстурам { if (TextureImage[loop]) // Если текстура существуют { if (TextureImage[loop]->data) // Если изображение текстуры существует { free(TextureImage[loop]->data); // Освободить память изображения } free(TextureImage[loop]); // Освободить структуру изображения } } return Status; // Возвращаем статус } |
Следующие функции позволяют использовать в программе таблицу шрифтов, загружаемую из графического файла. В нашем случае загружается таблица с английской раскладкой букв, но Вы можете попробовать загрузить русские шрифты и делать надписи на экране по русски.
Далее идет функция инициализации окна OpenGL, где устанавливаются его основные свойства и параметры:
| GLvoid InitGL(GLsizei Width, GLsizei Height) // Вызвать после создания окна GL { LoadGLTextures(); glLoadModel("model//sample.ase",&list_num); glLoadModel("model//sample1.ase",&list1_num); glLoadModel("model//sample2.ase",&list2_num); glLoadModel("model//hryx.ase",&list3_num); //высота VertAngle = 90.0; HorizAngle = 0.0; glColor4f(1.0f,1.0f,1.0f,0.5f); // Полная яркость, 50% альфа (НОВОЕ) glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Функция смешивания для непрозрачности, // Изменить для S режим генерации текстур на "сферическое наложение" ( Новое ) glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // Изменить для T режим генерации текстур на "сферическое наложение" ( Новое ) glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); BuildFont(); // Создаем шрифт glEnable(GL_TEXTURE_2D); // Разрешение наложение текстуры glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Очистка экрана в черный цвет glClearDepth(1.0); // Разрешить очистку буфера глубины glDepthFunc(GL_LESS); // Тип теста глубины glEnable(GL_DEPTH_TEST);// разрешить тест глубины glShadeModel(GL_SMOOTH);// запрретить /разрешить/ плавное цветовое сглаживание glMatrixMode(GL_PROJECTION);// Выбор матрицы проекции glLoadIdentity(); // Сброс матрицы проекции gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); // Вычислить соотношение геометрических размеров для окна glMatrixMode(GL_MODELVIEW);// Выбор матрицы просмотра модели } |
Дальнейший код описывает расчет позиций курсора и соответствeующую ориентацию наблюдателя в пространстве OpenGL, а также описывает изменение местоположения наблюдателя в пространстве используя соответствующие клавиши. Т.е. функция позволяет нам осматриваться и перемещаться по нашему лабиринту:
| void HandleControls() { float PI = 3.1415926535; static long xCurPosOld, yCurPosOld; POINT Point; GetCursorPos(&Point); float Speed=0.07f; VertAngle+= (yCurPosOld-Point.y)*0.1f; HorizAngle -= (xCurPosOld-Point.x)*0.1f; if(VertAngle< 0.0) VertAngle= 0.0; if(VertAngle> 180.0) VertAngle= 180.0; if(HorizAngle< 0.0) HorizAngle = 359.0; if(HorizAngle> 359.0) HorizAngle = 0.0; if( GetAsyncKeyState(81) < 0) //вверх { CameraZ-=0.05; } if( GetAsyncKeyState(90) < 0) //вниз { CameraZ+=0.05; } if( GetAsyncKeyState(87) < 0 || GetAsyncKeyState(VK_LBUTTON) < 0 || GetAsyncKeyState(VK_UP) < 0 ) //VK_UP { if(CameraX-Speed*sin(HorizAngle/180*PI)<zX)if(CameraX-Speed*sin(HorizAngle/180*PI)>=pX){ CameraX -= Speed*sin(HorizAngle/180*PI);}; if(CameraY-Speed*cos(HorizAngle/180*PI)<zY)if(CameraY-Speed*cos(HorizAngle/180*PI)>=pY){CameraY -= Speed*cos(HorizAngle/180*PI);}; } if( GetAsyncKeyState(83) < 0 || GetAsyncKeyState(VK_RBUTTON) < 0 || GetAsyncKeyState(VK_DOWN) < 0 ) //VK_DOWN { if(CameraX+Speed*sin(HorizAngle/180*PI)<zX)if(CameraX+Speed*sin(HorizAngle/180*PI)>=pX){ CameraX += Speed*sin(HorizAngle/180*PI);}; if(CameraY+Speed*cos(HorizAngle/180*PI)<zY)if(CameraY+Speed*cos(HorizAngle/180*PI)>=pY){CameraY += Speed*cos(HorizAngle/180*PI);}; } if( GetAsyncKeyState(68) < 0 || GetAsyncKeyState(VK_RIGHT) < 0 ) //VK_RIGHT { if(CameraX+Speed*sin((HorizAngle-90)/180*PI)<zX)if(CameraX+Speed*sin((HorizAngle-90)/180*PI)>=pX){CameraX += Speed*sin((HorizAngle-90)/180*PI);}; if(CameraY+Speed*cos((HorizAngle-90)/180*PI)<zY)if(CameraY+Speed*cos((HorizAngle-90)/180*PI)>=pY){CameraY += Speed*cos((HorizAngle-90)/180*PI);}; } if( GetAsyncKeyState(65) < 0 || GetAsyncKeyState(VK_LEFT) < 0) //VK_LEFT { if(CameraX+Speed*sin((HorizAngle+90)/180*PI)<zX)if(CameraX+Speed*sin((HorizAngle+90)/180*PI)>=pX){CameraX += Speed*sin((HorizAngle+90)/180*PI);}; if(CameraY+Speed*cos((HorizAngle+90)/180*PI)<zY)if(CameraY+Speed*cos((HorizAngle+90)/180*PI)>=pY){CameraY += Speed*cos((HorizAngle+90)/180*PI);}; } if(Point.x==Width-1) { Point.x = 1; SetCursorPos(Point.x,Point.y); } if(Point.y==Height-1) { Point.y = 1; SetCursorPos(Point.x,Point.y); } if(Point.x==0) { Point.x = Width-2; SetCursorPos(Point.x,Point.y); } if(Point.y==0) { Point.y = Height-2; SetCursorPos(Point.x,Point.y); } xCurPosOld = Point.x; yCurPosOld = Point.y; } |
Следующая функция задает размеры нашего лабиринта и производит расчет центров каждой ячейки матрицы, для определения места установки той или иной 3d модели:
| void center_cub_labirint() { int i,j; //расчет центров кубов for(i=0;i<=11;i++) for(j=0;j<=11;j++) {X[i][j]=(i*k+(i+1)*k)/2; Y[i][j]=(j*k+(j+1)*k)/2;}; } |
И, наконец, следующая функция объединяет всю графическую часть программы (все функции, связанные с созданием OpenGL сцены) воедино и осуществляет отрисовку нашего виртуального мира кадр за кадром. Одно выполнение этой функции рисует один кадр нашей игры. Учитывая что данная функция выполняется многократно (так как она вызывается в цикле "While" основной "материнской" функции WinMain()), картинка на экране изменяется, отражая наше перемещение, повороты и т.п. - эффект кинопленки:
| GLvoid DrawGLScene(GLvoid) { HandleControls(); // Будем очищать экран, заполняя его цветом тумана. ( Изменено ) //glEnable(GL_FOG); /*if (keys['B'] && !bp) */ if (fg == 1) {glClearColor(0.5f,0.5f,0.5f,1.0f); bp=TRUE; glEnable(GL_FOG); } /*if (!keys['B'])*/ if (fg == 0) // ’B’ отжата? { bp=FALSE;// тогда bp возвращает ложь glDisable(GL_FOG); } // Включает туман (GL_FOG) glFogi(GL_FOG_MODE, fogMode[fogfilter]);// Выбираем тип тумана glFogfv(GL_FOG_COLOR, fogColor); // Устанавливаем цвет тумана glFogf(GL_FOG_DENSITY, 0.15f); // Насколько густым будет туман glHint(GL_FOG_HINT, GL_NICEST); // Вспомогательная установка тумана glFogf(GL_FOG_START, 10.0f); // Глубина, с которой начинается туман glFogf(GL_FOG_END, 11.0f); // Глубина, где туман заканчивается. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glRotated(-VertAngle, 1.0, 0.0, 0.0); glRotated(HorizAngle, 0.0, 0.0, 1.0); //////////////////////////////////////////////////////////////////////////////// if(vihod==0){ if (CameraX<(-X[I][J]-k/2))I=I+1; if (CameraX>=(-X[I][J]+k/2))I=I-1; if (CameraY<(-Y[I][J]-k/2))J=J+1; if (CameraY>=(-Y[I][J]+k/2))J=J-1; if(labirint[I+1][J]==1)pX=-X[I][J]-k/2+0.5; else pX=-X[I+1][J]; if(labirint[I-1][J]==1)zX=-X[I][J]+k/2-0.5; else zX=-X[I-1][J]; if(labirint[I][J+1]==1)pY=-Y[I][J]-k/2+0.5; else pY=-Y[I][J+1]; if(labirint[I][J-1]==1)zY=-Y[I][J]+k/2-0.5; else zY=-Y[I][J-1]; if(labirint[I+1][J]==5)vihod=1; if(labirint[I-1][J]==5)vihod=1; if(labirint[I][J+1]==5)vihod=1; if(labirint[I][J-1]==5)vihod=1; } glTranslatef(CameraX,CameraY,CameraZ); //////////////////////////////////////////////////////////////////////////////// GLUquadricObj *quadObj; quadObj = gluNewQuadric(); // создаем новый объект int i,j ; GLfloat front_color[] = {1,1,1,1}; glPushMatrix(); glMaterialfv(GL_FRONT , GL_SPECULAR, front_color); // земля Top2(quadObj,texture[zem]); //прорисовка лабиринта glEnable(GL_TEXTURE_2D); glTranslatef(-0.5*k,-0.5*k,0); for(i=0;i<=11;i++){glTranslatef(k,0,0); for(j=0;j<=11;j++){glTranslatef(0,k,0); if(labirint[i][j]==1) { glBindTexture(GL_TEXTURE_2D, texture[lab] ); glCallList(list_num); } if(labirint[i][j]==2) //зеркальный шар { glEnable(GL_TEXTURE_GEN_S); // Включим генерацию координат текстуры для S ( НОВОЕ ) glEnable(GL_TEXTURE_GEN_T); // Включим генерацию координат текстуры для T ( НОВОЕ ) glBindTexture(GL_TEXTURE_2D, texture[1] ); glCallList(list1_num); glDisable(GL_TEXTURE_GEN_S); // Отключим генерацию текстурных координат ( НОВОЕ ) glDisable(GL_TEXTURE_GEN_T); // Отключим генерацию текстурных координат ( НОВОЕ ) } if(labirint[i][j]==3) { glBindTexture(GL_TEXTURE_2D, texture[11] ); glCallList(list2_num); } if(labirint[i][j]==5) { glBindTexture(GL_TEXTURE_2D, texture[23] ); glCallList(list3_num); } if(labirint[i][j]==4) //ловушка { glEnable(GL_TEXTURE_GEN_S); // Включим генерацию координат текстуры для S ( НОВОЕ ) glEnable(GL_TEXTURE_GEN_T); // Включим генерацию координат текстуры для T ( НОВОЕ ) glBindTexture(GL_TEXTURE_2D, texture[lab] ); glCallList(list_num); glDisable(GL_TEXTURE_GEN_S); // Отключим генерацию текстурных координат ( НОВОЕ ) glDisable(GL_TEXTURE_GEN_T); // Отключим генерацию текстурных координат ( НОВОЕ ) /* glEnable(GL_BLEND); // Разрешение смешивания glDisable(GL_DEPTH_TEST); glBlendFunc(GL_DST_COLOR,GL_ZERO); glBindTexture(GL_TEXTURE_2D, texture[22] ); glCallList(list_num); glBlendFunc(GL_ONE, GL_ONE); glBindTexture(GL_TEXTURE_2D, texture[24] ); glCallList(list_num); glEnable(GL_DEPTH_TEST); // Разрешение теста глубины glDisable(GL_BLEND); // Запрещение смешивания */ } if(j==11)glTranslatef(0,-12*k,0); }}; glFlush(); glDisable(GL_TEXTURE_2D); //glEnable(GL_DEPTH_TEST);//обработать все непрозрачные поверхности glPopMatrix(); glPushMatrix(); glEnable(GL_BLEND); // Включаем смешивание glEnable(GL_TEXTURE_2D); glColor3f(0.5,1.0,1.0); glPrint(10,10,"HRYX production",1); glPrint(10,460,(char *)glGetString(GL_RENDERER),1); if (vihod==1)glPrint(200,250,"-= EXIT =-",1); if (vihod==1)glPrint(200,200,". Congratulation!!! press Esk...",1); glTranslatef(k/2,(7*k+k/2),1); glRotatef(l,0,0,1); gluQuadricTexture(quadObj, GL_TRUE); glBindTexture(GL_TEXTURE_2D,texture[10]); glColor4f(1,1,1,0.5); gluSphere(quadObj, 1, 30, 30); // рисуем сферуw glPushMatrix(); glRotatef(l*2,0,0,-1); glColor4f(1,1,1,0.1); gluSphere(quadObj, 1.5, 30, 30); // рисуем сферу glPopMatrix(); glDisable(GL_BLEND); // Выключаем смешивание l=l+2; gluDeleteQuadric(quadObj); auxSwapBuffers(); glPopMatrix(); //////////////////////////////////////////////////////////////////////////////// } |
Ну вот, собственно и вся структура программы. Развивайте её и модернизируйте. В этой статье я не ставил задачу описать конкретные команды языка С++ и OpenGL. Этому будут посвящены следующие статьи, которые позволят Вам изучить базовые приемы программирования Open GL и разобраться в коде данной игры.
Комментариев нет:
Отправить комментарий
Примечание. Отправлять комментарии могут только участники этого блога.