| 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 | < Назад | Оглавление | Далее > |