Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.11 Добавляем поддержку шрифтов
Программируем 3D-шутер от первого лица (FPS) (Win32, C++, DirectX9)

1.11 Добавляем поддержку шрифтов


Дочитав Главу до конца, ты даже увидишь результаты рендеринга на экране. Вообще, это будет обычный текст. Но надо же с чего-то начинать.
Здесь мы разработаем и добавим в движок двольно простой класс для работы со шрифтами. За основу будет взят пример из DirectX SDK. Мы быстро просмотрим интерфейс класса Font (экспонированный DirectX), но не будем сильно вдаваться в реализацию, т.к. за основу будет взят пример CD3DFont, идущий в наборе с DirectX SDK. Пример будет слегка подкорректирован и сильно урезан. Т.к. для игр, обычно, не требуются шрифтовые "изыски". В будущем данный класс может быть изменён, дополнен либо вовсе заменён на другой. Модульность - одно из неотъемлемых качеств профессионально разработанного движка (и не только его).
Объявление класса Font будет содержаться в файле Font.h
Реализация класса Font (вместе с дополнительными функциями) будет содержаться в файле Font.cpp

Сейчас в Проекте Engine всего 14 файлов: Engine.h, Engine.cpp, LinkedList.h, Resource.h, ResourceManagement.h, Geometry.h, State.h, State.cpp, Scripting.h, Scripting.cpp, DeviceEnumeration.h, DeviceEnumeration.cpp, Input.h и Input.cpp, которые мы создали в предыдущих главах. Если не досчитался исходников - не беда. В конце этой главы будет ссылка на архив с Решением для MSVC++2010.

Создаём Font.h (Проект Engine)

Заголовочный файл Font.h будет содержать объявление всего 1 класса Font для работы со шрифтами.
ОК, приступаем.

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

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

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

Font.h (Проект Engine)
//-----------------------------------------------------------------------------
// A font render that draws the characters using textured quads.
// Поддержка рендеринга шрифтов, которые выводят символы с помощью
// т.н. "текстурированных квадов"
// 
// Note: This is just a cutdown, modified version of the CD3DFont class.
// Примечание: Это всего лишь урезанная версия класса CD3DFont из примера
// DirectXSDK.
//
// Original sourcecode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#ifndef FONT_H
#define FONT_H

//-----------------------------------------------------------------------------
// Font Class
//-----------------------------------------------------------------------------
class Font
{
public:
	Font( char *name = "Arial", short size = 10, unsigned long bold = FW_NORMAL, bool italic = false );
	virtual ~Font();

	void Render( char *text, float x, float y, D3DCOLOR colour = D3DCOLOR_COLORVALUE( 1.0f, 1.0f, 1.0f, 1.0f ) );

private:
	bool PrepareFont( HDC hDC, bool measure = false );

private:
	IDirect3DStateBlock9 *m_states; // Стейтблок (state block), используемый для восстановления рендер-стейтов.
	IDirect3DVertexBuffer9 *m_vb; // Вершинный буфер (vertex buffer) для рендеримого текста.
	IDirect3DTexture9 *m_texture; // Direct3D-текстура для шрифта.
	unsigned long m_textureWidth; // Ширина текстуры.
	unsigned long m_textureHeight; // Высота текстуры.
	float m_textureCoords[96][4]; // Текстурные координаты символа (Character texture coordinates).
	short m_spacing; // Расстояние между символами в пикселах (Character pixel spacing per side).
};

#endif

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

Исследуем Font.h

Класс в самом деле совсем небольшой, но в него входит несколько переменных членов (размещены в секции private), которые мы сейчас рассмотрим:

Перем. член Описание
IDirect3DStateBlock9 *m_states Используется для сохранения текущего состояния объекта устройства перед рендерингом любого текста. А всё потому, что класс Font изменяет состояние объекта устройства (device state) (примерно также, как это делает функция SetRenderState) для корректного выполнения рендеринга текста. Как только рендеринг текста будет окончен, класс восстанавливает исходное состояние объекта устройтсва (device state), которое было до начала рендеринга.
IDirect3DVertexBuffer9 *m_vb Вершинный буфер. Мы ранее не рассматривали вершинные буферы (т.к. пока в них не было необходимости), но в DirectX они используются практически повсеместно. Ты увидишь их в действии чуть позднее в данном курсе, когда мы приступим к рендерингу сцены и 3D-мешей на ней. В общих чертах вершинный буфер похож на большой бассейн, который содержит все вершины для рендеринга определённого куска 3D-геометрии. Например, если ты создаешь вершинный буфер, содержащий вершины одной грани, то в этом случае в нём будет всего 3 вершины. Как правило, вслед за этим вершинный буфер передаётся в Direct3D и даётся команда на рендеринг. Таким образом Direct3D будет рендерить грань (face), состоящую из трёх вершин, которые хранятся в вершинном буфере. Но данный принцип работы - лишь верхушка айсберга тех возможностей, которыми обладают вершинные буферы. Сейчас мы не будем рассматривать их подробно. Наша переменная m_vb хранит вершины граней, из которых будет рендериться наш текст. Каждый символ текста будет состоять из двух граней, с нанесённой на них текстурой. Текстура будет содержать изображение символа из выбранного шрифта.
IDirect3DTexture9 *m_texture Текстура с изображением всех символов выводимого текста. Позднее мы более подробно рассмотрим интерфейс IDirect3DTexture9, когда будем внедрять в движок материалы и 3D-меши.
unsigned long m_textureWidth Ширина этой структуры.
unsigned long m_textureHeight Высота этой структуры.
float m_textureCoords[96][4] Задаёт текстурные координаты для идентификации символов на общей текстуре.
short m_spacing Указывает количество пикселей отступа каждого символа с левой и правой стороны.

Создаём Font.cpp (Проект Engine)

В файле исходного кода Font.cpp будет содержаться реализация класса Font + дополнительные функции для работы со шрифтами. За основу взят исходник примера Font из DirectX SDK.
ОК, приступаем.

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

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

  • В только что созданном и открытом файле Font.cpp набираем следующий код:

Scripting.cpp (Проект Engine)
//-----------------------------------------------------------------------------
// Файл: Font.cpp
//
// Описание: Реализация класса Font, объявленного в Font.h.
//    Обращаемся к интерфейсу Font.h за подробностями.
// 
// Original Source code:
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#include "Engine.h"

//-----------------------------------------------------------------------------
// The font class constructor
//-----------------------------------------------------------------------------
Font::Font( char *name, short size, unsigned long bold, bool italic )
{
	HBITMAP bitmap = NULL;
	HGDIOBJ oldBitmap = NULL;
	HFONT oldFont = NULL;
	BYTE *dstRow = NULL;
	unsigned long x, y;

	HDC hDC = CreateCompatibleDC( NULL );
	SetMapMode( hDC, MM_TEXT );

	// Создаём шрифт.
	char height = -MulDiv( size, GetDeviceCaps( hDC, LOGPIXELSY ), 72 );
	HFONT font = CreateFont( height, 0, 0, 0, bold, italic, false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH, name );
	if( font == NULL )
		goto End;

	oldFont = (HFONT)SelectObject( hDC, font );

	// Ищем размеры наименьшей текстуры, содержащей символы.
	m_textureWidth = m_textureHeight = 128;
	while( !PrepareFont( hDC, true ) )
	{
		m_textureWidth *= 2;
		m_textureHeight *= 2;
	}

	// Создаём новую текстуру для шрифта
	if( FAILED( g_engine->GetDevice()->CreateTexture( m_textureWidth, m_textureHeight, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &m_texture, NULL ) ) )
		goto End;

	// Готовим растровое изображение (bitmap).
	unsigned long *bitmapBits;
	BITMAPINFO bmi;
	ZeroMemory( &bmi.bmiHeader, sizeof( BITMAPINFOHEADER ) );
	bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	bmi.bmiHeader.biWidth = (int)m_textureWidth;
	bmi.bmiHeader.biHeight = -(int)m_textureHeight;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biBitCount = 32;

	// Создаём растровое изображение (bitmap) для шрифта.
	bitmap = CreateDIBSection( hDC, &bmi, DIB_RGB_COLORS, (void**)&bitmapBits, NULL, 0 );

	oldBitmap = SelectObject( hDC, bitmap );

	// Устанавливаем свойства текста.
	SetTextColor( hDC, RGB( 255,255,255 ) );
	SetBkColor( hDC, 0x00000000 );
	SetTextAlign( hDC, TA_TOP );

	// Готовим шрифт к прорисовке символов на битмапе.
	if( !PrepareFont( hDC ) )
		goto End;

	// Лочим (блокируем) поверхность и записываем значения альфа-канала для набора пикселей.
	D3DLOCKED_RECT d3dlr;
	m_texture->LockRect( 0, &d3dlr, 0, 0 );
	dstRow = (BYTE*)d3dlr.pBits;
	WORD *dst16;
	BYTE alpha;

	for( y = 0; y < m_textureHeight; y++ )
	{
		dst16 = (WORD*)dstRow;
		for( x = 0; x < m_textureWidth; x++ )
		{
			alpha = (BYTE)( ( bitmapBits[m_textureWidth*y + x] & 0xff ) >> 4 );
			if( alpha > 0 )
				*dst16++ = (WORD)( ( alpha << 12 ) | 0x0fff );
			else
				*dst16++ = 0x0000;
		}
		dstRow += d3dlr.Pitch;
	}

	// Создаём вершинный буфер (vertex buffer) для символов.
	g_engine->GetDevice()->CreateVertexBuffer( 1020 * sizeof( TLVertex ), D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0, D3DPOOL_DEFAULT, &m_vb, NULL );

	// Готовим альфа-проверку (alpha testing) для рендеринга символов.
	g_engine->GetDevice()->SetRenderState( D3DRS_ALPHAREF, 0x08 );
	g_engine->GetDevice()->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL );

	// Создаём блок стейтов для захвата волатильных (volatile, нестабильных, постоянно изменяющихся) рендер-стейтов.
	g_engine->GetDevice()->BeginStateBlock();
	g_engine->GetDevice()->SetRenderState( D3DRS_LIGHTING, false );
	g_engine->GetDevice()->SetRenderState( D3DRS_ALPHATESTENABLE, false );
	g_engine->GetDevice()->SetRenderState( D3DRS_FOGENABLE, false );
	g_engine->GetDevice()->EndStateBlock( &m_states );

	// Прибираемся и возвращаем управление движку.
End:
	if( m_texture )
		m_texture->UnlockRect( 0 );

	SelectObject( hDC, oldBitmap );
	SelectObject( hDC, oldFont );
	DeleteObject( bitmap );
	DeleteObject( font );
	DeleteDC( hDC );
}

//-----------------------------------------------------------------------------
// The font class destructor
//-----------------------------------------------------------------------------
Font::~Font()
{
	SAFE_RELEASE( m_states );
	SAFE_RELEASE( m_vb );
	SAFE_RELEASE( m_texture );
}

//-----------------------------------------------------------------------------
// Готовит шрифт путём прорисовки всех символов в контексте устройтсва (device context).
//-----------------------------------------------------------------------------
bool Font::PrepareFont( HDC hDC, bool measure )
{
	SIZE size;
	char string[2];

	// Определяем отступы между символами, основываясь на высоте линии (line height).
	if( GetTextExtentPoint32( hDC, string, 1, &size ) == 0 )
		return false;
	m_spacing = (short)ceil( size.cy * 0.1f );

	// Устанавливаем позицию начала прорисовки.
	unsigned long x = m_spacing;
	unsigned long y = 0;

	// Выводим каждый символ в контекст устройства (device context, DC) и перемещаемся на след. позицию.
	for( char c = 32; c < 127; c++ )
	{
		string[0] = c;
		if( GetTextExtentPoint32( hDC, string, 1, &size ) == 0 )
			return false;

		if( (unsigned long)( x + size.cx + m_spacing ) > m_textureWidth )
		{
			x = m_spacing;
			y += size.cy + 1;
		}

		// Проверяем, осталось ли ещё место для прорисовки очередного символа.
		if( y + size.cy > m_textureHeight )
			return false;

		// Выводим символ, если не измерено.
		if( !measure )
		{
			if( ExtTextOut( hDC, x + 0, y + 0, ETO_OPAQUE, NULL, string, 1, NULL ) == 0 )
				return false;

			m_textureCoords[c - 32][0] = (float)( x - m_spacing ) / m_textureWidth;
			m_textureCoords[c - 32][1] = (float)( y ) / m_textureHeight;
			m_textureCoords[c - 32][2] = (float)( x + size.cx + m_spacing ) / m_textureWidth;
			m_textureCoords[c - 32][3] = (float)( y + size.cy ) / m_textureHeight;
		}

		x += size.cx + ( 2 * m_spacing );
	}

	return true;
}

//-----------------------------------------------------------------------------
// Рендерит указанный текст на экран, используя данный шрифт.
//-----------------------------------------------------------------------------
void Font::Render( char *text, float x, float y, D3DCOLOR colour )
{
	// Захватываем текущие волатильные (volatile, непостоянные) рендер-стейты.
	m_states->Capture();

	// Устанавливаем волатильные (volatile, непостоянные) рендер-стейты.
	g_engine->GetDevice()->SetRenderState( D3DRS_LIGHTING, false );
	g_engine->GetDevice()->SetRenderState( D3DRS_ALPHATESTENABLE, true );
	g_engine->GetDevice()->SetRenderState( D3DRS_FOGENABLE, false );

	// Устанавливаем неволатильные рендер-стейты.
	g_engine->GetDevice()->SetTexture( 0, m_texture );
	g_engine->GetDevice()->SetFVF( TL_VERTEX_FVF );
	g_engine->GetDevice()->SetStreamSource( 0, m_vb, 0, TL_VERTEX_FVF_SIZE );

	// Регулируем расстояние между символами.
	x -= m_spacing;
	float startX = x;

	// Заполняем вершинный буфер (vertex buffer).
	TLVertex* vertices = NULL;
	unsigned long totalTriangles = 0;
	m_vb->Lock( 0, 0, (void**)&vertices, D3DLOCK_DISCARD );

	// Для каждой буквы текста добавляем текстурированный квад (= две грани, образующие прямоугольник) в вершинный буфер.
	while( *text )
	{
		char c = *text++;

		if( c == _T( '\n' ) )
		{
			x = startX;
			y += ( m_textureCoords[0][3] - m_textureCoords[0][1] ) * m_textureHeight;
		}

		if( ( c - 32 ) < 0 || ( c - 32 ) >= 96 )
			continue;

		float tx1 = m_textureCoords[c - 32][0];
		float ty1 = m_textureCoords[c - 32][1];
		float tx2 = m_textureCoords[c - 32][2];
		float ty2 = m_textureCoords[c - 32][3];

		float w = ( tx2 - tx1 ) * m_textureWidth;
		float h = ( ty2 - ty1 ) * m_textureHeight;

		if( c != _T( ' ' ) )
		{
			*vertices++ = TLVertex( D3DXVECTOR4( x - 0.5f, y + h - 0.5f, 0.0f, 1.0f ), colour, tx1, ty2 );
			*vertices++ = TLVertex( D3DXVECTOR4( x - 0.5f, y - 0.5f, 0.0f, 1.0f ), colour, tx1, ty1 );
			*vertices++ = TLVertex( D3DXVECTOR4( x + w - 0.5f, y + h - 0.5f, 0.0f, 1.0f ), colour, tx2, ty2 );
			*vertices++ = TLVertex( D3DXVECTOR4( x + w - 0.5f, y - 0.5f, 0.0f, 1.0f ), colour, tx2, ty1 );
			*vertices++ = TLVertex( D3DXVECTOR4( x + w - 0.5f, y + h - 0.5f, 0.0f, 1.0f ), colour, tx2, ty2 );
			*vertices++ = TLVertex( D3DXVECTOR4( x - 0.5f, y - 0.5f, 0.0f, 1.0f ), colour, tx1, ty1 );
			totalTriangles += 2;

			if( totalTriangles == 340 )
			{
				// Разблокируем, рендерим и снова блокируем вершинный буфер.
				m_vb->Unlock();
				g_engine->GetDevice()->DrawPrimitive( D3DPT_TRIANGLELIST, 0, totalTriangles );
				vertices = NULL;
				m_vb->Lock( 0, 0, (void**)&vertices, D3DLOCK_DISCARD );
				totalTriangles = 0;
			}
		}

		x += w - ( 2 * m_spacing );
	}

	// Разблокируем и рендерим содержимое вершинного буфера (vertex buffer).
	m_vb->Unlock();
	if( totalTriangles > 0 )
		g_engine->GetDevice()->DrawPrimitive( D3DPT_TRIANGLELIST, 0, totalTriangles );

	// Восстанавливаем волатильные (изменяющиеся) рендер-стейты.
	m_states->Apply();
}

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


Код достаточно сложен. Но он представлен максимально доступно и снабжён подробными комментариями.

Интегрируем систему поддержки шрифтов в движок

Завершающим шагом перед использованием класса Font будет его включение в движок. Принцип тот же, что и при интеграции других систем.

Изменения в Font.cpp (Проект Engine)

  • Добавь инструкцию #include "Engine.h" в самом начале файла Font.cpp (проверь её наличие).

Изменения в Engine.h (Проект Engine)

  • Добавь инструкцию #include "Font.h" в файл Engine.h, сразу после инструкции #include "Geometry.h":
Фрагмент Engine.h (Проект Engine)
...
//-----------------------------------------------------------------------------
// Engine Includes
//-----------------------------------------------------------------------------
#include "Resource.h"
#include "LinkedList.h"
#include "ResourceManagement.h"
#include "Geometry.h"
#include "Font.h"
#include "Scripting.h"
#include "DeviceEnumeration.h"
#include "Input.h"
#include "State.h"
...

Тестовая перекомпиляция Engine.lib

Итак, система поддержки шрифтов полностью интегрирована в движок.
Для проверки работосопособности исходного кода, добавленного в этой Главе, перекомпилируем исходный код Проекта Engine:

  • В Обозревателе решений щёлкаем правой кнопкой мыши по значку Проекта Engine. Во всплывающем меню выбираем "Перестроить" (применяется в случае, когда код уже был успешно скомпилирован ранее).

Image
По окончании компиляции (обычно, при создании небольших проектов, она занимает менее 1 секунды) в панели "Вывод" (в нижней части главного окна IDE) будет представлен отчёт (лог) об успешной (либо неуспешной) компиляции.
В нашем случае компиляция прошла успешно:
Image
Полученная в результате компиляции двоичная библиотека Engine.lib перезаписывается по тому же пути (там же её будет искать тестовое приложение из Проекта Test).

Использование класса Font

Для использования класса Font достаточно создать его экземпляр (инстанс), который подготовит всё необходимое для начала рендеринга. Конструктор принимает в качестве вводного параметра название используемого шрифта (это должен быть шрифт, зарегистрированный в ОС), его размер и ещё 2 флага: жирный (bold) или нет и наклонный (italic) или нет. Так как в заголовке Font.h, в конструкторе класса, Font уже указаны значения этих параметров по умолчанию, то указывать их все в инстансе нет необходимости. Сразу после создания экземпляра класса Font можно начинать рендеринг текста путём вызова функции Render. Вот её параметры:

Параметр Описание
char *text Указатель на строку текста для рендеринга.
float x Х-координата экрана начала текстовой строки.
float y Y-координата экрана начала текстовой строки.
D3DCOLOR colour = D3DCOLOR_COLORVALUE( 1.0f, 1.0f, 1.0f, 1.0f ) ) Цветовое значение RGBA-палитры, указывающее цвет выводимого текста.

Координата (0,0) соответствует левому верхнему углу экрана.

Модифицируем тестовое приложение (Проект Test)

В Главе 1.6(external link) для проверки работоспособности движка в нашем Решении GameProject01 мы создали второй Проект Test, после компиляции которого получили исполняемое приложение (файл .EXE), показывающее окно. В Главе 1.8 его функционал был заметно расширен. Применив полученные в текущей Главе знания на практике, снова дополним исходный код тестового приложения, оснастив его новым "функционалом". Если по каким-то причинам Проект Test отсутствует в Решении GameProject01, создай его, следуя инструкциям Главы 1.6(external link) .
ОК, приступаем.

  • Открой для редактирования файл исходного кода Main.cpp (Проект Test) и замени содержащийся в нём код на следующий:
Main.cpp (Проект Test)
//-----------------------------------------------------------------------------
// System Includes
//-----------------------------------------------------------------------------
#include <windows.h>

//-----------------------------------------------------------------------------
// Engine Includes
//-----------------------------------------------------------------------------
#include "..\GameProject01\Engine.h"

//-----------------------------------------------------------------------------
// Test State Class
//-----------------------------------------------------------------------------
class TestState : public State
{
public:
	//-------------------------------------------------------------------------
	// Позволяет стейту выполнить какие-либо препроцессорные (pre-processing) инструкции.
	//-------------------------------------------------------------------------
	virtual void Load()
	{
		m_font = new Font;
	}

	//-------------------------------------------------------------------------
	// Позвоялет стейту выполнить какие-либо постпроцессорные (post-processing) инструкции.
	//-------------------------------------------------------------------------
	virtual void Close()
	{
		SAFE_DELETE( m_font );
	}

	//-------------------------------------------------------------------------
	// Возвращает установки вида для данного фрейма.
	//-------------------------------------------------------------------------
	virtual void RequestViewer( ViewerSetup *viewer )
	{
		viewer->viewClearFlags = D3DCLEAR_TARGET;
	}

	//-------------------------------------------------------------------------
	// Updates the state.
	//-------------------------------------------------------------------------
	virtual void Update( float elapsed )
	{
		// Подсчитываем фреймрейт (fps, частоту кадров).
		static float frameTime = 1.0f;
		static int frameCount = 0;
		frameTime += elapsed;
		frameCount++;

		// Обновляем данные о fps каждую сукунду.
		if( frameTime > 1.0f )
		{
			sprintf( m_fps, "%d fps", frameCount );

			frameTime = 0.0f;
			frameCount = 0;
		}
	}

	//-------------------------------------------------------------------------
	// Renders the state.
	//-------------------------------------------------------------------------
	virtual void Render()
	{
		m_font->Render( "Hello! This text is rendered using the new Direct3D device.", 0, 0 );
		m_font->Render( m_fps, 10, 100, D3DCOLOR_COLORVALUE( 1.0f, 0.0f, 1.0f, 1.0f ) );
	}

private:
	Font *m_font; // Шрифт, применяемый для рендеринга стейта.
	char m_fps[16]; // Сохраняет показания fps в виде строки текста для вывода на экран.
};

//-----------------------------------------------------------------------------
// Установки стейта, специфичные для приложения.
//-----------------------------------------------------------------------------
void StateSetup()
{
	g_engine->AddState( new TestState, true );
}

//-----------------------------------------------------------------------------
// Точка входа в приложение.
//-----------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE instance, HINSTANCE prev, LPSTR cmdLine, int cmdShow )
{
	// Создаём структуру engine setup.
	EngineSetup setup;
	setup.instance = instance;
	setup.name = "Rendering Test";
	setup.totalBackBuffers = 2;
	setup.StateSetup = StateSetup;

	// Создаём движок (на основе данных структуры EngineSetup), затем запускаем его.
	new Engine( &setup );
	g_engine->Run();

	return true;
}


И вновь в нашем Проекте Test всего 1 файл Main.cpp, который содержит весь исходный код тестового приложения. Перед нами готовые системы стейтов, пользовательского ввода и поддержки шрифтов в действии.

Исследуем код

Одно из самых заметных изменений - мы выставили новое значение переменной setup.totalBackBuffers (равное 2) в функции WinMain.
В стейте TestState мы также видим несколько добавлений. В частности мы используем новый класс Font для теста возможностей рендеринга нашего объекта устройства.
Обрати внимание, что мы создаём и уничтожаем инстанс класса Font с помощью специальных функций менеджера ресурсов Load и Close. Затем мы производим рендеринг текста путём вызова функции Render класса Font внутри функции Render класса TestState.
Также видна функция RequestViewer, где в качестве параметра viewClearFlags мы указываем D3DCLEAR_TARGET, чтобы указать на очищение цели рендеринга между кадрами. Остальное понято из комментариев в исходном коде.
Что касается индикатора FPS (указателя частоты обновления, т.е. числа кадров в секунду), то самое интересное здесь происходит в функции Render стейта TestState. Здесь мы используем переменную elapsed (затраченное время) для расчёта текущей частоты обновления цели рендеринга (фреймрейта). Это значение сохраняется в массиве символов (character array), который затем передаётся в функцию Render класса Font. Это демонстрирует, что в функцию Render класса Font может передаваться как обычный текст, так и содержимое массивов либо других переменных.

Подготовка Проекта Test к перекомпиляции

Напомним, что для успешной компиляции библиотека Engine должна быть добавлена в Проект Test (см. Главу 1.6).
Помимо этого, MS Visual C++ 2010 при компиляции нашего обновлённого Проекта Test активно юзает внешние библиотеки и требует их принудительного указания в настройках компоновщика (= линковщика, линкера) Проекта. Некоторые из них мы уже добавляли ранее (winmm.lib, dxguid.lib и dinput8.lib).
Проверим их наличие и добавим несколько новых:

  • В Обозревателе решений щёлкни правой кнопкой мыши по названию Проекта Test.
  • Во всплывающем контекстном меню выбираем пункт "Свойства".
  • В появившемся окне: Свойства конфигурации -> Компоновщик -> Ввод. Во всплывающем списке напротив пункта "Дополнительные зависимости" выбираем "Изменить...".
  • В появившемся окне "Дополнительные зависимости" в поле ввода должны быть указаны (обязательно в столбик и без запятых!) 7 библиотек:

d3dx9d.lib, d3d9.lib, dinput8.lib, dxguid.lib, winmm.lib, odbc32.lib, odbccp32.lib
Image

  • Жмём ОК, ОК.
  • Сохрани изменения в Решении (Файл->Сохранить все).


Сразу после этого компилируем Проект Test:

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


Компиляция завершилась успешно. Итоговый исполняемый двоичный файл (Test.exe) расположен на жёстком диске ПК в той же директории, что и библиотека движка.
В нашем случае, это: С:\Users\<Имя пользователя>\documents\visual studio 2010\Projects\GameProject01\Debug. (В разных ОС путь к файлу может отличаться от представленного).
Найди и запусти получившееся приложение. "Поиграй" с ним, посмотри как оно работает в различных видеорежимах и разрешениях. Измени текст, выводимый на экран. Правда, русский язык оно пока не хочет поддерживать...

Исходные коды Решения GameProject01 (для MS Visual C++ 2010), с которым работали в данной Главе, забираем здесь(external link).


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.11 Добавляем поддержку шрифтов

Contributors to this page: slymentat .
Последнее изменение страницы Четверг 02 / Март, 2017 10:45:56 MSK автор slymentat.

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

No records to display