netlib.narod.ru | < Назад | Оглавление | Далее > |
Системное ядро обрабатывает инициализацию и поток выполнения программы обычного игрового приложения Windows, а также процессы, состояния и упаковку данных. Это то место, с которого вы начинаете разработку вашего нового игрового проекта.
Наиболее полезный объект в системном ядре — это cApplication, который создает окно вашего приложения и управляет потоком выполнения программы. Объект регистрирует класс окна, создает окно приложения и запускает конвейер сообщений, который обрабатывает для вас посылаемые окну приложения сообщения и в случае необходимости вызывает внутренние функции класса.
При работе приложения класс cApplication вызывает три предоставляемых вами (через объявление наследуемого класса) перегруженных функции: Init, Shutdown и Frame. У каждой из функций есть свое особое назначение, с которым вы познакомитесь позже в этом разделе. Для работы с сообщениями Windows вам надо также предоставить обработчики сообщений. Об этом я тоже расскажу чуть позже.
А теперь пойдем вперед и взглянем на приведенный ниже код, содержащий объявление класса cApplication:
class cApplication { private: HINSTANCE m_hInst; // Дескриптор экземпляра HWND m_hWnd; // Дескриптор окна protected: char m_Class[MAX_PATH]; // Имя класса char m_Caption[MAX_PATH]; // Заголовок окна WNDCLASSEX m_wcex; // Структура класса окна DWORD m_Style; // Стиль окна DWORD m_XPos; // Координата X окна DWORD m_YPos; // Координата Y окна DWORD m_Width; // Ширина окна по умолчанию DWORD m_Height; // Высота окна по умолчанию public: cApplication(); // Конструктор HWND GethWnd(); // Возвращает дескриптор окна HINSTANCE GethInst(); // Возвращает дескриптор экземпляра BOOL Run(); // Исполняет код класса BOOL Error(BOOL Fatal, char *Text, ...); // Печать ошибок BOOL Move(long XPos, long YPos); // Перемещение окна BOOL Resize(long Width, long Height); // Изменение размера // клиентской области BOOL ShowMouse(BOOL Show = TRUE); // Скрывает или // отображает курсор // Обработка событий по умолчанию virtual FAR PASCAL MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hWnd, uMsg, wParam, lParam); } // Пользовательские функции, содержащие код игры virtual BOOL Init() { return TRUE; } virtual BOOL Shutdown() { return TRUE; } virtual BOOL Frame() { return TRUE; } };
Количество вызываемых функций в классе cApplication минимально, поскольку он разработан фактически для того, чтобы исполнять самого себя — вы просто добавляете ваш код игры.
Чтобы использовать класс cApplication вы сначала должны создать класс-наследник, использующий cApplication в качестве базового класса. Так вы выполняете перегрузку определенных функций для соответствия вашим собственным требованиям. Перегружаемые функции, как упоминалось ранее, — это Init, Shutdown и Frame.
В функции cApplication::Init вы размещаете весь код инициализации класса, такой как загрузка данных, подготовка состояний и т.д. Противоположность Init — функция cAplication::Shutdown, которая освобождает все ранее выделенные ресурсы. Функция Shutdown вызывается самой последней, а функция Init — самой первой.
Далее следует функция cApplication::Frame, которая вызывается почти на каждой итерации цикла обработки сообщений (в том случае, если у Windows нет сообщений для обработки). Как можно предположить, функция Frame обрабатывает один кадр из вашей игры, что может включать обработку пользовательского ввода, проверку сетевых данных и рисование графики.
У вас мало причин для перегрузки функций обработчиков сообщений, если вам не требуется собственный алгоритм обработки сообщений Windows. Для обработки сообщений вы перегружаете функцию cApplication::MsgProc, как я вскоре покажу вам.
А пока уделите минуту своего времени на работу с классом cApplication для быстрого создания приложения. Убедитесь, что не забыли включить в ваш проект файлы Core_System.h и Core_System.cpp, а также унаследовали класс вашего приложения, с которым будете работать, как показано ниже:
#include "Core_System.h" class cApp : public cApplication { private: char *m_Name; public: cApp(); // Конструктор BOOL Init(); // Перегруженная функция Init BOOL Shutdown(); // Перегруженная функция Shutdown BOOL Frame(); // Перегруженная функция Frame };
Этот пример кода инициализирует ваше приложение регистрируя класс окна, создавая окно и запуская конвейер сообщений, в котором непрерывно вызывается функция Frame. Цель примера — создание буфера и сохранение в нем текста (в данном случае моего имени) и отображение этого текста в каждом кадре. Завершая работу приложения вы освобождаете текстовый буфер и выходите из программы.
cApp::cApp() { // Присваиваем данным экземпляра // значения по умолчанию m_Name = NULL; // Устанавливаем стиль, местоположение, // ширину и высоту окна m_Style = WS_OVERLAPPEDWINDOW; // Стиль окна m_XPos = 100; // Координата X окна m_YPos = 20; // Координата Y окна m_Width = 400; // Ширина клиентской области m_Height = 400; // Высота клиентской области // Назначаем имя класса и заголовок окна strcpy(m_Class, "NameClass"); strcpy(m_Caption, "My Name Example"); } BOOL cApp::Init() { // Выделяем память для хранения имени if((m_Name = new char[10]) == NULL) strcpy(m_Name, "Jim Adams"); else return FALSE; return TRUE; } BOOL cApp::Shutdown() { // Освобождаем буфер имени delete [] m_Name; m_Name = NULL; // Сброс указателя } BOOL cApp::Frame() { // Отображаем имя и ждем нажатия OK или CANCEL, // выход из программы по CANCEL if(MessageBox(GethWnd(), m_Name, "My name is", MB_OKCANCEL) == IDCANCEL) return FALSE; return TRUE; }
Ну вот! Все, что осталось сделать — это создать экземпляр вашего нового класса и запустить его из функции WinMain:
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow) { cApp App; return App.Run(); }
В главе 1, «Подготовка к работе с книгой», было показано использование класса обработки на основе состояний. В этом разделе я применю данную информацию в качестве фундамента и покажу вам усовершенствованную версию разработанного в главе 1 класса cStateManager. При создании ваших игр вы обнаружите, что эта версия cStateManager лучше подходит для управления потоком выполнения игровой программы.
Новый диспетчер состояний добавляет пару концепций: цели вызова и добавление к функциям в cStateManager определяемых пользователем указателей на данные.
Вот код объявления класса cStateManager:
class cStateManager { // Указатели на функции состояний (связанный список) typedef struct sState { void (*Function)(void *Ptr, long Purpose); sState *Next; // Конструктор структуры, очищающий указатели sState() { Function = NULL; Next = NULL; } // Деструктор структуры для удаления // из связанного списка ~sState() { delete Next; Next = NULL; } } sState; protected: sState *m_StateParent; // Родитель связанного списка // стека состояний public: cStateManager(); // Конструктор ~cStateManager(); // Деструктор // Помещает состояние в стек вместе с определяемым // пользователем указателем. Функция помещения в стек // вызывает функцию состояния, указывая в качестве // цели инициализацию void Push(void (*Function)(void *Ptr, long Purpose), void *DataPtr = NULL); // Выталкивание верхнего состояния из стека с вызовом // функции состояния для завершения работы BOOL Pop(void *DataPtr = NULL); // Выталкивание всех состояний с завершением работы // каждого из них void PopAll(void *DataPtr = NULL); // Обработка верхнего состояния в стеке с указанием // обработки отдельного кадра в качестве цели вызова BOOL Process(void *DataPtr = NULL); };
Работа с классом cStateManager может сперва показаться необычной (особенно что касается целей вызова), но не следует беспокоиться, для этого и существуют примеры! Взгляните на пример, который базируется на предыдущем примере работы с cApplication:
class cApp : public cApplication { private: cStateManager m_StateManager; // Прототипы функций состояний static void Function1(void *, long); static void Function2(void *, long); public: BOOL Init() { m_StateManager.Push(Function1, this); } }; void cApp::Function1(void *DataPtr, long Purpose) { // Получаем указатель на вызывающий класс, // поскольку функция статическая, а значит // не имеет назначенного экземпляра класса cApp *cc = (cApp*)DataPtr; // При инициализации отображаем сообщение // и помещаем в стек второе состояние if(Purpose == INITPURPOSE) { MessageBox(cc->GethWnd(), "State 1", "Message", MB_OK); cc->m_StateManager.Push(Function2, cc); return; } // Принудительный выход из программы if(Purpose == FRAMEPURPOSE) cc->m_StateManager.Pop(cc); } void cApp::Function2(void *DataPtr, long Purpose) { cApp *cc = (cApp*)DataPtr; // Отображаем сообщение и удаляем себя из стека if(Purpose == FRAMEPURPOSE) { MessageBox(cc->GethWnd(), "State 2", "Message", MB_OK); cc->m_StateManager.Pop(cc); return; } }
void StateFunction(void *DataPtr, long Purpose);
Во время исполнения класс cApp помещает в стек функцию состояния (Function1). Сразу после помещения в стек функция состояния Function1 будет вызвана (классом cApp), и в качестве цели вызова будет указана инициализация. В функции Function1 сработает переключатель и в стек будет помещена вторая функция состояния (Function2). Когда функция Function2 будет вызвана для обработки кадра, отображается сообщение, состояние удаляется из стека и выполнение программы завершается.
Обратите внимание на назначение добавленного определяемого пользователем указателя. Поскольку функции состояний в классе должны быть объявлены как статические (иначе код не скомпилируется), вы должны передать функции состояния указатель на экземпляр класса. А раз функция состояния является частью класса, вы можете свободно использовать этот указатель для доступа к данным класса (даже к закрытым данным).
Класс cProcessManager во многом похож на cStateManager, за одним существенным исключением: для каждого кадра вызываются все находящиеся в стеке функции. Чтобы не остаться в тени ранее созданного в главе 1 класса, новый cProcessManager также использует цели вызова и определяемые пользователем указатели, подобно cStateManager.
Объявление класса cProcessManager идентично объявлению класса cStateManager. Вместо того, чтобы еще раз демонстрировать то же самое объявление, я пропущу его и перейду к примеру использования cProcessManager, который для каждого кадра вызывает две функции, помещенные в стек процессов:
class cApp : public cApplication { private: cProcessManager m_ProcessManager; // Прототипы функций процессов static void Function1(void *, long); static void Function2(void *, long); public: BOOL Init() { m_ProcessManager.Push(Function1, this); m_ProcessManager.Push(Function2, this); } }; void cApp::Function1(void *DataPtr, long Purpose) { // Получаем указатель на вызывающий класс, // поскольку функция статическая, а значит // не имеет назначенного экземпляра класса cApp *cc = (cApp*)DataPtr; // Выводим сообщение if(Purpose == FRAMEPURPOSE) { MessageBox(cc->GethWnd(), "Process 1", "Message", MB_OK); return; } } void cApp::Function2(void *DataPtr, long Purpose) { cApp *cc = (cApp*)DataPtr; // Выводим сообщение if(Purpose == FRAMEPURPOSE) { MessageBox(cc->GethWnd(), "Process 2", "Message", MB_OK); return; } }
В главе 1 вы узнали, как использовать диспетчер упаковки данных. Теперь мы изменим созданный ранее диспетчер, добавив две функции, которые возвращают размер буфера данных и указатель на буфер данных. Вот объявление класса:
class cDataPackage { protected: void *m_Buf; // Буфер данных unsigned long m_Size; // Размер буфера данных public: cDataPackage(); // Конструктор ~cDataPackage(); // Деструктор void *Create(unsigned long Size); // Создание буфера void Free(); // Освобождение буфера BOOL Save(char *Filename); // Запись буфера в файл void *Load(char *Filename, unsigned long *Size); // Загрузка void *GetPtr(); // Получение указателя на буфер данных unsigned long GetSize(); // Получение размера данных };
Как видите, класс cDataPackage остался тем же самым (чтобы посмотреть на использование класса cDataPackage, обратитесь к главе 1).
netlib.narod.ru | < Назад | Оглавление | Далее > |