После длительного застоя, за который я прошу прощения, мы продолжаем. Заправляйтесь хорошенько, сегодняшний перелёт обещает быть длинным:- Напишем класс профилей игроков, для хранения параметров.
- Класс системный, для связи окна и OpenGL контекста.
- Дадим зелёный свет на рождение солюшена, который будет оборачивать весь наш движок; составим удобную структуру каталогов.
Хочу сразу заметить, что сейчас и далее я буду приводить много «черновых» набросков: т.е. варианты реализации, которые заведомо немаксимально эффективны, но на текущем этапе можно оставить «как есть», а позже будем возвращаться и править. Я призываю всех и каждого давать свои советы и участвовать в обсуждении реализаций, потому что только в споре рождается истина.
Строим кузню для ковки нашего Глемдринга Сейчас мы создадим рабочее место для нашего движка: чтобы было удобно тестить и дебажить. Сначала сформируем дерево каталогов. Выберете себе место на диске, где будет заложен наш проект. В этой папке сгенерируйте след. каталоги: 0) inc - тут будут лежать все заголовочные файлы проектов. 1) out - сюда будет бросаться результат всей компиляции. 2) libs - здесь все скомпилированные библиотеки залегать будут. 3) library - тут проекты всех библиотек. 4) prj - здесь проекты, которые будут использовать ту, или иную часть движка. Что за библиотеки? Мы разобьём весь движок на составные части: класс общего назначения, классы системного типа, классы работы с геометрией, классы работы с миром и объектами. Ну и там ещё по ходу дела будем добавлять другие Так вот, каждая из этих частей будет отдельной библиотекой (*.lib), которую можно будет использовать в любом проекте, не прибегая к подключению файлов описания, а используя только заголовочные файлы и либы. Думаю тут вообще нет смысла разжёвывать, читающие и так это должны понимать.
Moving on!
Итак, у вас на руках запущенный Visual Studio (далее VS): 0) File -> New -> Project. 1) Project types: Other project types -> Visual Studio Solutions -> Blank Solution. Имя указываем любое, а местоположение солюшена - наша папка/library. 2) Создаём в солюшене разделы(Add -> New Solution Folder): Projects, Library. В первом будут лежать проекты, которые юзают наш двигл, а во втором сам двигл. 3) Теперь в solution explorer добавляем Add -> New Project... 4) Visual C++ -> Win32 application. Местоположение наша папка/library, имя doCommon. 5) В выпавших предварительных параметрах создаваемого проекта выбираем Static Library и снимаем галку с Precompiled header. 6) Вот у нас девственный проект, который мы сразу же перетаскиваем в раздел Library. 7) Идём в наша папка/library/doCommon и создаём тама папку cpp Этот проект будет содержать в себе классы общего назначения: aAux, aVector, aString, aList. Что мы сейчас делаем? Помните, в первой части я говорил что мы будем держать заголовочные файлы в папке inc, а файлы описаний в cpp? Сейчас возьмите заголовочные файлы перечисленных выше классов и перетащите их в наша папка/inc, а файлы описания в наша папка/library/doCommon/cpp. Вернитесь в VS, и в проекте doCommon создаёте подпапки, каждая из которых будет носить название класса: aAux, aList и т.п. В каждую из этих папок добавьте файл описания и файл реализации соответствующего класса. 8) Последний шаг - это создание файла, который бы позволял нормально работать с нашей либой. Добавляем новый заголовочный файл doCommon.h, в проект, ложем его в наша папка/inc. Тело у него примерно такое:
| Код | //=============================================================================
#ifndef DOCOMMON_H #define DOCOMMON_H
//=============================================================================
#include "aAux.h" #include "aList.h" #include "aVector.h" #include "aString.h"
//=============================================================================
#pragma comment(lib, "../../libs/doCommon.lib")
//=============================================================================
#endif
//============================================================================= |
Теперь, если мы захотим использовать один из наших классов, нам нужно будет подключить эту библиотеку для проекта.
Дальше маза: оттопырить водолаза!
0) Заходим в свойства пока единственного проекта: Configuration Properties -> Librarian -> General. 1) Значение Output file меняем на ..\..\libs\$(ProjectName).lib. Ах да, не забывайте переключить Configuration с Active(Debug) на All Configurations. 2) Теперь попробуйте скомпилировать... 3) Упс? 4) Или не упс? 5) Если 3 - то всё нормально и вам самостоятельно надо только правильно подправить пути в файлах описаний к файлам заголовочным каждого класса. 6) Если 4 - то вы уже это сделали сразу. Молоток!
Теперь что получается? Вся вторичная хренотень лежит у нас в папке с проектом, а чистая откомпиленая статик либа в наша папка/libs Самоё время её по-пежить! Давайте добавим тестовый консольный проект, который будет первым, кто начнёт юзать начало фундамента нашего "движка".
0) В раздел солюшена Projects добавляем консольный Win32 проект, все настройки при создании как обычно, акромя пути куда его ложить: наша папка/prj 1) В папке проекта заводим также папку cpp, куда добавляем файл описания, где будет лежать код консольного приложения. 2) Тыкаем правой кнопкой на нашем консольном проекте в "solution explorer" и "Set as StartUp Project" - теперь, при запуске нашего солюшена (например, по F5) будет стартовать консольное приложение. 3) В том же "solution explorer" правой кнопкой по опять консольному проекту и "Project Dependencies..." - тут ставим галку на нашем "doCommon". Таким образом мы установили зависимость нашего консольного проекта от проекта "doCommon". Зачем? Затем что это определяет последовательность компиляции и теперь VS знает, что сначала надо будет собрать "doCommon", а уже затем консольный проект. И соответственно, если появятся изменения в "doCommon", то он пересоберёт и консольный проект. 4) Идём свойства консольного проекта Configurations properties -> Linker -> General -> Output File меняем на ..\..\out\$(ProjectName).exe. Да, и не забываем переключить Configuration с Active(Debug) на All Configurations. Теперь идём в наш консольный проект, в его ед. файл описания и заносим исходный код:
| Код | //=============================================================================
#include <stdio.h> #include "../../../inc/doCommon.h"
//=============================================================================
int main(int argc, char** argv) { aString str("Good morning!\n");
printf(str.asGetStringChar()); return 0; }
//============================================================================= |
Аминь!
Всегда помните!- При добавлении новых проектов настраивать их.
- Верно указывать пути.
- Верно укладывать файлы.
- Не забывайте про зависимости проектов.
- Регулируйте стартовый проект, когда работаете над чем-то.
Домашнее задание по этому разделу: как вы, наверное, заметили в наша папка/out, кроме конечного результата(*.exe) было брошено ещё два файла, с расширениями *.ilk и *.pdb: сделайте так, чтобы их не было. Далее мы будем всё делать на основе этого солюшена и именно таким образом. Разберитесь со всем! Что непонятно - спрашивайте!
Начнём с профилей игроков. Частенько за одной машиной играет несколько человек и у каждого, конечно, свои настройки управления, графики, звука. Лично меня всегда накаляли игры, где не было возможности хоть как-то быстро сохранять/загружать настройки, а приходилось после брата всё перенастраивать. Поэтому наша задача в данный момент написать класс, который по сути был бы контейнером всех системных параметров/настроек клавиатуры/мыши. В идеале он все данные будет получать из файлов, которые будут лежать в определённой папке, но на данный момент мы создадим некоторый набор минимума, чтобы просто инжектить класс в нашу работу, а уже потом мочь наращивать мясо на его костях. aProfile.h
| Код | //=============================================================================
#ifndef APROFILE_H #define APROFILE_H
//=============================================================================
#include "../inc/aString.h"
//=============================================================================
enum keyActions { KA_NONE = -128, // no actions KA_CANCEL, KA_STRAFERIGHT, KA_STRAFELEFT, KA_MOVEFORWARD, KA_MOVEBACKWARD, KA_CROUCH, KA_JUMP, };
//============================================================================= |
Это перечисление содержит набор действий, которые будут вешаться на клавиши. Сейчас мы их использовать не будем, но когда прикрутим камеру, то обязательно найдём им применение 
| Код | class aKeys { public: aKeys() { act = NONE; key = -1; }
public: keyActions act; int key; };
//============================================================================= |
Класс клавиши, чтобы хранить какой кнопе какое действие соответствует.
| Код | class aProfile { private: static const unsigned int keysCount = 128;
public: aProfile(); keyActions apGetAction(int key)const; ~aProfile();
private: aString caption; aKeys keys[keysCount]; };
//=============================================================================
#endif
//============================================================================= |
Основной класс профиля. “caption” – ник игрока Единственную фукнцию опишем далее, в реализации aProfile.cpp:
| Код | //=============================================================================
#include <windows.h> #include "../../../../inc/aProfile.h"
//=============================================================================
aProfile::aProfile() { caption = L"Default"; //keys sets; keys[0].key = 0/*найдите код клавиши escape*/; keys[0].act = KA_CANCEL; keys[1].key = 0/*Так же дайте назначение всем действиям*/; keys[1].act = KA_STRAFELEFT; keys[2].key = 0/*Так же дайте назначение всем действиям*/; keys[2].act = KA_STRAFERIGHT; keys[3].key = 0/*Так же дайте назначение всем действиям*/; keys[3].act = KA_MOVEFORWARD; keys[4].key = 0/*Так же дайте назначение всем действиям*/; keys[4].act = KA_MOVEBACKWARD; keys[5].key = 0/*Так же дайте назначение всем действиям*/; keys[5].act = KA_CROUCH; keys[6].key = 0/*Так же дайте назначение всем действиям*/; keys[6].act = KA_JUMP; }
//============================================================================= |
Ну тут думаю всё ясно: “key ” параметр – это код клавиши, на которую будет повешено дейсвтие “act”.
| Код | keyActions aProfile::apGetAction(int key) const { for (unsigned int i = 0; i < keysCount; ++i) if (keys[i].key == key) return keys[i].act;
return KA_NONE; }
//============================================================================= |
Тут тупо проверяем какая клавиша сейчас нажата и возвращаем результат. Чем плох такой метод? Медленный и + если вешать его на событие окна «нажатие клавиши», то тут больше одной клавиши за раз не нажмёшь. Один из тех самых «черновых» моментов.
| Код | aProfile::~aProfile() { }
//============================================================================= |
Очень сложный код, подумайте над ним…
Системный синглтон для связи окна и OGL контекста Сейчас набросаем основной системный класс, который завяжет настройки текущего профиля (изначально единственного) и настройки OpenGL + будет ответственный за все основные события: типа отрисовки сцены, обработки надавливания кнопок и прочего… Задание: Помните как мы создали статик либу doCommon.lib ? Вернитесь, перечитайте и добавьте новый проект библиотеки под названием doSystem – тут у нас будут все классы, так сказать «системного назначения». Добавляем туда след. файл, не забываем делить файлы на папки. Поехали - aSystem.h
| Код | //=============================================================================
#ifndef ASYSTEM_H #define ASYSTEM_H
//=============================================================================
#include <windows.h> #include <gl/gl.h> #include <gl/glu.h> #include "aProfile.h" #include "doCommon.h"
//=============================================================================
#define SYS aSystem::asGetInstance()
//============================================================================= |
Подрубаем необходимые библиотеки + пару наших. Заводим опеределение для “SYS”: так проще работать с синглтоном. Задание: Вернитесь к описанию класса aAux и заведите такой же define для него.
| Код | enum asmRet { SR_WELL = -128, SR_NULLED_PATH_TO_PROFILES_FOLDER, };
//============================================================================= |
Если функция должна возвращать что-то, то все значения должны описываться через опред. перечисление.
| Код | class aSystem { private: aSystem();//нельзя создавать объекты данного типа ~aSystem();
public: inline static aSystem& asGetInstance() {static aSystem sys; return sys;} inline void asSetWnd(const HWND wnd) {if (wnd != NULL) hWnd = wnd;}//устанавливаем окно inline const HWND asGetWnd()const {return hWnd;}//получаем окно inline GLsizei asGetWidth()const {return width;}//получаем длинну окна inline GLsizei asGetHeight()const {return height;}//получаем высоту окна inline void asSetDC(const HDC dc) {if (dc != NULL) hDc = dc;}//устанавливаем контекст inline const HDC asGetDC()const {return hDc;} //получаем контекст окна inline void asSetRC(const HGLRC gl) {if (gl != NULL) glRc = gl;}//ставим контекст OGL inline const HGLRC asGetRC()const {return glRc;}//получаем его же
bool asOpenGLInit();//инициализация OpenGL void asResizeOpenGL(GLsizei w, GLsizei h);//изменение размеров окна void asPaintTheWorld();//начинаем отрисовку сцены void asSetDisplayToFullscreen();//переходим в полноэкранный режим void asPrintLastError()const;//получить послед. Ошибку от винды void asKeyDown(WPARAM wp);//обработка нажатых кнопок void asGetFPS();//Считать кол-во кадров в секунду void asParseParameters(const aString& str);//разбираем строку параметров
private: aProfile useProfile;//Текущий профиль (потом будет список) HGLRC glRc;//Контекст отображения OpenGL HDC hDc;//Контекст отображения окна HWND hWnd;//Хэндл окна GLsizei width, height;//Длинна, высота окна GLdouble fov;//Угол обзора GLdouble aspect;//Отношение длинны к высоте окна GLdouble nearClip;//Ближайшая плоскость отсечения GLdouble farClip;//Дальняя плоскость отсечения GLbitfield clearField;//Отчистка кадра //aCamera eyes;//Камера. В будущем 8) float framesInterval;//Интервал между кадрами float motionSpeed;//Скорость движения в мире. Надо будет для физики // for debug only const float debug_cameraSpeed; //Пока что на будущее для скорости камеры };
//=============================================================================
#endif
//============================================================================= |
Кто не работал с графикой, некоторые переменные не знает. Пока забейте, сейчас будем проводить инициализацию – я всё раскажу подробно. По идее теперь надо было бы дать описание aSystem.cpp, но на этот раз я сначала опишу другой файл.
Рожаем окно. 0) Итак, пришло время добавить в наш солюшен второй проект, который будет пежить движок (первым был тестовый - консольный). Добавялем в подраздел Projects - win32 Application. В параметрах создания выбираем Empty Project. 1) Теперь сделаем этот проект стартовым. 2) Далее тыкаем на нашем консольном проекте правой кнопкой и выбираем пункт меня Unload Project. Таким образом проект остаётся включёным в слюшен, но никак не участвует в компиляции – не нужен нам лишний баласт. 3) В папке вновь созданного проекта добавляем файл описания. Этот самый файл будет содержать инициализацию окна, основной рабочий цикл. По-сути он будет единственным файлом, и он же будет брать всё из движка, поэтому мы не будем создавать папку cpp, а запишем файл main.cpp прямо в папку с проектом win32 Application, который только что создали. 4) Конечно название может быть любым. 5) Пристегнитесь, взлетаем main.cpp:
| Код | //=============================================================================
#include "../../inc/doSystem.h"
//============================================================================= |
Подключили файл библиотеки, которого ещё не создали. Создадим, не торопитесь компилить 
| Код | LRESULT CALLBACK WndProc(HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam) { GLuint PixelFormat; PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; |
Эта функция принимает и обрабатывает все приходящие окну сообщения. Первый параметр функции – хэндл окна, второй – само сообщение, ну и собственно два параметра этого сообщения. Далее идёт заведение переменной PixelFormat – Задание: глянуть в msdn по функции ChosePixelFormat и понять что же это такое. Затем мы заполняем структуру дескриптора для нашего OpenGL контекста отображения. Так же поройтесь и разберитесь что это такое!
| Код | switch(message) { case WM_CREATE: { SYS.asSetDC(GetDC(hWnd));//получаем контекст нашего окна. PixelFormat = ChoosePixelFormat(/*какие тут должны быть параметры? Я уже просил вас почитать про эту функцию в msdn.*/);
if (PixelFormat == NULL) { MessageBox(0, L"Can't find suitable pixel format.", L"Window creation error", MB_OK | MB_ICONERROR); PostQuitMessage(0);//Ошибка – выход. }
if (SetPixelFormat(SYS.asGetDC(), PixelFormat, &pfd) == NULL) { MessageBox(0, L"Can't set pixel format.", L"Window creation error", MB_OK | MB_ICONERROR); PostQuitMessage(0); }
SYS.asSetRC(wglCreateContext(SYS.asGetDC()));//Создаём контекст OGL для вывода на него кадров.
if (SYS.asGetRC() == NULL) { MessageBox(0, L"Can't create OpenGL context.", L"Window creation error", MB_OK | MB_ICONERROR); PostQuitMessage(0); }
if (wglMakeCurrent(SYS.asGetDC(), SYS.asGetRC()) == NULL)//Делаем наш контекст активным { MessageBox(0, L"Can't set OpenGL context as current.", L"Window creation error", MB_OK | MB_ICONERROR); PostQuitMessage(0); }
if (!SYS.asOpenGLInit())//Инициализируем основные параметры OpenGL { MessageBox(0, L"Can't correctly init OpenGL.", L"OpenGL init error", MB_OK | MB_ICONERROR); PostQuitMessage(0); } break; } |
Наше первое сообщение. Как ясно из названия: сообщение создания окна.
| Код | case WM_CLOSE: { ChangeDisplaySettings(NULL, 0);//Если мы меняли настройки экрана, то надо их вернуть в зад. wglMakeCurrent(SYS.asGetDC(), SYS.asGetRC()); wglDeleteContext(SYS.asGetRC()); ReleaseDC(SYS.asGetWnd(), SYS.asGetDC()); PostQuitMessage(0);
break; } case WM_PAINT://Сообщение об отрисовке окна. { SYS.asPaintTheWorld();//Рисуем наш мир SwapBuffers(SYS.asGetDC());//Выводим кадр на экран
break; } case WM_KEYDOWN: { SYS.asKeyDown(wParam);//Обрабатываем нажатие клавиши
break; } case WM_MOUSEMOVE: { //потом тут будет обработка движения мышкой. Будем вертеть балдой ^_^ break; } default: { return DefWindowProc(hWnd, message, wParam, lParam);//Остальные сообщения обрабатываем default’овым способом. } }
return 0; }
//============================================================================= |
Далее идёт самая заглавная функция. Аля int main() в консольных приложениях. Начало конца короче 8)
| Код | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR cmdLine, int showCmd) { MSG msg; WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = L"Springy";//Создаём класс окна и выставляем ему параметры.
SYS.asParseParameters(cmdLine);//Передаём строку параметров, на парсинг нашей системе. if (!RegisterClass(&wc))//Регистрируем класс { SYS.asPrintLastError();//Если что – выводим ошибку.
return FALSE; }
SYS.asSetWnd(CreateWindow(wc.lpszClassName, L"Springy jump!", WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS/*WS_OVERLAPPEDWINDOW*/, 0, 0, SYS.asGetWidth(), SYS.asGetHeight(), NULL, NULL, hInstance, NULL));//Создаём окно. Обратите внимание на закомментированные параметры. Потом попробуйте оставить их, а закомментировать ныне стоящие. if (SYS.asGetWnd() == NULL)//Если окно родился нормальным { SYS.asPrintLastError();
return FALSE; }
ShowWindow(SYS.asGetWnd(), SW_SHOW);//… то будет нам спартанец. Покажем его миру! UpdateWindow(SYS.asGetWnd()); SetFocus(SYS.asGetWnd());
while(GetMessage(&msg, NULL, 0, 0))//Сидим в конце очереди сообщений и хватаем последнее. { TranslateMessage(&msg); DispatchMessage(&msg); }
return TRUE; }
//============================================================================= |
Как вы думаете есть ошибки? Наверное - есть Ваша задача – устранить 8) Вы уже порядочно утомились и хотите увидеть уже хоть что-нибудь, я прав? Вот сейчас мы создадим последний класс описания, который пропсутили и будет вам ваша маленькая вселенная, где вы сможете сотворить всё, aSystem.cpp (незабываем куда его надо ложить!):
| Код | //=============================================================================
#include "../../../../inc/aSystem.h"
//=============================================================================
aSystem::aSystem() : debug_cameraSpeed(1.0f) { glRc = 0; hDc = 0; hWnd = 0; width = 800; height = 600; fov = 45.0; |
Угол обзора. Представьте что ваши глаза – это вершина пирамиды, идущая от вас и основание которой лежит параллельно плоскости экрана. Так вот fov – это угол расхождения граней от вершины к основанию, которые характеризуют области видимости камеры.
| Код | aspect = GLdouble(width)/GLdouble(height); nearClip = 0.01; farClip = 1000.0; |
В OpenGL есть две плоскости параллельные вашему экрану, которые ограничивают сцену. За ними ничего нет, есть только то, что между ними. Переменная nearClip – задаёт расстояние между ближней плоскостью отсечения и камерой, а farClip – далней и камерой.
| Код | clearField = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; |
После каждого кадра нам нужно отчищать буферы, коих несколько. Тут мы указываем, что сейчас будем отчищать буфер цвета (по сути сбрасывать всё что нарисованно) и буфер глубины.
| Код | framesInterval = 0.0f; motionSpeed = 1; }
//=============================================================================
bool aSystem::asOpenGLInit() { glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_2D); glEnable(GL_POINT_SMOOTH); |
Инициализация OpenGL. Разрешаем тест глубины, чтобы OpenGL мог оценить: какой объект ближе, а какой дальше. Включаем возможность использования текстур. И делаем точки сглаженными (будут выглядить как круглешки. Создаёт доп. нагрузку, но зато прикольно. Пусть пока будет: потом отключем 8))
| Код | glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); |
Вся наша геометрия будет выводи ться через массивы отображения. Что это такое и с чем всё это курят – будем разбираться, когда дойдём до вывода моделей (думаю это будет где-то в 5 части).
| Код | glClearColor(0.0f, 0.0f, 0.0f, 0.0f);//Устанавливаем цвет, которым будет заливаться каждый кадр. glClearDepth(1.0f);//Почитайте glDepthFunc(GL_LESS);//любимый glShadeModel(GL_SMOOTH);//msdn SetCursorPos(width/2, height/2);
asResizeOpenGL(width, height);
return 1; }
//=============================================================================
void aSystem::asResizeOpenGL(GLsizei w, GLsizei h)//если вдруг решили изменить размер окна { if (w <= 0) w = 1;
if (h <= 0) h = 1;
width = w; height = h; aspect = GLdouble(w)/GLdouble(h);
glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(fov, aspect, nearClip, farClip);//Устанавливаем наши параметры. glMatrixMode(GL_MODELVIEW); }
//=============================================================================
void aSystem::asPaintTheWorld() { glClear(clearField); glLoadIdentity(); gluLookAt(3.0, 5.0, 4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); |
Отчистили экран, сбросили матрицу вида в ноль и устоновили камеру в координаты (3.0, 5.0, 4.0), заставили смотреть в начало координат. Последние три параметра – указывают ось (абсцисс, ординат, аппликат) которая будет направлена вверх. Сейчас мы сказали, что это - ось Y(0.0, 1.0, 0.0).
| Код | glDisable(GL_TEXTURE_2D);//отключаем текстуры, потому что если у нас до этого отображались модели с текстурами (на будущее) – у нас не будет корректно отображены примитивы типа точек, линий и прочего. glBegin(GL_LINES);// Начинаем рисовать линии. glColor3ub(255, 0, 0);// Красный цвет glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
glColor3ub(0, 255, 0);// Система цвета - RGB glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
glColor3ub(0, 0, 255);// Цветовой диапозон: [0…255] glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 1.0f); glEnd(); glEnable(GL_TEXTURE_2D);
asGetFPS();//Проведём подсчёт кадров в секунду. }
//=============================================================================
void aSystem::asSetDisplayToFullscreen()//Переход в полноэкранный режим. { DEVMODE dm;
memset(&dm, 0, sizeof(DEVMODE)); dm.dmPelsWidth = width; dm.dmPelsHeight = height; dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; dm.dmSize = sizeof(DEVMODE);//Устанавливаем параметры ChangeDisplaySettings(&dm, CDS_FULLSCREEN);//Меняем разрешение нашего дисплея на необходимые. По сути: мы не окно растягиваем на весь экран, а экран уменьшаем до размера нашего окна. Если бы оно находилось не в (0, 0), то мы это заметили. }
//=============================================================================
void aSystem::asPrintLastError()const { LPVOID lpMsgBuf; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), 0, (LPTSTR) &lpMsgBuf, 0, NULL); MessageBox(0, (LPCTSTR)lpMsgBuf, L"Error", MB_OK | MB_ICONERROR); LocalFree(lpMsgBuf); }
//=============================================================================
void aSystem::asKeyDown(WPARAM wp)//Пока что никаких клавиш мы не обрабтываем :) { switch(useProfile.apGetAction(wp)) { default: { break; } } }
//=============================================================================
void aSystem::asGetFPS() { static int fps = 0; static float frameTime = 0.0f; static float lastTime = 0.0f; float currentTime = GetTickCount()/1000.0f;
framesInterval = currentTime - frameTime; if (AUX.aaCompareFloat(frameTime, 0.0f)) AUX.dt = 0.0f; else AUX.dt = framesInterval/motionSpeed; |
Подсчёт времени между кадрами. Необходимо для контроля за работой физики, исходя из скорости работы машины. Кстати, мы не заводили переменной dt типа float, у синглтона aAux?
| Код | frameTime = currentTime; ++fps;//Собственно само кол-во кадров в секунду на данный момент. if ((currentTime - lastTime) > 1.0f) { lastTime = currentTime; fps = 0; } }
//=============================================================================
void aSystem::asParseParameters(const aString& str) { aList<aString> list;
str.asGetWords(list);
aItem<aString>* tmpItem = list.alGetFirstItem();
while(tmpItem) { tmpItem = tmpItem->next; } }
//============================================================================= |
У вас вопрос по поводу второй строки последней функции? Вроде у нас нет такого метода aString::asGetWords(aList<aString>& list) ? Этот метод разбирает строку на слова и записывает их в список, который передан в качестве параметра. Задание: реализуйте этот метод. Подсказка: код пробела – 32.
| Код | aSystem::~aSystem() { }
//============================================================================= |
«Лев два скачка сделал, и сел под пальму отдыхать…» А лошадь-то всё бежит! Последних выдох – doSystem.h:
| Код | //=============================================================================
#ifndef DOSYSTEM_H #define DOSYSTEM_H
//=============================================================================
#include "aSystem.h" #include "aProfile.h"
//=============================================================================
#pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glu32.lib") #pragma comment(lib, "../../libs/doSystem.lib")
//=============================================================================
#endif
//============================================================================= |
Наверное вы ещё не сразу запустите приложение, потому что придётся сделать задания, придётся найти ошибки, самим подумать, почитать, разобраться, что-то спросить у меня. Дорогу осилит идущий! Вот теперь всем и каждому ещё одно задание – начать читать перевод красной книги OpenGL на progz.ru – все основные функции разбираются до мелочей, с кучей примеров. Ссылку я давал в нулевой части цикла, которая закреплена в топе раздела. Всё: «И да прибудет с вам великая сила!»
|