netlib.narod.ru< Назад | Оглавление | Далее >

Системное ядро

Системное ядро обрабатывает инициализацию и поток выполнения программы обычного игрового приложения Windows, а также процессы, состояния и упаковку данных. Это то место, с которого вы начинаете разработку вашего нового игрового проекта.

Использование объекта ядра cApplication

Наиболее полезный объект в системном ядре — это 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 обрабатывает один кадр из вашей игры, что может включать обработку пользовательского ввода, проверку сетевых данных и рисование графики.

ПРИМЕЧАНИЕ
Каждая из упомянутых функций (Init, Shutdown и Frame) возвращает логическое значение, сообщающее классу cApplication о том, продолжать ли выполнение программы или выйти из нее. Если какая-либо из этих функций возвращает FALSE, работа приложения прерывается.

У вас мало причин для перегрузки функций обработчиков сообщений, если вам не требуется собственный алгоритм обработки сообщений 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();
}

Обработка состояний с cStateManager

В главе 1, «Подготовка к работе с книгой», было показано использование класса обработки на основе состояний. В этом разделе я применю данную информацию в качестве фундамента и покажу вам усовершенствованную версию разработанного в главе 1 класса cStateManager. При создании ваших игр вы обнаружите, что эта версия cStateManager лучше подходит для управления потоком выполнения игровой программы.

Новый диспетчер состояний добавляет пару концепций: цели вызова и добавление к функциям в cStateManager определяемых пользователем указателей на данные.

ПРИМЕЧАНИЕ
Цель вызова (calling purpose) — это, согласно названию, причина, по которой вызывается состояние. «Целью» может быть INITPURPOSE (сигнализирует, что функция должна подготовить себя к работе), FRAMEPURPOSE (для обработки отдельного кадра) или SHUTDOWNPURPOSE (для освобождения всех ресурсов, когда обработка завершена).

Вот код объявления класса 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

Класс 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;
    }
}

Управление данными с cDataPackage

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

Сайт управляется системой uCoz