Окна, Логи и все, все, все ...

Изменения

Как ни печально, но этот раздел тоже будет периодически появляться. Сами понимаете - игра еще не закончена, а у автора (т.е. у меня :)) могут меняться взгляды на отдельные классы, блоки, функции и т.д. Поэтому привожу небольшой список изменений, коснувшиеся прежних классов:

Логи

Не знаю как вам, а мне всегда нравились игры, которые оставляли после себя небольшие log-файлы, в которых было интересно копаться. В них можно было найти кучу полезной информации: начиная от типа процессора и его частоты, заканчивая полным списком видеорежимов. Поэтому и появился новый класс tLogFile:

/*--------------------------------------------------------------
tLogFile

Класс log-файла
--------------------------------------------------------------*/
class TENGINE_DLL tLogFile : public tFile
{
public:
    tLogFile();
    virtual ~tLogFile();
    //
    void OpenLogFile(const char *name);
    void LogMessage(const char *message ...);
    void CloseLogFile(void);
};

Этот класс является производным от tFile (я же говорил, что он нам еще пригодится), который в принципе отличается от базового только наличием функции вывода сообщения в log-файл - LogMessage.

Окна

Любая программа для Windows, если она хочет вывести что-нибудь на экран, должна создать окно. Наша игра не исключение из правил, поэтому мы тоже это сделаем.

/*--------------------------------------------------------------
tWindow

Класс окна
--------------------------------------------------------------*/
class TENGINE_DLL tWindow
{
protected:
    HWND m_hWindow;
public:
    tWindow();
    virtual ~tWindow();
    //
    void InitWindow(int x,int y,int width,int height);
    void ReleaseWindow();
};

Класс tWindow очень простой и состоит из двух функций: InitWindow - создание окна и ReleaseWindow - удаление окна. Рассмотрим первую из них:

/*--------------------------------------------------------------
tWindow::InitWindow()

Создание окна
--------------------------------------------------------------*/
void tWindow::InitWindow(int x,int y,int width,int height)
{
    WNDCLASSEX windowClass;

    windowClass.cbSize = sizeof(WNDCLASSEX);
    windowClass.style = CS_OWNDC|CS_HREDRAW|CS_VREDRAW;
    windowClass.lpfnWndProc = WindowMsgProc;
    windowClass.cbClsExtra = 0;
    windowClass.cbWndExtra = 0;
    windowClass.hInstance = GetModuleHandle(NULL);
    windowClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
    windowClass.hCursor = LoadCursor(NULL,IDC_ARROW);
    windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    windowClass.lpszMenuName = NULL;
    windowClass.lpszClassName = TWINDOWCLASSNAME;
    windowClass.hIconSm = NULL;

    // Регестрируем класс окна
    if(!GetClassInfoEx(GetModuleHandle(NULL),TWINDOWCLASSNAME,&windowClass))
        if(!RegisterClassEx(&windowClass))
            throw tError(__FILE__,__LINE__,"tWindow::InitWindow",
                            "Can't register window class! / class name - %s",TWINDOWCLASSNAME);

    // Создаем окно
    m_hWindow = CreateWindowEx(0,TWINDOWCLASSNAME,TWINDOWNAME,
                                            WS_OVERLAPPEDWINDOW,
                                            x,y,width,height,
                                            NULL,
                                            NULL,
                                            GetModuleHandle(NULL),
                                            NULL);
    if(m_hWindow)
        ShowWindow(m_hWindow,SW_SHOWNORMAL);
    else
        throw tError(__FILE__,__LINE__,"tWindow::InitWindow",
                        "Can't create window! / name - %s",TWINDOWNAME);
}

Подробно описывать ее не буду, потому что создание окна в Window довольно избитая тема. Повторюсь только, что сперва регистируется класс окна, а только потом создается само окно.

Сборка

Теперь, мы имеем несколько классов, которые работоспособны, но обсолютно не связанные ни чем. Если вы смотрели исходники предыдущего шага, то наверное заметили класс tEngine. Он то и станет связующим звеном для нашего движка. Посмотрите на схему внизу.

Обобщающим классом будет tEngine, в котором содержаться классы tLogFile - для создания и формирования лог-сообщений и класс tRender - класс графического вывода, который пока только способен создавать окно.

/*--------------------------------------------------------------
tEngine

Класс движка
--------------------------------------------------------------*/
class TENGINE_DLL tEngine : public tLogFile, public tRender
{
    void (*m_thread)(void);
public:
    tEngine();
    virtual ~tEngine();
    //
    void InitEngine(void);
    void RunEngine(void);
    void ReleaseEngine(void);
    //
    void SetEngineThread(void (*thread)(void));
};

Функции InitEngine и ReleaseEngine - инициализируют и деинициализируют движок.

/*--------------------------------------------------------------
tEngine::InitEngine()

Инициализация движка
--------------------------------------------------------------*/
void tEngine::InitEngine(void)
{
    // Начало log-сообщений
    tLogFile::OpenLogFile("tEngine.log");
    tLogFile::LogMessage("----------------------- begin log ------------------------------\n");
}

/*--------------------------------------------------------------
tEngine::ReleaseEngine()

Деинициализация движка
--------------------------------------------------------------*/
void tEngine::ReleaseEngine(void)
{
    // Конец log-сообщений
    tLogFile::LogMessage("----------------------- end log --------------------------------\n");
    tLogFile::CloseLogFile();
}

Главный цикл игры запускается функцией RunEngine.

/*--------------------------------------------------------------
tEngine::RunEngine()

Запуск движка
--------------------------------------------------------------*/
void tEngine::RunEngine(void)
{
    MSG msg;
    try {
        // Главный цикл
        while(1)
            if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
                if(msg.message==WM_QUIT) break;
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            else
                // Запуск задачи
                (*m_thread)();
    }
    catch(tError &error) {
        error.AddErrorHistory(" <-tEngine::RunEngine <-tEngine.dll");
        throw;
    }
}

Как видите, в ней реализован стандартный цикл сообщений, которые передаются окну программы. В том случае если сообщений нет, то запускается задача, определенная пользователем через функцию SetEngineThread. Поэтому изменился принцип построения игры.

tEngine *engine = new tEngine;

/*--------------------------------------------------------------
Нулевая задача
--------------------------------------------------------------*/
void Null(void)
{
}

/*--------------------------------------------------------------
Главная функция игры
--------------------------------------------------------------*/
void Main(void)
{
    try {
        // Инициализация окна
        engine->LogMessage("Create window ... ");
        engine->InitWindow(100,100,640,480);
        engine->LogMessage("OK.\n");
        // Установка нулевой задачи
        engine->SetEngineThread(Null);
    }
    catch(tError &error) {
        error.AddErrorHistory(" <-thread 0x%x",&Main);
        throw;
    }
}

/*--------------------------------------------------------------
Главная функция Game.exe
--------------------------------------------------------------*/
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
    const char *errorMessage;

    try {
        // Инициализация движка
        engine->InitEngine();
        // Установка задачи на выполнение
        engine->SetEngineThread(Main);
        // Запуск главного цикла
        engine->RunEngine();
        // Деинициализация движка
        engine->ReleaseEngine();
    }
    catch(tError &error) {
        error.AddErrorHistory(" <-WinMain <-Game.exe");
        //
        errorMessage = error.GetErrorMessage();
        //
        engine->LogMessage("\n%s\n",errorMessage);
        MessageBox(NULL,errorMessage,"Critical error!",MB_OK|MB_ICONERROR);
        //
        return 1;
    }
    return 0;
}

Сперва инициализируется движок и указывается задача на выполнение, в данном случае это Main. Затем при запуске движка управление автоматически передается этой функции. В ней создается главное окно программы. Чтобы эта процедура не повторялось все время, необходимо установить указатель на задачу которая ничего не выполняет - Null.

Сайт создан в системе uCoz