Загрузка...
 
Печать
Программируем RPG (Win32, C++, DirectX 8)

1. Создаём ядро игры


Intro

Вообще, ядро будет не одно, а целых 5:1

  • System Core (Системное ядро)
  • Graphics Core (Графическое ядро)
  • Input Core (Ядро ввода)
  • Sound Core (Звуковое ядро)
  • Network Core (Сетевое ядро).

Все они будут заключены в одной lib-библиотеке, подключаемой к игровому Проекту через контестное меню MS Visual C++ 2010.

Устанавливаем Microsoft Windows SDK for Windows 7

Windows SDK содержит в себе набор необходимых заголовков, библиотек, документации и утилит для создания приложений под ОС Windows 7. На Windows SDK опирается MS Visual Studio при разработке приложений в том числе под DirectX. Содержит в себе такие важные заголовки, как windows.h, windowsx.h, winnt.h. Без них исходный код большинства Windows-приложений не станет компилироваться.

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

Существуют SDK и для других ОС Windows. То есть, если прогаешь под Win10, погугли Windows SDK под свою ОС. Софт с Win7 без труда пойдёт под Win10.

Данная версия Windows SDK ставится автоматом вместе с MS Visual Studio 2010.
Если нет, то его можно установить отдельно:

Англоязычный инсталлятор не слишком докучает вопросами. Просто жмём Next, Next...
Путь установки по умолчанию (Win7 x64): c:\Program Files\Microsoft SDKs\Windows\v7.1\

Устанавливаем Microsoft DirectX SDK 8

В нашем случае это Microsoft DirectX SDK 8.1 . Представляет собой набор необходимых библиотек, документации и утилит для создания приложений с использованием DirectX. Вышел в свет аж в 2001 г. До сих пор активно применяется разработчиками инди-игр и специализированных приложений. Кладезь информации по программированию графики средствами DirectX посредством неуправляемого (unmanaged, т.е. на чистом C++ без .NET-фреймворка) кода.
В DirectX SDK 8.1 есть куча уникального стафа, тупо вырезанного в последующих версиях. Кроме того, данный курс написан для DirectX 8.

Объём прибл. 164 Мб.

  • Скачанный ZIP-архив извлекаем (распаковываем) в любую папку на жёстком диске и запускаем Setup.exe.

Процесс установки несложен.

  • На странице программы-установщика с лицензионным соглашением поставь галку у пункта 'I Accept the terms of the license agreement' (принимаем его условия вобщем). Жми Next.
  • На странице 'Customer information' в поля 'User Name' и 'Company Name' введи любые значения. Жми Next.

Каталог установки по умолчанию C:\DXSDK можно оставить как есть. Но мы переименуем его в C:\DXSDK8. Для наглядности.
Список устанавливаемых компонентов тоже оставь как есть. Пусть ставится всё. Жми Next.

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

Желательно также скачать и установить (в отдельную папку) последнюю версию DirectX SDK с сайта Microsoft. На всякий случай.


DirectX SDK 8.1 не имеет своей программной оболочки. Для просмотра содержимого открой соответствующий каталог (в нашем случае C:\DXSDK8) с помощью Проводника Windows.
В соответствующих каталогах расположены файлы:

bin Внутри него расположен единственный каталог DXUtils, содержащий различный утилиты (=вспомогательные программы). Самая интересная из них - mview.exe (Mesh viewer, просмотрщик моделей формата .x). Позднее мы не раз увидим её в деле.
doc Документация по DirectX SDK. Всё на английском языке. Размещена в двух CHM-файлах (файлы справки Windows): directx8_c для языка программирования C++ и directx8_vb для языка программирования Visual Basic. Компилированные файлы справки Windows (CHM) имеют удобную древовидную навигацию и поиск. Напомним, в данном курсе весь исходный код представлен на языке C++.
include Заголовочные файлы для импорта функций DirectX. Самый нужный каталог. Путь к нему мы чуть позднее укажем в настройках MS Visual C++.
lib Библиотеки DLL, содержащие функции DirectX. Самый нужный каталог. Путь к нему мы чуть позднее укажем в настройках MS Visual C++.
Redist Т.н. "библиотека времени выполнения". Необходима для запуска игр, спрограммированных под MS DirectX, на ПК. Включена по умолчанию во все версии ОС MS Windows XP/Vista/7. Но на всякий случай добавлена в DirectX SDK.
samples 109 мегабайт примеров! Разложены по папкам в виде проектов, у каждого из которых есть 2 главных файла: dsp-файл проекта и dsw-файл (т.н. воркспейс, англ. workspace - рабочее пространство). Оба они являются "родными" для актуальной на тот момент MS Visual C++ 6. В принципе они равнозначны, отличаются нюансами. dsw-файлы служат для объединения нескольких проектов (=dsp-файлов) в группу. Если открыть dsw-файл в блокноте, то в нём можно увидеть путь к dsp-файлу. Dsw-воркспейсы открываются и компилируются в исполняемый .exe файл в (правильно сконфигурированной) среде MS Visual Studio 6.0 (1998 года).

Создаём пустой Проект Core

  • Стартуй MS Visual Studio 2010, если не сделал этого ранее.

Видим Окно приветствия (т.е. в IDE ни один Проект пока не открыт).

  • Создай новый пустой Проект Win32. (В Главном меню: Файл->Создать->Проект...).

В окне "Создать Проект":

  • Отмечаем щелчком пункт "Проект Win32".
  • В поле Имя введи Test01.

Строки "Расположение" и "Имя Решения" заполняются автоматически (при необходимости изменяем).
Под Проект Test01 будет автоматически создано Решение с тем же именем.
Image

  • Жмём ОК.

Появится окно Мастера приложений.

  • Жмём Далее.

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

  • На странице "Параметры приложения" отмечаем пункт "Статическая библиотека" и убираем галку у пункта "Предварительно скомпилированный заголовок":

Image

  • Жмём "Готово".

При создании наш Проект автоматически размещается внутри Решения с тем же именем. В результате в левой колонке обозревателя решений видим Решение Test01, которое содержит Проект Test01.
Это неправильно, так как согласно замысла, внутри Решения Test01 будут содержаться два Проекта: Core (ядро, движок; статическая библиотека с расширением .lib) и Game (игра на базе этого движка; файл приложения с расширением .exe). Напомним, что в первой части курса мы разрабатываем движок.

  • Поэтому смени имя вновь созданного Проекта с Test01 на Core (правой кнопкой мыши по названию Проекта -> из контекстного меню выбрать "Переименовать").
Переименовываем Проект GameProject01 в Engine
Переименовываем Проект GameProject01 в Engine

Решение тоже переименуй на своё усмотрение.

  • Сохрани Решение (File -> Save All).


Проект создан. Так как это "Пустой проект", он не содержит в себе никаких файлов. В левой части главного окна MS Visual C++ 2010 расположен "Обозреватель решений". (Если его нет, в главном меню выбираем: Вид->Другие окна->Обозреватель решений. Или комбинация горячих клавиш Ctrl+Alt+L.) В Обозревателе решений видна древовидная структура Проектов, входящих в данное Решение. Чуть ниже названия Проекта видим специально заготовленные папки (в MSVC++2010 они называются "фильтры") для файлов Проекта.

Настраиваем Проект Core

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

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

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

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

Image
Напомним, что такую настройку необходимо повторно проделывать при создании каждого нового Проекта.

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

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

Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден

Отключается в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:

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

Создаём Core_Global.h

Напомним, что именно Core_Global.h будет включать в себя ссылки на остальные заголовки движка и, таким образом, будет использоваться в качестве единой точки контакта.

  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы".
  • Во всплывающем меню Добавить->Создать элемент...

Image

  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи Core_Global.h.
  • Жмём "Добавить".

Image
Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле Core_Global.h набираем следующий код:
Core_Global.h
/**************************************************
Core_Global.h GameCore Component

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

#ifndef _CORE_GLOBAL_H_
#define _CORE_GLOBAL_H_

//-----------------------------------
// Макроопределение на использование 8 версии DirectInput (Функционала 8 версии будет предостаточно)
//-----------------------------------
#define DIRECTINPUT_VERSION 0x0800

// Windows includes
#include <windows.h>

// Standard ANSI-C includes
#include <stdio.h>

// DirectX includes
#include "d3d8.h"
#include "d3dx8.h"
#include "dmusici.h"
#include "dsound.h"
#include "dplay8.h"
#include "dpaddr.h"
#include "dinput.h"

// Core includes
#include "Core_System.h"

#endif

  • Сохрани созданное Решение и входяшие в него Проекты (Файл -> Сохранить все).


Содержит в себе кучу инклудов из DirectX SDK 8.1.

Создаём Core_System.h

  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы".
  • Во всплывающем меню Добавить->Создать элемент...

Image

  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи Core_System.h.
  • Жмём "Добавить".

Image
Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле Core_System.h набираем следующий код:
Core_System.h
/**************************************************
Core_System.h Заголовок Системного ядра.
Содержит объявления классов и функций, чьи реализации
размещены в Core_System.cpp

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

#ifndef _SYSTEM_H_
#define _SYSTEM_H_

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;
    DWORD         m_YPos;
    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 LRESULT CALLBACK 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; }
};

static cApplication *g_pApplication = NULL;
static long FAR PASCAL AppWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

enum Purposes {
    NOPURPOSE = 0,
    INITPURPOSE,
    SHUTDOWNPURPOSE,
    FRAMEPURPOSE
  };
    
class cStateManager
{
  typedef struct sState {
    void (*Function)(void *Ptr, long Purpose);
    sState *Next;

    sState()
    {
      Function = NULL;
      Next = NULL;
    }

    ~sState()
    {
      delete Next;
    }
  } 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);
};

class cProcessManager
{
  typedef struct sProcess {
    void  (*Function)(void *Ptr, long Purpose);
    sProcess *Next;

    sProcess()
    {
      Function = NULL;
      Next = NULL;
    }

    ~sProcess()
    {
      delete Next;
    }
  } sProcess;

  protected:
    sProcess *m_ProcessParent; 

  public:
    cProcessManager();
    ~cProcessManager();

    void Push(void (*Process)(void *Ptr, long Purpose), void *DataPtr = NULL);
    BOOL Pop(void *DataPtr = NULL);
    void PopAll(void *DataPtr = NULL);
    void Process(void *Ptr = NULL);
};

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();
}; 

#endif

  • Сохрани созданное Решение и входяшие в него Проекты (Файл -> Сохранить все).

Исследуем код Core_System.h

Помимо всего прочего, в Core_System.h объявлены 3 важных класса:

  • cStateManager (менеджер стейтов),
  • cProcessManager (менеджер процессов),
  • cDataPackage (система сохранения и загрузки данных).

Три данных класса в том или ином виде почти повсеместно применяются при создании каркаса (=framework) игрового движка. Подробнее о них читай в статье Программный поток (Program Flow). Чуть ниже мы увидим, как файле исходного кода Core_System.cpp будут созданы (пустые) объекты данных классов.

Создаём Core_System.cpp

Если помнишь, для написания хорошо структурированного кода мы размещаем объявления всех классов и функций в заголовочных файлах (.h), а их реализацию в файлах исходного кода (.cpp), в которых также прописываем ссылку на "сопровождающий" его хэдер (=заголовочный файл .h).
Создадим файл исходного кода Core_System.cpp, в котором будут содержаться реализации функций, объявленных в заголовочном файле Core_System.h:

  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Core.
  • Во всплывающем меню Добавить->Создать элемент...
Добавляем исходный файл
Добавляем исходный файл
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи Core_System.cpp.
  • Жмём "Добавить".

Image
Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле Engine.cpp набираем следующий код:
Core_System.cpp
/**************************************************
Core_system.cpp
GameCore Component

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

#include "Core_Global.h"

cApplication::cApplication()
{
	// Save instance handle. Сохраняем дескриптор инстанса.
  g_pApplication = this;

  // Get the instance handle. Получаем дескриптор инстанса.
  m_hInst = GetModuleHandle(NULL);

  // Set a default window class and caption
  strcpy(m_Class, "AppClass");
  strcpy(m_Caption, "Application Caption");

  // Set default window style, position, width, height
  m_Style  = WS_OVERLAPPEDWINDOW;
  m_XPos   = 0;
  m_YPos   = 0;
  m_Width  = 256;
  m_Height = 256;

  // Set default WNDCLASSEX structure. Заполняем структуру оконного класса WNDCLASSEX.
  m_wcex.cbSize        = sizeof(WNDCLASSEX);
  m_wcex.style         = CS_CLASSDC;
  m_wcex.lpfnWndProc   = AppWindowProc;
  m_wcex.cbClsExtra    = 0;
  m_wcex.cbWndExtra    = 0;
  m_wcex.hInstance     = m_hInst;
  m_wcex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  m_wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
  m_wcex.hbrBackground = NULL;
  m_wcex.lpszMenuName  = NULL;
  m_wcex.lpszClassName = m_Class;
  m_wcex.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
}

HWND cApplication::GethWnd()
{
  return m_hWnd;
}

HINSTANCE cApplication::GethInst()
{
  return m_hInst;
}

BOOL cApplication::Run()
{
  MSG Msg;

  // Register window class
  if(!RegisterClassEx(&m_wcex))
    return FALSE;

  // Create the Main Window
  m_hWnd = CreateWindow(m_Class, m_Caption,
        m_Style, 
        m_XPos, m_YPos,
        m_Width, m_Height,
        NULL, NULL, m_hInst, NULL);
  if(!m_hWnd)
    return FALSE;

  // Show and update the window
  ShowWindow(m_hWnd, SW_NORMAL);
  UpdateWindow(m_hWnd);

  // Make sure client area is correct size. Проверяем, что клиентская область окна нужного размера.
  Resize(m_Width, m_Height);

  // Initialize COM
  CoInitialize(NULL);

  if(Init() == TRUE) {
     // Enter the message pump. Входим в цикл выборки сообщений.
    ZeroMemory(&Msg, sizeof(MSG));
    while(Msg.message != WM_QUIT) {

      // Handle Windows messages (if any). Обрабатываем сообщения Windows (если таковые есть).
      if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
      } else {
        // Do per-frame processing, break on FALSE return value
		// Выполняем ежекадровый процессинг. Выходим, если текущий фрейм - FALSE.
        if(Frame() == FALSE)
          break;
      }
    }
  }

  Shutdown();

  // Shutdown COM
  CoUninitialize();

  // Unregister the window class
  UnregisterClass(m_Class, m_hInst);

  return TRUE;
}

BOOL cApplication::Error(BOOL Fatal, char *Text, ...)
{
  char CaptionText[12];
  char ErrorText[2048];
  va_list valist;

  // Build the message box caption based on 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);

  // Post a quit message if error was fatal
  if(Fatal == TRUE)
    PostQuitMessage(0);

  return TRUE;
}

BOOL cApplication::Move(long XPos, long YPos)
{
  RECT ClientRect;

  GetClientRect(m_hWnd, &ClientRect);
  MoveWindow(m_hWnd, XPos, YPos, ClientRect.right, ClientRect.bottom, TRUE);

  return TRUE;
}

BOOL cApplication::Resize(long Width, long Height)
{
  RECT WndRect, ClientRect;
  long WndWidth, WndHeight;

  GetWindowRect(m_hWnd, &WndRect);
  GetClientRect(m_hWnd, &ClientRect);

  WndWidth  = (WndRect.right  - (ClientRect.right  - Width))  - WndRect.left;
  WndHeight = (WndRect.bottom - (ClientRect.bottom - Height)) - WndRect.top;

  MoveWindow(m_hWnd, WndRect.left, WndRect.top, WndWidth, WndHeight, TRUE);

  return TRUE;
}

BOOL cApplication::ShowMouse(BOOL Show)
{
  ShowCursor(Show);
  return TRUE;
}

// The message procedure - empty except for destroy message.
// Стандартная процедура выборки сообщений. Пуста, за исключением сообщения DESTROY, закрывающем окно.
long FAR PASCAL AppWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

    default: return g_pApplication->MsgProc(hWnd, uMsg, wParam, lParam);
  }
}

cStateManager::cStateManager() 
{ 
  m_StateParent = NULL; 
}

cStateManager::~cStateManager() 
{
  PopAll();
}

// Push a function on to the stack
void cStateManager::Push(void (*Function)(void *Ptr, long Purpose), void *DataPtr)
{
  sState *StatePtr;

  // Don't push a NULL value
// Не помещать нулевое значение
  if(Function != NULL) {
    // Allocate a new state and push it on stack
	// Создать новый стейт и поместить его в стэк.
    StatePtr = new sState();

    StatePtr->Function = Function;
    StatePtr->Next = m_StateParent;

    m_StateParent = StatePtr;

    // Call state with init purpose
    StatePtr->Function(DataPtr, INITPURPOSE);
  }
}

BOOL cStateManager::Pop(void *DataPtr)
{
  sState *StatePtr;

  // Remove the head of stack (if any)
  if((StatePtr = m_StateParent) != NULL) {
    // First call with shutdown purpose
    m_StateParent->Function(DataPtr, SHUTDOWNPURPOSE);

    m_StateParent = StatePtr->Next;
    StatePtr->Next = NULL;
    delete StatePtr;
  }

  // return TRUE if more states exist, FALSE otherwise
  if(m_StateParent == NULL)
    return FALSE;
  return TRUE;
}

void cStateManager::PopAll(void *DataPtr)
{
  while(Pop(DataPtr) == TRUE);
}

BOOL cStateManager::Process(void *DataPtr)
{ 
  // return an error if no more states
  if(m_StateParent == NULL)
    return FALSE;

  // Process the top-most state
  m_StateParent->Function(DataPtr, FRAMEPURPOSE); 

  return TRUE;
}

cProcessManager::cProcessManager() 
{ 
  m_ProcessParent = NULL; 
}

cProcessManager::~cProcessManager() 
{
  // Pop each process
  while(Pop()==TRUE);
}

// Push a function on to the stack
void cProcessManager::Push(void (*Process)(void *Ptr, long Purpose), void *DataPtr)
{
  // Don't push a NULL value
  if(Process != NULL) {
    // Allocate a new process and push it on stack
    sProcess *ProcessPtr = new sProcess();
    ProcessPtr->Function = Process;
    ProcessPtr->Next = m_ProcessParent;
    m_ProcessParent = ProcessPtr;

    // Call process with init purpose
    ProcessPtr->Function(DataPtr, INITPURPOSE);
  }
}

// Pop top process from stack
BOOL cProcessManager::Pop(void *DataPtr)
{
  sProcess *ProcessPtr;

  // Remove the head of stack (if any)
  if((ProcessPtr = m_ProcessParent) != NULL) {
    // First call with shutdown purpose
    m_ProcessParent->Function(DataPtr, SHUTDOWNPURPOSE);

    m_ProcessParent = ProcessPtr->Next;
    ProcessPtr->Next = NULL;
    delete ProcessPtr;
  }

  // return TRUE if more processes exist, FALSE otherwise
  if(m_ProcessParent == NULL)
    return FALSE;
  return TRUE;
}

void cProcessManager::PopAll(void *DataPtr)
{
  while(Pop(DataPtr) == TRUE);
}

// Process all functions
void cProcessManager::Process(void *DataPtr)
{ 
  sProcess *ProcessPtr = m_ProcessParent;

  while(ProcessPtr != NULL) {
    ProcessPtr->Function(DataPtr, FRAMEPURPOSE);
    ProcessPtr = ProcessPtr->Next;
  }
}

cDataPackage::cDataPackage()  
{  
  m_Buf = NULL; 
  m_Size = 0; 
}

cDataPackage::~cDataPackage() 
{ 
  Free(); 
}

void *cDataPackage::Create(unsigned long Size)
{
  // Free a previously created buffer. Обнуляем ранее созданный буфер.
  Free();

  // Allocate some memory and return a pointer
  return (m_Buf = (void*)new char[(m_Size = Size)]);
}

// Free the allocated memory
void cDataPackage::Free() 
{ 
  delete m_Buf; 
  m_Buf = NULL; 
  m_Size = 0; 
}

BOOL cDataPackage::Save(char *Filename)
{
  FILE *fp;

  // Make sure there's something to write
// Проверим, что в самом деле есть, что записывать.
  if(m_Buf != NULL && m_Size) {
    // Open file, write size and data
    if((fp=fopen(Filename, "wb")) != NULL) {
      fwrite(&m_Size, 1, 4, fp);
      fwrite(m_Buf, 1, m_Size, fp);
      fclose(fp);
      return TRUE;
    }
  }

  return FALSE;
}

void *cDataPackage::Load(char *Filename, unsigned long *Size)
{
  FILE *fp;

  // Free a prior buffer
  Free();

  if((fp=fopen(Filename, "rb"))!=NULL) {
    // Read in size and data
    fread(&m_Size, 1, 4, fp);
    if((m_Buf = (void*)new char[m_Size]) != NULL)
      fread(m_Buf, 1, m_Size, fp);
    fclose(fp);

    // Store size to return
    if(Size != NULL)
      *Size = m_Size;

    // return pointer
    return m_Buf;
  }

  return NULL;
}

void *cDataPackage::GetPtr()
{
  return m_Buf;
}

unsigned long cDataPackage::GetSize()
{
  return m_Size;
}

  • Сохрани Решение (Файл -> Сохранить все).


Для тех, кто скажет "чё так много", спешим напомнить, что это, по сути, пустой каркас, куда позднее будут добавляться другие элементы.

Исследуем код Core_System.cpp


Следуя ранее утверждённой концепции "единой точки контакта", подключаем заголовок Core_Global.h, который уже содержит ссылки на все остальные заголовочные файлы:

Фрагмент файла Core_System.cpp
...
#include "Core_Global.h"
...

В конце листинга видим реализацию трёх классов, которые пригодятся нам в будущем:

  • cStateManager (менеджер стейтов),
  • cProcessManager (менеджер процессов),
  • cDataPackage (система работы с данными приложения).

Все они подробно рассматриваются в статье Программный поток (Program Flow).

Настраиваем Проект Core: указываем пути к DirectX SDK 8.1

Если попытаться скомпилировать Проект Core в таком виде, то ничего не выйдет.
В заголовке Core_Global.h можно увидеть инклуды различных заголовочных файлов DirectX (весии 8). MSVC++2010 ничего не знает об их местоположении. В статье Настройка MS Visual C plus plus 2010 и DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Для DirectX SDK 8 это делается аналогично. Начнём.

  • В Обозревателе решений видим: "Решение "Test01"", а строкой ниже жирным шрифтом название Проекта (Core). Щелчок правой кнопкой мыши по названию Проекта Core. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.

Image

  • В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++. В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) DirectX SDK 8.1

  • Щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."

Image

  • В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам DirectX SDK 8.1. В нашем случае это C:\DXSDK8\include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Жмём "ОК".

Указываем каталог библиотек (lib) DirectX SDK 8.1

  • Щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к 32-разрядным версиям файлов библиотек (lib) DirectX SDK 8.1. В нашем случае это c:\DXSDK8\lib\. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
  • Жмём "ОК".

Image

  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All).

Готово.

Настраиваем Проект Core: указываем пути к Windows SDK

Если попытаться скомпилировать Проект Core в таком виде, то ничего не выйдет. Процесс компилирования "споткнётся" о множество ошибок. А всё из-за неверного порядка указания дополнительных каталогов вложений (include) и библиотек (lib). Вообще, помимо указания путей к DirectX SDK, также необходимо аналогичным образом указать пути к заголовкам (include) и библиотекам (lib) Windows SDK. Самое смешное, что в MS Visual C++ 2010 эти пути указываются по умолчанию для каждого создаваемого Проекта. В этом нетрудно убедиться, если ещё раз открыть Проект -> Свойства -> Свойства конфигурации - >Каталоги VC++ -> Каталоги включения -> Изменить. В окне "Каталоги включения" в нижней части видим список "Унаследованные значения", где в третьей строке стоит значение:

$(WindowsSdkDir)include

Однако менять порядок перечисления элементов данного списка мы не можем - ограничение бесплатной версии MSVC++2010 Express.
Но заголовочным файлам DirectX SDK 8.1 "жизненно важно", чтобы они включались после включений заголовков Windows SDK.
Поэтому, аналогично тому, как мы указывали пути к каталогам включения (include) и библиотек (lib) DirectX SDK 8.1, добавим ещё раз пути к таким же каталогам для Windows SDK.

Закрыть
noteВажно!

Каталоги, пути к которым указаны в окнах "Каталоги включения" и "Каталоги библиотек" при компиляции считываются один за другим по списку сверху вниз. Поэтому для корректного указания путей размести пути к Windows SDK выше, а к DirectX SDK 8.1 - ниже по списку (чтобы они считывались последними).

Выше мы указывали пути к DirectX SDK 8.1. Для Windows SDK это делается аналогично. Начнём.

  • В Обозревателе решений видим: "Решение "Test01"", а строкой ниже жирным шрифтом название Проекта (Core). Щелчок правой кнопкой мыши по названию Проекта Core. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.

Image

  • В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++. В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) Windows SDK

  • Щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."

Image

  • В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".

Каталог включений DirectX SDK 8.1 должен всегда стоять в самом конце списка, как на этом скриншоте:
Image

  • Жмём "ОК".

Указываем каталог библиотек (lib) Windows SDK

  • Щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к папке с файлами библиотек (lib) Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".

Каталог включений DirectX SDK 8.1 должен всегда стоять в самом конце списка, как на этом скриншоте:
Image

  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All).

Готово.

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

  • В Обозревателе решений щёлкни правой кнопкой мыши по названию Проекта Core.
  • Во всплывающем контекстном меню выбери Перестроить.

Проект успешно скомпилируется.
Само собой, статическая lib-библиотека в среде Windows не запустится и окна не покажет.
В док-окне Output видим:

1>  Test01.vcxproj -> C:\Users\User1\Documents\Visual Studio 2010\Projects\Test01\Debug\Core.lib
========== Перестроение всех: успешно: 1, с ошибками: 0, пропущено: 0 ==========

Выше также видим кучу предупреждений (Warnings) о том, что многие операторы в исходниках признаны небезопасными (unsafe). Мы знали, на что шли, начиная прогать под DirectX 8 и потому эти предупреждения можно смело игнорировать. Но лучше просто отключить их вывод.

Отключаем предупреждения (warnings) об устаревших (deprecated) или небезопасных (unsafe) операторах и функциях

  • Открой Проект -> Свойства -> С++ -> Препроцессор -> Определения препроцессора -> Изменить
  • В появившемся окне "Определения препроцессора" добавь в конце списка на новой строке:
_CRT_SECURE_NO_WARNINGS

Готово. При перекомпиляции такие сообщения не появляются.

Итоги главы

DirectX SDK 8 изначально "затачивалась" под применение в актуальной на тот момент MS Visual Studio 6.0, стоившей порядка 500 USD (версия Pro). Бесплатные (для некоммерческого использования) версии (Express) этой IDE впервые появились только в 2005 году (MS Visual Studio 2005 Express) с целью популяризации своих платных собратьев... Ну и иксбокса, конечно же.)
В этой статье мы доказали, что бесплатная (для некоммерческого использования) MS Visual C++ 2010 Express (из пакета MS Visual Studio 2010 Express) вполне годится для создания классических Win32-приложений и lib-библиотек. Таким образом игрокодинг в домашних условиях доступен каждому: достаточно наличие компьютера с ОС Windows и доступа в Интернет.
Всё вышенаписанное справедливо и для других версий MS Visual C++ Express (2008, 2015, 2017 и др.).

Источники:


1. Jim Adams. Programming Role Playing Games with DirectX 8.0. - Premier Press. 2001

Contributors to this page: slymentat .
Последнее изменение страницы Пятница 27 / Ноябрь, 2020 23:42:20 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108