Загрузка...
 
Печать

Каркас (framework) игры




Intro

Когда игрокодер с головой погружен в написание кода, часто очень трудно держать под контролем все внесённые в код изменения и поддерживать порядок в исходных файлах. Здесь не обойтись без понимания принципов работы программного потока. Грамотно структурированное приложение (или фреймворк) позволяет легко ориентироваться в своём исходном коде и вносить в него необходимые изменения. Имея на руках готовый дизайн-документ игры (Ты же сделал его, не так ли?), осталось лишь на его основе построить структуру процесса выполнения программы (structure of the processing flow).1
Каждый раз при создании новой игры набирать одни и те же функции и классы не рационально. На деле игрокодеры создают одну или несколько lib-библиотек (поддерживаются всеми версиями MS Visual C++), содержащих все необходимые функции, и затем подключают их в свои Проекты, экономя кучу времени. В этом и заключается идея фреймворка (=каркаса) приложения (application framework). В самом простом случае фреймворк должен содержать код:
  • инициализации и создания окна;
  • различных движков (графического, звукового, ввода, сети и т.д.);
  • инициализации игры;
  • покадровых процедур (per-frame routines);
  • функции завершения работы игрового приложения.
Применение модульного подхода программирования (modular-coding techniques) полезно, т.к. все основные компоненты (например движки) могут содержаться в отдельных файлах исходного кода.

Жизненный цикл игрового приложения

Типичная программа начинает свою работу с инициализации всех своих "систем" и данных. Затем она входит в т.н. главный цикл (main loop). Именно здесь происходит само выполнение программы. В зависимости от текущего игрового стейта (главный экран, игровой процесс и т.д.) игровые ввод и вывод обрабатываются по-разному.
При создании любого игрового приложения игрокодер, как правило, выполняет следующие шаги:
  1. Инициализация систем (Windows, графика, ввод, звук и т.д.).
  2. Подготовка данных (загрузка конфигурационных файлов).
  3. Конфигурирование стейта по умолчанию (default state; обычно это стейт с титульным экраном).
  4. Старт главного цикла программы (main loop).
  5. Определение текущего стейта и его обработка (путём обработки по необходимости ввода и вывода).
  6. Возврат к шагу 5 до тех пор, пока приложение не будет закрыто, после чего переход к шагу 7.
  7. Очищаем данные, освобождаем ранее занятые ресурсы.
  8. Освобождаем ресурсы системы (закрываем окна, обнуляем графику, ввод и т.д.).
Шаги 1-3 типичны при создании любой игры: настройка текущей системы, загрузка необходимых сопутствующих файлов (графика, звуки и т.д.), подготовка игрового процесса (actual gameplay). Игровое приложение обычно тратит уйму времени на определение текущего стейта и его обработку (шаг 5). Этот шаг можно разделить на 3 части:
  • докадровая обработка (pre-frame processing),
  • покадровая обработка (per-frame processing),
  • послекадровая обработка (post-frame processing).
Докадровая обработка выполняет ряд простых операций, вроде получения текущего времени (для зависимых от времени событий, например синхронизации) или обновления положения/состояния объектов сцены.
Покадровая обработка также обновляет статус объектов (если это не было сделано в докадровой обработке) и рендерит графику.
Послекадровая обработка выполняет оставшиеся функции вроде синхронизации временных операций или даже вывод уже отрендеренной графики.
В твоей игре может быть несколько докадровых стейтов: один для меню, один для организации внутриигрового процесса и т.д. Грамотная организация стейтов в игре называется стейт-процессинг (state-processing) и является ключом к созданию шустрого игрового приложения. Подробнее о них читай статью Стейты игры и процессы (Game States and Processes)
Удаление данных и закрытие приложения (шаги 7 и 8) освобождают системные ресурсы, которые выделялись на стадии препроцессинга. Графика должна быть удалена из памяти, окно приложения уничтожено и т.д. Пропуск данных шагов категорически противопоказан.
При создании программного потока каждому шагу сопоставлен соответствующий блок исходного кода. Поэтому, чем яснее и чётче структура этого кода, тем проще будет на его основе создать приложение. Часто код размещают в отдельных lib-файлах (системное ядро, графическое ядро, звуковое ядро и т.д.), подключаемых к проектам Visual C++ при необходимости.

Структурирование проекта (Structuring a Project)

Исходный код в любом Проекте можно организовать разными путями. Можно собрать весь код в одном .cpp-файле, либо разделить на несколько отдельных файлов, исходя из функционала. Например, графические функции можно разместить в файле graphics.cpp, а их объявления вынести в заголовочный файл graphics.h (не забыв, конечно, прописать в graphics.cpp соответствующую инструкцию include).
Jim Adams в своей книге "Programming Role Playing Games with DirectX 8.0" пишет, что всегда начинает свои проекты с одного файла WinMain.cpp, в котором сосредоточен весь исходный код. Здесь инициализируется окно приложения, вызываются необходимые (авторские) движковые функции (DoInit, DoPreFrame, DoFrame и т.д.). Уже по мере разрастания кода игрокодер выносит код отдельных групп функций (графика, звук, сеть и т.д.) в отдельные .cpp-файлы (с сопутствующими им заголовками .h). Позднее, при создании игры, достаточно просто подключить к своему исходнику соответствующий заголовок и создать объекты (экземпляры, инстансы) отдельных классов, которые будут задействованы. На быстродействие такие манипуляции никак не влияют. Зато позволят не заблудиться в собственном коде.

Пример простейшего приложения-каркаса игры (C++, Win32)

Создадим простейшее оконное приложение и тупо добавим в самом конце файла исходного кода реализации трёх функций (пока пустых):
  • DoInit() (докадровая обработка);
  • DoFrame() (покадровая обработка);
  • DoShutdown() (послекадровая обработка).
Перед началом проверь, что у тебя установлены следующие программные компоненты:
    • MS Visual C++ 2010.
Инструкции по её установке ты найдёшь в разделе "Софт" нашего сайта.

Создаём Проект приложения

  • Создай пустой Проект с именем "Shell01".
Проект автоматически разместится внутри Решения с таким же именем. Весь процесс подробно расписан в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

Добавляем в Проект WinMain.cpp

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Shell01.
  • Во всплывающем меню Добавить->Создать элемент...
Image
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи WinMain.cpp. Жмём "Добавить".
Image
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле WinMain.cpp набираем следующий код:
WinMain.cpp
/**************************************************
WinMain.cpp
Chapter 5 Shell Application

Programming Role-Playing Games with DirectX
by Jim Adams (01 Jan 2002)
**************************************************/

// Include files
#include <windows.h>
#include <stdio.h>

// Window handles, class and caption text
HWND          g_hWnd;
HINSTANCE     g_hInst;
static char   g_szClass[]   = "ShellClass";
static char   g_szCaption[] = "Shell Application";

// Function prototypes
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow);
long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam);

BOOL DoInit();
BOOL DoShutdown();
BOOL DoFrame();

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex;
  MSG        Msg;

  g_hInst = hInst;

  // Create the window class here and register it
  // Заполняем оконный класс и регистрируем его
  wcex.cbSize        = sizeof(wcex);
  wcex.style         = CS_CLASSDC;
  wcex.lpfnWndProc   = WindowProc;
  wcex.cbClsExtra    = 0;
  wcex.cbWndExtra    = 0;
  wcex.hInstance     = hInst;
  wcex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = NULL;
  wcex.lpszMenuName  = NULL;
  wcex.lpszClassName = g_szClass;
  wcex.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  if(!RegisterClassEx(&wcex))
    return FALSE;

  // Create the Main Window
  // Создаём окно
  g_hWnd = CreateWindow(g_szClass, g_szCaption,
        WS_CAPTION | WS_SYSMENU,
        0, 0, 400, 400,
        NULL, NULL,
        hInst, NULL );
  if(!g_hWnd)
    return FALSE;
  ShowWindow(g_hWnd, SW_NORMAL);
  UpdateWindow(g_hWnd);

  // Run init function and return on error
  if(DoInit() == FALSE)
    return FALSE;

  // Start message pump, waiting for signal to quit
  // Стартуем цикл выборки сообщений. Ждём сигнала завершения
  ZeroMemory(&Msg, sizeof(MSG));
  while(Msg.message != WM_QUIT) {
    if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
    }
    if(DoFrame() == FALSE)
      break;
  }

  // Run shutdown function
  DoShutdown();
  
  UnregisterClass(g_szClass, hInst);

  return Msg.wParam;
}

long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

  }

  return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL DoInit()
{
  return TRUE;
}

BOOL DoShutdown()
{
  return TRUE;
}

BOOL DoFrame()
{
  return TRUE;
}


  • Сохрани Решение (Файл -> Сохранить все).
Данный исходный код целиком взят из примера к книге Programming Role-Playing Games with DirectX by Jim Adams (01 Jan 2002). Автор прогал на MS Visual C++ 6.0 (вышла в свет в 1998 г.) У нас IDE совсем другая (MS Visual C++ 2010 Express Edition). Нет, не с целью усложнения задачи. Просто MS Visual C++ 6.0 в своё время стоила 500 USD. Сейчас её, в принципе, можно поискать в торрентах. (За всё, что ты там скачаешь, администрация Igrocoder.ru ответственности не несёт!) Но использование платного/пиратского софта не соответствует концепции сайта Igrocoder.ru . Поэтому наш выбор:
  • Бесплатная IDE MS Visual C++ 2010 Express edition.

Готовим Проект Shell01 к компиляции

Для успешной компиляции изменим настройки (=свойства) текущего Проекта Shell01, созданного в MSVC++2010. При этом сам код из книги 2002 года останется нетронутым.
С момента выхода MS Visual C++ 6.0 прошло немало времени. С MSVC++2010 Express их разделяют аж 12 лет. Из-за этого вышеприведённый код (написанный в начале 2002 г.) на данном этапе в MSVC++2010 Express компилироваться не будет, выдавая многочисленные ошибки.

Выбираем многобайтовую кодировку

Закрыть
noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, данный Проект мы настроим под многобайтовую кодировку.

Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект Shell01.
В Обозревателе решений видим: "Решение "Shell01"", а строкой ниже жирным шрифтом название Проекта (тоже Shell01).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта Shell01.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации->Общие, в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
Image
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все).

Отключаем инкрементную компоновку (incremental linking)

Инкрементная компоновка призвана сократить время компилирования. Но на деле её присутствие часто вызывает ошибки вроде этой:
Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден


Закрыть
warningОшибка LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

В MS Visual C++ 2010 даже компиляция консольных приложений нередко завершается неудачей, а вместо исполняемого файла программист видит сообщение:
LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt.

ПРИЧИНА: MS Visual С++ 2010 "не нравится" версия .NET Framework(external link), установленная в операционной системе. В общих чертах, MS Visual С++ 2010 спрограммирована для работы под управлением .NET Framework 4.0 и сильно к нему привязана. Если точнее, к нему сильно привязана система инкрементной компоновки приложений (incremental linking), которая по умолчанию включена для всех создаваемых проектов.
Во время установки MS Visual C++ 2010 пытается установить свой "родной" .NET Framework 4.0, проверяя версию этой программной платформы, установленную в ОС на данный момент. Если версия .NET Framework ниже 4.0, то она обновляется до 4.0 и всё прекрасно компилируется. Если версия .NET Framework выше 4.0, то всё оставляется как есть: IDE успешно завершает установку, но при компиляции ВСЕХ приложений выскакивает данная ошибка. Более того, ошибка была замечена даже при наличии в системе .NET Framework версии 4.0, но отличающейся от "родной" припиской вроде "Beta" или "Release Candidate".

ВАРИАНТЫ РЕШЕНИЙ:
1. Отключить инкрементную линковку в опциях Проекта.
В главном меню MSVC++2010 выбираем: Проект->Свойства->Свойства конфигурации->Компоновщик(Linker)->Включить инкрементное построение (Incremental Linking). Данный пункт по умолчанию включен для всех новых проектов. Выставляем его в Нет (No). И жмём OK. Инкрементное построение заметно сокращает время компилирования больших проектов. Но в нашем случае его отсутствие некритично.
2. Удалить (переместить в другое место) утилиту cvtres.exe из каталога bin установленной MS Visual C++ 2010.
В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe . Официальное название данного приложения - Microsoft® Resource File To COFF Object Conversion Utility (утилита конвертации файлов двоичных ресурсов в Component Object File Format). Опытным путём установлено, что при линковке IDE "спотыкается" именно об него.
3. Удалить из системы все версии .NET Framework (включая языковые пакеты и всякие профайлеры, если есть) и саму MS Visual C++ 2010.
Всё вышеперечисленное можно без труда найти в меню "Программы и компоненты" (MS Windows Vista/7/8). Затем заново установить MS Visual C++ 2010. При этом автоматом установится .NET Framework 4.0, идущий с ней в наборе.

Третий пункт - самый долгий. На деле почти всегда хватает выполнения первых двух. Данным вопросом озадачивались ребята здесь: https://www.cyberforum.ru/cpp-beginners/thread637174.html?ysclid=l3akpmanrq(external link). После этого линковка (=компоновка) проходит идеально. Подобные "костыли" в IDE от Майков - не редкость. Можно предположить, что команда с головой ударилась в тестирование .NET-возможностей MSVC++2010, совсем забыв о Win32-направлении (либо признав его бесперспективным).


Отключим инкрементную компоновку в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь.
В Обозревателе решений видим: "Решение "..."", а строкой ниже жирным шрифтом название Проекта. Обычно с тем же названием (если не менял вручную).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение Нет (/INCREMENTAL:NO).
  • Жмём ОК.

Удаляем файл cvtres.exe

В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe
В Win32-кодинге он особо ни на что не влияет. Зато без него линковка идёт как по маслу.

Компилируем Проект Shell01

Наконец, наш тестовый Проект готов к компиляции.
  • Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 на клавиатуре.
Image
Скомпилированное .exe-приложение в нашем случае (Win7 x64) расположено по пути C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\Shell01\Debug .
После компилирования приложение Shell01 автоматически запустится и покажет окно c белым фоном. В данном случае функции DoInit(), DoFrame() и DoShutdown() ничего не делают, но служат своеобразной заготовкой будущего алгоритма программного потока. Имена функций, конечно, игрокодер выбирает на своё усмотрение.

Расширяем функционал. Каркас игрового приложения. Версия 2

Игровых функций может быть и больше.
  • Рассмотри такой исходй код:
WinMain.cpp
//================================================
//WinMain.cpp
// Base Framework application
// Source:
// Programming Role-Playing Games with DirectX by Jim Adams (01 Jan 2002)
//================================================

// Include files
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>

// Main application instances
HINSTANCE g_hInst; // Глобальный дескриптор экземпляра (Global Instance handler)
HWND g_hWnd; // Глобальный дескриптор окна (Global window handle).

// Application window dimensions, type, class and caption text
#define WNDWIDTH 400
#define WNDHEIGHT 400
#define WNDTYPE WS_OVERLAPPEDWINDOW

static char g_szClass[] = "FrameClass";
static char g_szCaption[] = "Base Framework Application"; // Main application prototypes

// Entry point
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow);

// Function to display an error message.
void AppError(BOOL Fatal, char *Text, ...);

// Message procedure
long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Functions to register and unregister Windows' classes
BOOL RegisterWindowClasses(HINSTANCE hInst);
BOOL UnregisterWindowClasses(HINSTANCE hInst);

// Function to create the application window
HWND CreateMainWindow(HINSTANCE hInst);

// Functions to init, shutdown and handle per-frame functions
BOOL DoInit();
BOOL DoShutdown ();
BOOL DoPreFrame ();
BOOL DoFrame();
BOOL DoPostFrame();

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow)
{
	MSG Msg;
	
	// Save application instance
	g_hInst = hInst;
	
	// Register windows' classes. Return on False
	if(RegisterWindowClasses(hInst) == FALSE)
		return FALSE;
	
	// Create window. Return on False
	if((g_hWnd = CreateMainWindow(hInst)) == NULL)
		return FALSE;
	
	// Do application initialization. Return on FALSE
	if(DoInit() == TRUE)
	{
		// Enter the message pump, waiting for signal to quit
		ZeroMemory(&Msg, sizeof(MSG));
		while(Msg.message != WM_QUIT) {
			if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&Msg);
				DispatchMessage(&Msg);
			}
			else
			{
				// Do pre-frame processing. Break on FALSE return value
				if(DoPreFrame() == FALSE) break;
				// Do per-frame processing. Break on FALSE return value
				if (DoFrame () == FALSE) break;
				// Do post-frame processing. Break on FALSE return value
				if(DoPostFrame() == FALSE) break;
			}
		}
	}
	
	// Do shutdown functions
	DoShutdown();
	
	// Unregister window
	UnregisterWindowClasses(hInst);
	return TRUE;
} // закрывающая скобка функции WinMain

BOOL RegisterWindowClasses(HINSTANCE hInst) // Авторская функция-обёртка (wrapper-function).
{
	WNDCLASSEX wcex;
	
	// Create the window class here and register it
	wcex.cbSize	= sizeof(wcex);
	wcex.style	= CS_CLASSDC;
	wcex.lpfnWndProc = WindowProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance	= hInst;
	wcex.hIcon	= LoadIcon(NULL, IDI_APPLICATION);
	wcex.hCursor	= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = NULL;
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = g_szClass;
	wcex.hIconSm	= LoadIcon(NULL, IDI_APPLICATION);
	
	if(!RegisterClassEx(&wcex)) return FALSE;
	
	return TRUE;
}

BOOL UnregisterWindowClasses(HINSTANCE hInst)
{
	// Unregister the window class
	UnregisterClass(g_szClass, hInst);
	return TRUE;
}

HWND CreateMainWindow(HINSTANCE hInst) // Авторская функция-обёртка (wrapper-function).
{
	HWND hWnd;
	
	// Create the Main Window
	hWnd = CreateWindow(g_szClass, g_szCaption, WNDTYPE, 0, 0,
		WNDWIDTH, WNDHEIGHT, NULL, NULL, hInst, NULL );
	
	if(!hWnd)
		return FALSE;
	
	ShowWindow(hWnd, SW_NORMAL);
	UpdateWindow(hWnd);
	
	return hWnd;
}

void AppError(BOOL Fatal, char *Text, ...)
{
	char CaptionText[12];
	char ErrorText[2048];
	va_list valist;
	
	// Создаём заголовок окна ошибки на основе fatal flag.
	if(Fatal == FALSE)
	{
		strcpy(CaptionText, "Error");
	}
	else
	{
		strcpy(CaptionText, "Fatal Error");
	}
	
	// Build variable text buffer
	va_start(valist, Text);
	vsprintf(ErrorText, Text, valist);
	va_end(valist);
	
	// Display the message box
	MessageBox(NULL, ErrorText, CaptionText, MB_OK | MB_ICONEXCLAMATION);
	
	// Посылаем окну сообщение QUIT, если ошибка была фатальной.
	if(Fatal == TRUE) PostQuitMessage(0);
}

// The message procedure. Empty except for destroy message
long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL DoInit()
{
	// Здесь выполняем функции инициализации приложения.
	// Например те, что настраивают графику, звук, сеть и т.д.
	// В случае успеха возвращает TRUE. При неудачном выполнении - FALSE.
	return TRUE;
}

BOOL DoShutdown()
{
	// Здесь выполняем функции завершения работы приложения.
	// Например те, что освобождают память от графики, звука, сети и т. д.
	// В случае успеха возвращает TRUE. При неудачном выполнении - FALSE.
	return TRUE;
}

BOOL DoPreFrame()
{
	// Здесь выполняем функции предкадровой обработки.
	// Например устанавливаем таймер.
	// В случае успеха возвращает TRUE. При неудачном выполнении - FALSE.
	return TRUE;
}

BOOL DoFrame()
{
	// Здесь выполняем функции покадровой обработки.
	// Например рендеринг.
	// В случае успеха возвращает TRUE. При неудачном выполнении - FALSE.
	return TRUE;
}

BOOL DoPostFrame()
{
	// Здесь выполняем функции послекадровой обработки.
	// Например синхронизация времени.
	// В случае успеха возвращает TRUE. При неудачном выполнении - FALSE.
	return TRUE;
}

  • Замени исходный код в WinMain.cpp Проекта Shell01 на приведённый выше.
  • Перекомпилируй решение.
Приложение покажет окно с белым фоном.

Исследуем код WinMain.cpp (версия 2)

Весь код WinMain.cpp инициализирует окно приложения и входит в цикл выборки сообщений (message pump), ожидая единственного системного сообщения WM_QUIT, завершающего работу программы. Весь листинг построен так: сначала объявления/вызов функций, а в конце их реализация. В конце оказалось даже заполнение оконного класса. Этим и отличается профессиональный код от кода из учебников по C++ для начинающих. Все движковые функции размещены так, чтобы программер мог легко их обслуживать без ущерба стабильности. При заполнении оконного класса мы прописали NULL в элементе hbrBackground, отвечающего за фоновую заливку окна:
Фрагмент WinMain.cpp
...
wcex.hbrBackground = NULL;
...

Это норма для DirectX Graphics приложений, т.к. именно этот компонент будет "заведовать" заливкой окна в будущем. Тем не менее при такой настройке по факту получили белую заливку окна.
Многие параметры, как например ширина (width), высота (height) и тип окна, объявлены в начале листинга в виде констант. Это удобно для их быстрого обнаружения в коде и "централизованного" внесения изменений. Также в константы вынесены название оконного класса (window class) и заголовок (caption) окна. Самый интересный фрагмент кода это цикл выборки сообщений:
Фрагмент WinMain.cpp
...
	// Do application initialization. Return on FALSE
	if(DoInit() == TRUE)
	{
		// Enter the message pump, waiting for signal to quit
		ZeroMemory(&Msg, sizeof(MSG));
		while(Msg.message != WM_QUIT) {
			if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&Msg);
				DispatchMessage(&Msg);
			}
			else
			{
				// Do pre-frame processing. Break on FALSE return value
				if(DoPreFrame() == FALSE) break;
				// Do per-frame processing. Break on FALSE return value
				if (DoFrame () == FALSE) break;
				// Do post-frame processing. Break on FALSE return value
				if(DoPostFrame() == FALSE) break;
			}
		}
	}
	
	// Do shutdown functions
	DoShutdown();
	
	// Unregister window
	UnregisterWindowClasses(hInst);
	return TRUE;
...

Сам цикл обрамлён в условный переход, где проверяется выполнение функции DoInit. Если на ней программа "споткнулась", значит всё остальное - "тлен". В остальном это стандартный цикл выборки сообщений с функциями TranslateMessage и DispatchMessage. Через оператор else (англ. "ещё") добавлены вызовы "движковых" функций DoPreFrame, DoFrame, DoPostFrame, каждая из которых также проверяется на успешность выполнения.
Перед снятием регистрации оконного класса идёт вызов движковой функции DoShutdown, выполняющей очищающие операции. В конце листинга идут (пока пустые) реализации всех вышеперечисленных движковых функций.

Функция AppError

  • Авторская функция, которая (пока) нигде в коде не вызывается (только объявляется).
  • Применяется для отображения пользователю информативных сообщений об ошибках.
Фрагмент WinMain.cpp
...
void AppError(BOOL Fatal, char *Text, ...)
{
	char CaptionText[12];
	char ErrorText[2048];
	va_list valist;
	
	// Создаём заголовок окна ошибки на основе fatal flag.
	if(Fatal == FALSE)
	{
		strcpy(CaptionText, "Error");
	}
	else
	{
		strcpy(CaptionText, "Fatal Error");
	}
	
	// Build variable text buffer
	va_start(valist, Text);
	vsprintf(ErrorText, Text, valist);
	va_end(valist);
	
	// Display the message box
	MessageBox(NULL, ErrorText, CaptionText, MB_OK | MB_ICONEXCLAMATION);
	
	// Посылаем окну сообщение QUIT, если ошибка была фатальной.
	if(Fatal == TRUE) PostQuitMessage(0);
}
...

Все ошибки поделены на 2 вида: Fatal и не очень. При передаче параметру Fatal значения TRUE приложение принудительно завершает работу. При значении FALSE приложению разрешено продолжить работу после закрытия всплывающего окна сообщения об ошибке. Грамотная обработка возникающих ошибок также является признаком профессионализма.

Источники


1. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press, 2002


Последние изменения страницы Четверг 23 / Июнь, 2022 14:35:41 MSK

Последние комментарии wiki

No records to display

Search Wiki Page

Точное совпадение

Категории

|--> C#
|--> C++