ФЭНДОМ


Статическая библиотекаПравить

Далее по урокам будет подразумеваться что фреймворк у вас создан в виде статической библиотеки которую вы будете подключать к проекту урока. Когда я буду писать что нужно внести изменения во фреймворк, вы должны внести эти изменения именно в библиотеку, а не в проект. Также хочу заметить что у меня нет конечной версии фреймворка а значит возможны изменения в ее архитектуре - поэтому для предотвращения проблем, советую для каждого урока заводить новую библиотеку. Кроме того после каждого изменения я буду присваивать новую версию фреймворку, а в уроках будет написано - какая именно версия нужна для этого урока. Номер версии будет расположен в заголовке macros.h, в _VERSION_FRAMEWORK.

Как создать статическую библиотеку, смотрите в предыдущем уроке.

Предкомпилированный заголовокПравить

Для начала давайте напишем заголовок в котором будем подключать стороний код. Сделайте его предкомпилированным (если не знаете что это такое, читайте здесь). У меня этот заголовок имеет стандартное имя - stdafx.h

Напишите в нем следующий код:

#pragma once

#include <clocale>
#include <ctime>

#include <string>
#include <list>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <d3d11.h>
#include <d3dx11.h>

#pragma comment(lib, "d3d11.lib")
#ifdef _DEBUG
#	pragma comment(lib, "d3dx11d.lib")
#else
#	pragma comment(lib, "d3dx11.lib")
#endif

Первая строчка, как я писал, нужна для того чтобы заголовок включался только один раз.Здесь она описана более подробно. Если ваш компилятор не поддерживает ее, используйте вместо нее include guard.

Далее мы подключаем все нужные внешние заголовки – стандартные, STL, WinAPI и DX11.

WIN32_LEAN_AND_MEAN указывает что из заголовка windows.h нужно брать минимум кода, за счет этого уменьшается размер нашего файла и ускоряется компиляция.

Теперь по поводу #pragma comment. С помощью этой директивы мы указываем, что к проекту нужно будет прилинковать d3d11.lib и d3dx11.lib (d3dx11d.lib). Если ваш компилятор не поддерживает такой директивы, то укажите библиотеки через настройки проекта.

МакросыПравить

Создайте заговолок macros.h. В нем будут прописаны несколько общих макросов. Напишите следующий код:

#pragma once

#define _DELETE(p)		{ if(p){delete (p); (p)=nullptr;} }
#define _DELETE_ARRAY(p)	{ if(p){delete[] (p); (p)=nullptr;} }
#define _RELEASE(p)		{ if(p){(p)->Release(); (p)=nullptr;} }
#define _CLOSE(p)		{ if(p){(p)->Close(); delete (p); (p)=nullptr;} }

// Текущая версия фреймворка
#define _VERSION_FRAMEWORK 1

Первые 4 макроса позволяют нам удобней очищать память. _DELETE удаляет указатель. _DELETE_ARRAY удаляет массив указателей. _RELEASE используется для удаления интерфейсов DirectX. _CLOSE - позволяет нам удобней завершить работу наших указателей, то есть вызывает метод Close(), затем удаляет сам указатель.

что такое nullptr? Это пустой указатель. nullptr появился в новом стандарте С++ 11, и его рекомендуют использовать вместо 0 или NULL. Почему, вам скажет гугл. Если ваша IDE не поддерживает эту директиву, то выше напишите:

#define nullptr 0

_VERSION_FRAMEWORK - как я писал выше, версия текущего фреймворка. В статьях, далее будет указана требуемая версия фреймворка. Мы не будем особо мудрить с номерами и просто после каждого изменения будем увеличивать на 1.

Класс LogПравить

Начнем с этого класса. Он предназначен для логирования. Он должен:

  • Создать файл для записи, закрыть его по завершению, в случае любой ошибки произвести запись в файл
  • Быть автономным, то есть самостоятельно создаваться и завершаться до выполнения любого другого кода
  • Вести логирование в файл и в консоль, если она доступна.
  • Три режима логирования – информация, ошибка и debug.
  • Быть доступным из любого места в коде, без громоздких передач между классами объекта лога.

Создайте заголовок Log.h и напишите в нем следующий код:

#pragma once

namespace D3D11Framework
{
//------------------------------------------------------------------

	class Log
	{
	public:
		Log();
		~Log();

		static Log* Get(){return m_instance;}

		void Print(const char *message, ...);
		void Debug(const char *message, ...);
		void Err(const char *message, ...);

	private:
		static Log *m_instance;

		void m_init();
		void m_close();
		void m_print(const char *levtext, const char *text);

		FILE *m_file;	
	};

//------------------------------------------------------------------
}

Весь код фреймворка я буду держать в пространстве имен D3D11Framework.

Конструктор класса будет вызывать приватный метод m_init(), в котором мы будем создавать файл.

Деструктора вызывает m_close(), очищая память.

Теперь про Get(). У нас будет всего лишь один объект этого класса, и используя статическую переменную мы всегда сможем обратиться к нему. Последние три публичных метода нужны для записи. Они вызывают приватный метод m_print().

Использовать этот класс можно вот так:

Log::Get()->Print("Hello World");
Log::Get()->Err("Hell %d %s", 100, "mud");


Создайте теперь файл Log.cpp и напишите следующий код:

#include "stdafx.h"
#include "Log.h"

#define LOGNAME "log.txt"

namespace D3D11Framework
{
//------------------------------------------------------------------
	
	Log *Log::m_instance = nullptr;

	Log::Log()
	{	
		if (!m_instance)
		{
			m_file = nullptr;
			m_instance = this;
			m_init();
		}
		else
			Err("Log уже был создан");
	}

	Log::~Log()
	{
		m_close();
		m_instance = nullptr;
	}

	void Log::m_init()
	{	
		setlocale(LC_ALL, "rus");

		if( fopen_s(&m_file, LOGNAME, "w") == 0 )
		{
			char timer[9];
			_strtime_s(timer,9);
			char date[9];
			_strdate_s(date,9);
			fprintf(m_file, "Лог создан: %s %s.\n", date, timer);
			fprintf(m_file, "---------------------------------------\n\n");
		}		
		else
		{
			printf("Ошибка при создании файла лога...\n");
			m_file = nullptr;
		}
	}

	void Log::m_close()
	{
		if (!m_file)
			return;

		char timer[9];
		_strtime_s(timer,9);
		char date[9];
		_strdate_s(date,9);
		fprintf(m_file, "\n---------------------------------------\n");
		fprintf(m_file, "Конец лога: %s %s.", date, timer);
		fclose(m_file);
	}

	void Log::Print(const char *message, ...)
	{
		va_list args;
		va_start(args, message);
		int len = _vscprintf( message, args ) + 1;
		char *buffer = static_cast<char*>( malloc(len*sizeof(char)) );
		vsprintf_s( buffer, len, message, args );
		m_print("", buffer);
		va_end(args);
		free(buffer);
	}

	void Log::Debug(const char *message, ...)
	{
#ifdef _DEBUG
		va_list args;
		va_start(args, message);
		int len = _vscprintf( message, args ) + 1;
		char *buffer = static_cast<char*>( malloc(len*sizeof(char)) );
		vsprintf_s( buffer, len, message, args );
		m_print("*DEBUG: ", buffer);
		va_end(args);
		free(buffer);
#endif
	}
	void Log::Err(const char *message, ...)
	{
		va_list args;
		va_start(args, message);
		int len = _vscprintf( message, args ) + 1;
		char *buffer = static_cast<char*>( malloc(len*sizeof(char)) );
		vsprintf_s( buffer, len, message, args );
		m_print("*ERROR: ", buffer);
		va_end(args);
		free(buffer);
	}

	void Log::m_print(const char *levtext, const char *text)
	{
		char timer[9];
		_strtime_s(timer,9);
		clock_t cl = clock();

		printf("%s::%d: %s%s\n", timer, cl, levtext, text); 
		if(m_file)
		{
			fprintf(m_file, "%s::%d: %s%s\n", timer, cl, levtext, text);
			fflush(m_file);
		}
	}

//------------------------------------------------------------------
}

В начале мы подключаем все нужные заголовки. Обратите внимание что заголовок stdafx.h выше остальных, если это будет не так – компилятор сообщит об ошибке.

Далее мы указываем, что наша статическая переменная пока никуда не указывает.

В конструкторе мы проверяем, не был ли Log создан ранее. Если лог не был создан, то выполняем метод m_init(). Иначе сообщаем об ошибке.

В деструкторе мы завершаем работу класса.

Метод m_init() выполняет всю инициализацию. Первая строчка включает русскую локаль, благодаря чему мы можем писать в консоль русский текст. Далее, мы создаем файл для лога. При успехе, узнаем текущее время и дату и пишем их в файл.

Далее рассмотрю только метод Log::Debug(). Сначала мы проверяем, в какой конфигурации собирается проект. _DEBUG автоматически определяется компилятором, если текущая конфигурация – debug. В релизе этого кода просто не будет. Далее, мы получаем все переданные аргументы (их может быть много). После, считаем длину строки и пишем ее в len. Узнав длину, мы выделяем кусок памяти используя malloc, и переносим в этот кусок (buffer) нашу строку и все аргументы. Затем мы вызываем приватный метод Log::m_print(). Первым аргументом мы передаем режим лога, а вторым готовую строчку.

В методе Log::m_print(). Мы узнаем текущее время и выведем все в консоль и файл.

Система вводаПравить

Теперь давайте поработаем с системой ввода. Создайте заголовок InputCodes.h и напишите следующий код:

#pragma once

namespace D3D11Framework
{
//------------------------------------------------------------------

	enum eMouseKeyCodes
	{
		MOUSE_LEFT = 0,
		MOUSE_MIDDLE,
		MOUSE_RIGHT,

		MOUSE_MAX
	};

	enum eKeyCodes
	{
		KEY_LBUTTON     = 0x01, // Left mouse button
		KEY_RBUTTON     = 0x02, // Right mouse button
		KEY_CANCEL      = 0x03, // Control-break processing
		KEY_MBUTTON     = 0x04, // Middle mouse button (three-button mouse)
		KEY_XBUTTON1    = 0x05, // X1 mouse button
		KEY_XBUTTON2    = 0x06, // X2 mouse button
		KEY_BACK        = 0x08, // BACKSPACE key
		KEY_TAB         = 0x09, // TAB key
		KEY_CLEAR       = 0x0C, // CLEAR key
		KEY_RETURN      = 0x0D, // ENTER key
		KEY_SHIFT       = 0x10, // SHIFT key
		KEY_CONTROL     = 0x11, // CTRL key
		KEY_ALT	        = 0x12, // ALT key
		KEY_PAUSE       = 0x13, // PAUSE key
		KEY_CAPITAL     = 0x14, // CAPS LOCK key
		KEY_KANA        = 0x15, // IME Kana mode
		KEY_HANGUEL     = 0x15, // IME Hanguel mode
		KEY_HANGUL      = 0x15, // IME Hangul mode
		KEY_JUNJA       = 0x17, // IME Junja mode
		KEY_FINAL       = 0x18, // IME final mode
		KEY_HANJA       = 0x19, // IME Hanja mode
		KEY_KANJI       = 0x19, // IME Kanji mode

		KEY_ESCAPE      = 0x1B,
		KEY_SPACE       = 0x20,
		KEY_PAGEUP      = 0x21,
		KEY_PAGEDOWN    = 0x22,
		KEY_END         = 0x23,
		KEY_HOME        = 0x24,
		KEY_LEFT        = 0x25, // left arrow key
		KEY_UP          = 0x26, // up arrow key
		KEY_RIGHT       = 0x27, // right arrow key
		KEY_DOWN        = 0x28, // down arrow key
		KEY_SELECT      = 0x29,
		KEY_EXE         = 0x2B, // execute key
		KEY_SNAPSHOT    = 0x2C,
		KEY_INSERT      = 0x2D,
		KEY_DELETE      = 0x2E,
		KEY_HELP        = 0x2F,

		KEY_0           = 0x30,
		KEY_1           = 0x31,
		KEY_2           = 0x32,
		KEY_3           = 0x33,
		KEY_4           = 0x34,
		KEY_5           = 0x35,
		KEY_6           = 0x36,
		KEY_7           = 0x37,
		KEY_8           = 0x38,
		KEY_9           = 0x39,

		KEY_A           = 0x41,
		KEY_B           = 0x42,
		KEY_C           = 0x43,
		KEY_D           = 0x44,
		KEY_E           = 0x45,
		KEY_F           = 0x46,
		KEY_G           = 0x47,
		KEY_H           = 0x48,
		KEY_I           = 0x49,
		KEY_J           = 0x4A,
		KEY_K           = 0x4B,
		KEY_L           = 0x4C,
		KEY_M           = 0x4D,
		KEY_N           = 0x4E,
		KEY_O           = 0x4F,
		KEY_P           = 0x50,
		KEY_Q           = 0x51,
		KEY_R           = 0x52,
		KEY_S           = 0x53,
		KEY_T           = 0x54,
		KEY_U           = 0x55,
		KEY_V           = 0x56,
		KEY_W           = 0x57,
		KEY_X           = 0x58,
		KEY_Y           = 0x59,
		KEY_Z           = 0x5A,

		KEY_WINLEFT     = 0x5B,
		KEY_WINRIGHT    = 0x5C,
		KEY_APPS        = 0x5D,

		KEY_NUMPAD0     = 0x60,
		KEY_NUMPAD1     = 0x61,
		KEY_NUMPAD2     = 0x62,
		KEY_NUMPAD3     = 0x63,
		KEY_NUMPAD4     = 0x64,
		KEY_NUMPAD5     = 0x65,
		KEY_NUMPAD6     = 0x66,
		KEY_NUMPAD7     = 0x67,
		KEY_NUMPAD8     = 0x68,
		KEY_NUMPAD9     = 0x69,

		KEY_MULTIPLY    = 0x6A,
		KEY_ADD         = 0x6B,
		KEY_SEPARATOR   = 0x6C,
		KEY_SUBTRACT    = 0x6D,
		KEY_DECIMAL     = 0x6E,
		KEY_DIVIDE      = 0x6F,

		KEY_F1          = 0x70,
		KEY_F2          = 0x71,
		KEY_F3          = 0x72,
		KEY_F4          = 0x73,
		KEY_F5          = 0x74,
		KEY_F6          = 0x75,
		KEY_F7          = 0x76,
		KEY_F8          = 0x77,
		KEY_F9          = 0x78,
		KEY_F10         = 0x79,
		KEY_F11         = 0x7A,
		KEY_F12         = 0x7B,
		KEY_F13         = 0x7C,
		KEY_F14         = 0x7D,
		KEY_F15         = 0x7E,
		KEY_F16         = 0x7F,
		KEY_F17         = 0x80,
		KEY_F18         = 0x81,
		KEY_F19         = 0x82,
		KEY_F20         = 0x83,
		KEY_F21         = 0x84,
		KEY_F22         = 0x85,
		KEY_F23         = 0x86,
		KEY_F24         = 0x87,

		KEY_NUMLOCK     = 0x90,
		KEY_SCROLL      = 0x91,

		KEY_LSHIFT      = 0xA0,
		KEY_RSHIFT      = 0xA1,
		KEY_LCONTROL    = 0xA2,
		KEY_RCONTROL    = 0xA3,
		KEY_LALT	    = 0xA4,
		KEY_RALT        = 0xA5,

		KEY_PLUS        = 0xBB, // '+'
		KEY_COMMA       = 0xBC, // ','
		KEY_MINUS       = 0xBD, // '-'
		KEY_PERIOD      = 0xBE, // '.'

		KEY_EXPONENT    = 0xDC, // '^'

		KEY_ATTN        = 0xF6,
		KEY_CRSEL       = 0xF7,
		KEY_EXSEL       = 0xF8,
		KEY_EREOF       = 0xF9,
		KEY_PLAY        = 0xFA,
		KEY_ZOOM        = 0xFB,
		KEY_NONAME      = 0xFC,
		KEY_PA1         = 0xFD,
		KEY_OEMCLEAR    = 0xFE,

		KEY_MAX			= 0x100
	};

//------------------------------------------------------------------
}

Этот код можете спокойно скопипастить. Коды символов взяты из WinAPI.

Создайте заголовок InputListener.h.

Напомню, класс InputListener реализует паттерн «Слушатель». Если мы хотим обработать событие ввода, мы создаем наследника этого класса, пишем нужный функционал и добавляем в InputMgr. В этом классе будут события мыши – нажатие/отпускание кнопки мыши, вращение колесика, движение мыши, и события клавиатуры - нажатие/отпускание клавиш. В каждое событие будет передаваться специальная структура содержащая описание события (например при движении мыши мы будем передавать структуру с координатами, а при нажатии структуру с кодом нажатой кнопки).

Напишите следующий код:

#pragma once

namespace D3D11Framework
{
//------------------------------------------------------------------

	// события мыши
	struct MouseEvent
	{
		MouseEvent(int nx, int ny) :  x(nx), y(ny) {}

		// координаты мыши
		int x;				
		int y;
	};

	// событие нажатия кнопки мыши
	struct MouseEventClick : public MouseEvent
	{
		MouseEventClick(eMouseKeyCodes b, int nx, int ny) : MouseEvent(nx,ny), btn(b) {}

		const eMouseKeyCodes btn;	// Клавиша
	};

	// событие прокрутки мыши
	struct MouseEventWheel : public MouseEvent
	{
		MouseEventWheel(int nwheel, int nx, int ny) : MouseEvent(nx,ny), wheel(nwheel) {}

		int wheel;
	};

	// событие клавиши
	struct KeyEvent
	{
		KeyEvent(wchar_t c, eKeyCodes kc) : wc(c), code(kc) {}

		const wchar_t wc;
		const eKeyCodes code;
	};

	class InputListener
	{
	public:
		// если методы возращают true - событие больше никем не обрабатывается
 
		// кнопка нажата
		virtual bool MousePressed(const MouseEventClick &arg) { return false; }
		// кнопка отпущена
		virtual bool MouseReleased(const MouseEventClick &arg) { return false; }
		// вращение колесика
		virtual bool MouseWheel(const MouseEventWheel &arg) { return false; }
		// движение мыши
		virtual bool MouseMove(const MouseEvent &arg) {return false; }

		// кнопка нажата
		virtual bool KeyPressed(const KeyEvent &arg) { return false; }
		// кнопка отпущена
		virtual bool KeyReleased(const KeyEvent &arg) { return false; }
	};

//------------------------------------------------------------------
}

В начале идут структуры описывающие события ввода (движение мыши, нажатие кнопки, прокрутка колесика мыши и нажатие клавиши).

Затем идет класс InputListener. Как вы видите, все его методы виртуальны. Я добавил им тело { return false; } для удобства в будущем, вы спокойно можете сделать их абстрактными. Любой метод возвращает булево значение. Что оно означает? Дело в том, что слушателей может быть несколько, и обрабатываются они в порядке добавления. Так вот, если метод возвращает true, то это означает, что данный слушатель обработал данное событие и это событие не нужно обрабатывать другим слушателям.

Теперь создаем заголовок InputMgr.h

Этот класс будет оповещать всех слушателей о возникновении события. Все события поступают от Window. Обработку ввода мы будем осуществлять прямо через WinAPI. Возможно, вы спросите, почему не через Direct Input? Дело в том что Microsoft не рекомендует больше использовать для обработки ввода, а раз так, последуюем этому совету, а то возможно в будущем DirectIput вообще вырежут за ненадобностью, так как DirectX теперь является частью Windows SDK и весь мусор был выкинут.

InputMgr будет получать событие и далее вызывать нужные методы своих слушателей. Кроме того, мы должны будем добавлять слушателей в InputMgr.
В файле напишите следующий код:

#pragma once

#include "InputCodes.h"

namespace D3D11Framework
{
//------------------------------------------------------------------

	class InputListener;

	class InputMgr
	{
	public:
		void Init();
		void Close();

		void Run(const UINT &msg, WPARAM wParam, LPARAM lParam);

		void AddListener(InputListener *Listener);

		// зона окна
		void SetWinRect(const RECT &winrect);

	private:
		// событие движения мыши
		void m_eventcursor();
		// событие кнопки мыши
		void m_eventmouse(const eMouseKeyCodes KeyCode, bool press);
		// событие вращения колесика
		void m_mousewheel(short Value);
		// обработка события клавиши
		void m_eventkey(const eKeyCodes KeyCode, const wchar_t ch, bool press);

		std::list<InputListener*> m_Listener;

		RECT m_windowrect;
		int m_curx;
		int m_cury;
		int m_MouseWheel;
	};

//------------------------------------------------------------------
}

В методе Init() мы будем инициализировать наш менеджер ввода. Соответственно, метод Close() будет завершать работу.

Метод Run() получает событие от Window и выполняет определенное поведение, то есть оповещает слушателей.

Метод AddListener() добавляет слушателя в менеджер ввода.

Метод SetWinRect() сообщает нашему менеджеру о расположении и размере окна. Зачем это нужно? Для получения координат мыши. Дело в том, что через WinAPI мы получаем координаты на самом экране, а не в окне, вот мы и следим, чтобы координаты мыши всегда были оконными.

Методы m_eventcursor(), m_eventmouse(), m_mousewheel(),m_eventkey() вызываются из Run(). Они связаны с соответствующими событиями.

В члене m_Listener хранится список наших слушателей. Последние методы - вспомогательные.

Создайте файл InputMgr.cpp и напишите следующий код:

#include "stdafx.h"
#include "InputMgr.h"
#include "InputCodes.h"
#include "InputListener.h"
#include "Log.h"

namespace D3D11Framework
{
//------------------------------------------------------------------

	void InputMgr::Init()
	{
		m_MouseWheel = m_curx = m_cury = 0;
		Log::Get()->Debug("InputMgr init");
	}

	void InputMgr::Close()
	{
		if (!m_Listener.empty())
			m_Listener.clear();
		Log::Get()->Debug("InputMgr close");
	}

	void InputMgr::SetWinRect(const RECT &winrect)
	{
		m_windowrect.left = winrect.left;
		m_windowrect.right = winrect.right;
		m_windowrect.top = winrect.top;
		m_windowrect.bottom = winrect.bottom;
	}

	void InputMgr::AddListener(InputListener *Listener)
	{
		m_Listener.push_back(Listener);
	}

	void InputMgr::Run(const UINT &msg, WPARAM wParam, LPARAM lParam)
	{
		if (m_Listener.empty())
			return;

		eKeyCodes KeyIndex;
		wchar_t buffer[1];
		BYTE lpKeyState[256];

		m_eventcursor();// событие движения мыши
		switch(msg)
		{
		case WM_KEYDOWN:
			KeyIndex = static_cast<eKeyCodes>(wParam);
			GetKeyboardState(lpKeyState);
			ToUnicode(wParam, HIWORD(lParam)&0xFF, lpKeyState, buffer, 1, 0);
			m_eventkey(KeyIndex,buffer[0],true);
			break;
		case WM_KEYUP:
			KeyIndex = static_cast<eKeyCodes>(wParam);
			GetKeyboardState(lpKeyState);
			ToUnicode(wParam, HIWORD(lParam)&0xFF, lpKeyState, buffer, 1, 0);
			m_eventkey(KeyIndex,buffer[0],false);
			break;
		case WM_LBUTTONDOWN:
			m_eventmouse(MOUSE_LEFT,true);
			break;
		case WM_LBUTTONUP:
			m_eventmouse(MOUSE_LEFT,false);
			break;
		case WM_RBUTTONDOWN:
			m_eventmouse(MOUSE_RIGHT,true);
			break;
		case WM_RBUTTONUP:
			m_eventmouse(MOUSE_RIGHT,false);
			break;
		case WM_MBUTTONDOWN:
			m_eventmouse(MOUSE_MIDDLE,true);
			break;
		case WM_MBUTTONUP:
			m_eventmouse(MOUSE_MIDDLE,false);
			break;
		case WM_MOUSEWHEEL:
			m_mousewheel( (short)GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA );
			break;
		}
	}

	void InputMgr::m_eventcursor()
	{
		POINT Position;
		GetCursorPos(&Position);	// получаем текущую позицию курсора

		Position.x -= m_windowrect.left;
		Position.y -= m_windowrect.top;

		if (m_curx==Position.x && m_cury==Position.y)
			return;

		m_curx = Position.x;
		m_cury = Position.y;

		for(auto it = m_Listener.begin(); it != m_Listener.end(); ++it)
		{
			if (!(*it))
				continue;
			else if ((*it)->MouseMove(MouseEvent(m_curx,m_cury))==true)
				return;
		}
	}

	void InputMgr::m_eventmouse(const eMouseKeyCodes Code, bool press)
	{
		for(auto it = m_Listener.begin(); it != m_Listener.end(); ++it)
		{
			if (!(*it))
				continue;
			// кнопка нажата
			if (press==true)
			{
				if ((*it)->MousePressed(MouseEventClick(Code, m_curx,m_cury))==true)
					return;
			}
			// кнопка отпущена
			else
			{
				if ((*it)->MouseReleased(MouseEventClick(Code, m_curx,m_cury))==true)
					return;
			}
		}
	}

	void InputMgr::m_mousewheel(short Value)
	{
		if (m_MouseWheel==Value)
			return;

		m_MouseWheel = Value;

		for(auto it = m_Listener.begin(); it != m_Listener.end(); ++it)
		{
			if (!(*it))
				continue;
			else if ((*it)->MouseWheel(MouseEventWheel(m_MouseWheel, m_curx,m_cury))==true)
				return;
		}
	}

	void InputMgr::m_eventkey(const eKeyCodes KeyCode, const wchar_t ch, bool press)
	{
		for(auto it = m_Listener.begin(); it != m_Listener.end(); ++it)
		{
			if (!(*it))
				continue;
			// кнопка нажата
			if (press==true)
			{
				if ((*it)->KeyPressed(KeyEvent(ch,KeyCode))==true)
					return;
			}
			// кнопка отпущена
			else
			{
				if ((*it)->KeyReleased(KeyEvent(ch,KeyCode))==true)
					return;
			}
		}
	}

//------------------------------------------------------------------
}

Начало, думаю, не вызовет сложностей. Начну с Run(). В начале, мы смотрим, есть ли вообще слушатели, если их нет, завершаем работу. Далее, после создания переменных, мы зовем метод m_eventcursor(). Его рассмотрим ниже, он создает событие движения мыши. Далее, мы просматриваем что за событие к нам поступило, получаем нужные параметры и вызываем нужный приватный метод обработки события.

Теперь вернемся к методу m_eventcursor(). В начале, мы получаем текущие координаты мыши. Затем от них отнимаем позицию окна (например, если координата мыши 150, а окно расположено в координате 100, то координата мыши в окне равна 50).

После чего смотрим, сдвинулась ли мышь. Если да, оповещаем всех слушателей о событии движения мыши. auto – это новое (со старыми дырками) ключевое слово С++ 11. Переменная после auto будет иметь тот же тип, что и переменная справа. В нашем случае, это будет итератор.

Затем, мы смотрим, а существует ли еще текущий слушатель и вызываем его метод обработки события. Если последний вернет true, то завершаем оповещение слушателей. Остальные аналогичны.

На этом, система ввода завершена. Приступаем к системе вывода:)

Система рендераПравить

Создайте заголовок Window.h и напишите следующий код:

#pragma once

namespace D3D11Framework
{
//------------------------------------------------------------------
	
	class InputMgr;

	struct DescWindow
	{
		DescWindow() : 
			caption(L""),
			width(640),
			height(480),
			posx(200),
			posy(20),
			resizing(true)
		{}

		int posx;
		int posy;
		std::wstring caption;	///< заголовок окна
		int width;				///< ширина клиентской части окна
		int height;				///< высота клиентской части окна
		bool resizing;
	};

	class Window
	{
	public:
		Window();

		static Window* Get(){return m_wndthis;}

		// Создать окно
		bool Create(const DescWindow &desc);

		// Обработка событий окна
		void RunEvent();

		// Закрыть окно.
		void Close();

		void SetInputMgr(InputMgr *inputmgr);
		
		HWND GetHWND() const {return m_hwnd;}
		int Window::GetLeft() const {return m_desc.posx;}
		int Window::GetTop() const {return m_desc.posy;}
		int Window::GetWidth() const {return m_desc.width;}
		int Window::GetHeight() const {return m_desc.height;}
		// Вернуть заголовок окна
		const std::wstring& GetCaption() const {return m_desc.caption;}

		// сообщает, было ли сообщение о выходе
		bool IsExit() const {return m_isexit;}
		// сообщает об активности окна
		bool IsActive() const {return m_active;}
		// сообщает об изменении окна
		// предупреждение: после вызова оповещает окно об обработке события
		bool IsResize() 
		{
			bool ret = m_isresize;
			m_isresize = false;
			return ret;
		}

		// обработка событий
		LRESULT WndProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam);
	private:
		void m_UpdateWindowState();

		static Window *m_wndthis;

		DescWindow m_desc;	// описание окна
		InputMgr *m_inputmgr;
		HWND m_hwnd;		// дескриптор окна	
		bool m_isexit;		// флаг сообщающий о событии выхода	
		bool m_active;		// окно активно?
		bool m_minimized;
		bool m_maximized;
		bool m_isresize;	// если окно изменило размер
	};

	// обработка событий
	static LRESULT CALLBACK StaticWndProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam);

//------------------------------------------------------------------
}

Структура DescWindow, позволяет нам удобно описать окно – позицию, размер, заголовок. С классом Window советую разобраться самостоятельно.

Создайте файл Window.cpp и напишите следующий код:

#include "stdafx.h"
#include "Window.h"
#include "InputMgr.h"
#include "Log.h"

namespace D3D11Framework
{
//------------------------------------------------------------------
	
	Window *Window::m_wndthis = nullptr;

	Window::Window(void) :
		m_inputmgr(nullptr),
		m_hwnd(0),
		m_isexit(false),
		m_active(true),
		m_minimized(false),
		m_maximized(false),
		m_isresize(false)
	{
		if (!m_wndthis)
			m_wndthis = this;
		else
			Log::Get()->Err("Window уже был создан");
	}

	bool Window::Create(const DescWindow &desc)
	{
		Log::Get()->Debug("Window Create");
		m_desc = desc;

		WNDCLASSEXW wnd;

		wnd.cbSize = sizeof(WNDCLASSEXW);
		wnd.style = CS_HREDRAW | CS_VREDRAW;
		wnd.lpfnWndProc = StaticWndProc;
		wnd.cbClsExtra = 0;
		wnd.cbWndExtra = 0;
		wnd.hInstance = 0;
		wnd.hIcon = LoadIcon(NULL, IDI_WINLOGO);
		wnd.hIconSm = wnd.hIcon;
		wnd.hCursor = LoadCursor(0, IDC_ARROW);
		wnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
		wnd.lpszMenuName = NULL;
		wnd.lpszClassName = L"D3D11F";
		wnd.cbSize = sizeof(WNDCLASSEX);

		if( !RegisterClassEx( &wnd ) )
		{
			Log::Get()->Err("Не удалось зарегистрировать окно");
			return false;
		}

		RECT rect = {0, 0, m_desc.width, m_desc.height};
		AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW | WS_VISIBLE, FALSE);

		long lwidth = rect.right - rect.left;
		long lheight = rect.bottom - rect.top;

		long lleft = (long)m_desc.posx;	
		long ltop = (long)m_desc.posy;

		m_hwnd = CreateWindowEx(NULL, L"D3D11F", m_desc.caption.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,  lleft, ltop, lwidth, lheight, NULL, NULL, NULL, NULL);

		if( !m_hwnd )
		{
			Log::Get()->Err("Не удалось создать окно");
			return false;
		}

		ShowWindow(m_hwnd, SW_SHOW);
		UpdateWindow(m_hwnd);

		return true;
	}

	void Window::RunEvent()
	{
		MSG msg;			// события окна	
		// просматриваем все поступившие события
		while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	void Window::Close()
	{
		if (m_hwnd)
			DestroyWindow(m_hwnd);
		m_hwnd = nullptr;

		Log::Get()->Debug("Window Close");
	}

	LRESULT Window::WndProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
	{
		switch(nMsg)
		{
		case WM_CREATE:
			return 0;
		case WM_CLOSE:
			m_isexit = true;
			return 0;
		case WM_ACTIVATE:
			if (LOWORD(wParam) != WA_INACTIVE)
				m_active = true;
			else
				m_active = false;
			return 0;
		case WM_MOVE:
			m_desc.posx = LOWORD(lParam);
			m_desc.posy = HIWORD(lParam);
			m_UpdateWindowState();
			return 0;
		case WM_SIZE:
			if (!m_desc.resizing)
				return 0;
			m_desc.width = LOWORD(lParam);
			m_desc.height = HIWORD(lParam);
			m_isresize = true;
			if( wParam == SIZE_MINIMIZED )
			{
				m_active = false;
				m_minimized = true;
				m_maximized = false;
			}
			else if( wParam == SIZE_MAXIMIZED )
			{
				m_active = true;
				m_minimized = false;
				m_maximized = true;
			}
			else if( wParam == SIZE_RESTORED )
			{
				if( m_minimized )
				{
					m_active = true;
					m_minimized = false;
				}
				else if( m_maximized )
				{
					m_active = true;
					m_maximized = false;
				}
			}
			m_UpdateWindowState();
			return 0;
		case WM_MOUSEMOVE: case WM_LBUTTONUP: case WM_LBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDOWN: case WM_RBUTTONUP: case WM_RBUTTONDOWN: case WM_MOUSEWHEEL: case WM_KEYDOWN: case WM_KEYUP:
			if (m_inputmgr)
				m_inputmgr->Run(nMsg,wParam, lParam);
			return 0;
		}

		return DefWindowProcW( hwnd, nMsg, wParam, lParam);
	}

	void Window::SetInputMgr(InputMgr *inputmgr) 
	{
		m_inputmgr = inputmgr;
		m_UpdateWindowState();	
	}

	void Window::m_UpdateWindowState()
	{
		RECT ClientRect;
		ClientRect.left = m_desc.posx;
		ClientRect.top = m_desc.posy;
		ClientRect.right = m_desc.width;
		ClientRect.bottom = m_desc.height;
		if (m_inputmgr)
			m_inputmgr->SetWinRect(ClientRect);
	}

	LRESULT CALLBACK D3D11Framework::StaticWndProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
	{
		return Window::Get()->WndProc( hwnd, nMsg, wParam, lParam );
	}

//------------------------------------------------------------------
}

Да, кода много. Но ничего, мы почти уже закончили. Рассмотрю только некоторые методы. В методе Create() мы создаем окно с указанными через DescWindow параметрами. Метод RunEvent() запускает обработку событий WinAPI. Close() завершает работу окна. WndProc() обрабатывает поступившие от операционной системы события и совершает нужные действия – например, оповещает InputMgr о событии клавиатуры или мыши.

Примечание: у вас может быть только одно окно Window.

Теперь создайте заголовок Render.h и напишите следующий код:

#pragma once

namespace D3D11Framework
{
//------------------------------------------------------------------

	class Render
	{
	public:
		Render();
		virtual ~Render();

		virtual bool Init(HWND hwnd) = 0;
		virtual bool Draw() = 0;
		virtual void Close() = 0;
	};

//------------------------------------------------------------------
}

Затем создайте файл Render.cpp и напишите следующий код:

#include "stdafx.h"
#include "Render.h"

namespace D3D11Framework
{
//------------------------------------------------------------------


	Render::Render()
	{
	}

	Render::~Render()
	{
	}

//------------------------------------------------------------------
}

Хе-хе. После Window это смешно, не правда ли? Почему этот класс ничего не делает? А вот именно через него мы и будем в будущем изучать DirectX. А так как мы этого еще не начали, то он пустой. То есть, изучая DirectX, мы будем заполнять этот класс. А для чего мы вообще этот класс здесь написали? Дело в том, что как вы видите, он абстрактный. В проекте, вы должны будете создать наследника этого класса с нужным поведением и передать его в Framework. Остался последний класс Framework.

Класс фреймворкаПравить

Создайте заголовок Framework.h и напишите этот код:

#pragma once

#include "Window.h"
#include "Render.h"
#include "InputMgr.h"
#include "Log.h"

namespace D3D11Framework
{
//------------------------------------------------------------------

	class Framework
	{
	public:
		Framework();
		~Framework();

		bool Init();
		void Run();
		void Close();

		void SetRender(Render *render){m_render = render;}
		void AddInputListener(InputListener *listener);
	protected:	
		bool m_frame();	

		Window *m_wnd;
		Render *m_render;
		InputMgr *m_input;
		Log m_log;
		bool m_init;		// если было инициализировано
	};

//------------------------------------------------------------------
}

Класс Framework взаимодействует с другими классами и поэтому содержит их экземпляры. Init() – инициализирует нашу библиотеку. Run() производит выполнение библиотеки. Close(), завершает работу библиотеки. SetRender() позволяет нам передать нашего наследника класса Render. AddInputListener() добавляет слушателя для ввода. m_frame() выполняет работу одного кадра.

Теперь создайте Framework.cpp и напишите следующий код:

#include "stdafx.h"
#include "Framework.h"
#include "macros.h"
#include "Log.h"

namespace D3D11Framework
{
//------------------------------------------------------------------

	Framework::Framework() :
		m_wnd(nullptr),
		m_render(nullptr),
		m_input(nullptr),
		m_init(false)
	{
	}

	Framework::~Framework()
	{
	}

	void Framework::AddInputListener(InputListener *listener)
	{
		if (m_input)
			m_input->AddListener(listener);
	}

	void Framework::Run()
	{
		if (m_init)
			while(m_frame());
	}

	bool Framework::Init()
	{
		m_wnd = new Window();
		m_input = new InputMgr();

		if ( !m_wnd || !m_input )
		{
			Log::Get()->Err("Не удалось выделить память");
			return false;
		}

		m_input->Init();

		// Создаем значения настроек по умолчанию. В одном из будущих уроков мы вернемся к этому
		DescWindow desc;			
		if ( !m_wnd->Create(desc) )
		{
			Log::Get()->Err("Не удалось создать окно");
			return false;
		}
		m_wnd->SetInputMgr(m_input);

		if ( !m_render->Init(m_wnd->GetHWND()) )
		{
			Log::Get()->Err("Не удалось создать рендер");
			return false;
		}

		m_init = true;
		return true;
	}

	bool Framework::m_frame()
	{
		// обрабатываем события окна
		m_wnd->RunEvent();
		// если окно неактивно - завершаем кадр
		if (!m_wnd->IsActive())
			return true;

		// если окно было закрыто, завершаем работу движка
		if (m_wnd->IsExit())
			return false;

		// если окно изменило размер
		if (m_wnd->IsResize())
		{
		}

		if (!m_render->Draw())
			return false;

		return true;
	}

	void Framework::Close()
	{
		m_init = false;
		_CLOSE(m_render);
		_CLOSE(m_wnd);
		_CLOSE(m_input);
	}

//------------------------------------------------------------------
}

Компилируйте. Если все успешно, поспешу порадовать вас – на этом мы закончили создание фреймворка для наших уроков. Но не спешите закрывать код, мы еще не протестировали его.
И еще, создайте заголовок D3D11_Framework.h и в нем следующий код:

#pragma once

#include "macros.h"
#include "stdafx.h"
#include "Framework.h"
#include "InputListener.h"
#include "Render.h"

С его помощью мы будем использовать фреймворк в наших проектах уроков.

Исходный кодскачать

Материалы сообщества доступны в соответствии с условиями лицензии CC-BY-SA , если не указано иное.