Загрузка...
 
Печать
Кодим 3D FPS DX9

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




Intro

Дочитав Главу до конца, ты даже увидишь результаты рендеринга на экране. Вообще, это будет обычный текст. Но надо же с чего-то начинать.
Здесь мы разработаем и добавим в движок довольно простой класс для работы со шрифтами.1 Мы быстро просмотрим интерфейс класса 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.
  • Во всплывающем меню Добавить->Создать элемент... (Add->New Item...)
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "Font.h".
Image
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле Font.h набираем следующий код:
Font.h
//-----------------------------------------------------------------------------
// File: Font.h
// 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 хранит вершины граней, из которых будет рендериться наш текст. Каждый символ текста будет состоять из двух граней (вообще это 2 треугольника с одной общей стороной), с нанесённой на них текстурой. Текстура будет содержать изображение символа из выбранного шрифта.
IDirect3DTexture9 *m_texture Текстура с изображением всех символов выводимого текста. Позднее мы более подробно рассмотрим интерфейс IDirect3DTexture9, когда будем внедрять в движок материалы и 3D-меши.
unsigned long m_textureWidth Ширина этой структуры.
unsigned long m_textureHeight Высота этой структуры.
float m_textureCoords [96][4] Задаёт текстурные координаты для идентификации символов на общей текстуре.
short m_spacing Указывает количество пикселей отступа каждого символа с левой и правой стороны.


Добавляем Font.срр (Проект Engine)

В файле исходного кода Font.cpp будет содержаться реализация класса Font + дополнительные функции для работы со шрифтами. За основу взят исходник примера Font из DirectX SDK.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Engine.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи "Font.срр".
  • Жмём "Добавить".
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле Font.срр набираем следующий код:
Font.срр (Проект Engine)
//-----------------------------------------------------------------------------
// File: 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. Во всплывающем меню выбираем "Перестроить".
Полученная в результате компиляции двоичная библиотека 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 для проверки работоспособности движка в нашем Решении GameProject01 мы создали второй Проект Test, после компиляции которого получили исполняемое приложение (файл .EXE), показывающее окно. В Главе 1.8 его функционал был заметно расширен. Применив, полученные в текущей Главе, знания на практике, снова дополним исходный код тестового приложения, оснастив его новым "функционалом". Если по каким-то причинам Проект Test отсутствует в Решении GameProject01, создай его, следуя инструкциям Главы 1.6.
ОК, приступаем.
  • Открой для редактирования файл исходного кода 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, который содержит весь исходный код тестового приложения. Перед нами готовые системы стейтов, пользовательского ввода и поддержки шрифтов в действии.

Исследуем код Main.cpp (Проект Test)

Одно из самых заметных изменений - мы выставили новое значение переменной 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
  • Жмём ОК, ОК.
  • Сохрани изменения в Решении (Файл->Сохранить все).
Сразу после этого перекомпилируем Проект Test:
  • В Обозревателе решений щёлкни правой кнопкой мыши по названию Проекта Test.
  • Во всплывающем контекстном меню выбираем: Перестроить.
Компиляция завершилась успешно. Итоговый исполняемый двоичный файл (Test.exe) расположен на жёстком диске ПК в той же директории, что и библиотека движка.
В нашем случае (Win7 x64) это: С:\Useгs\<Имя пользователя>\documents\visual studio 2010\Projects\GameProject01\Debug. (В разных ОС путь к файлу может отличаться от представленного).
  • Найди и запусти получившееся приложение.
Перед запуском основного окна появится диалоговое окно графических настроек. Все его поля и комбобоксы заполнены корректными данными и опциями. Радиокнопки Windowed/Fullscreen взаимоисключающи. При выборе Fullscreen активируются комбобоксы выбора разрешения экрана, формата цветности и частоты обновления.
При нажатии ОК в окне графических настроек приложение автоматически создаёт файл DisplaySettings.txt (ищи в той же папке, что и Test.exe). Завершение работы по нажатию Q не работает (т.к. не прописано в коде приложения). Жмём F12 для выхода через движок.
  • "Поиграй" с его исходным кодом, посмотри как оно работает в различных видеорежимах и разрешениях. Измени текст, выводимый на экран. Проверь поддержку русскоязычных шрифтов.

Исходные коды Решения GameProject01 для MSVC+ + 2010

Забираем здесь(external link).

Итоги главы

Обрати внимание на значимость момента. Мы ухитрились создать двоичный ресурс "диалоговое окно" в обход ограничений бесплатной MSVC++2010 Express. Да, редактора двоичных ресурсов в данной IDE нет, но есть возможность добавлять готовые ресурсы, созданные в сторонних приложениях (программа Resource Editor by AM далеко не единственная в своём роде). Конечно, диалог Graphic Settings выглядит не слишком презентабельно. Зато работает на "ура" и занимает минимум дискового пространства. В нашем случае диалог хранится в самом исполняемом файле Test.exe, размер которого всего 69 Кб. По теме двоичных ресурсов Windows написана не одна сотня весьма увесистых книг. Но мы коснулись её лишь в той части, которая нам необходима. Тестовое приложение, показывающее текст на экране - это уже отправная точка к созданию игры.
Так мы в очередной раз убедились, что для создания компьютерых игр нужен лишь компьютер с доступом в Интернет и желание.

Источники


1. Young V. Programming a Multiplayer FPS in DirectX 9.0. - Charles River Media, 2005


ДАЛЕЕ ==> Кодим 3D FPS DX9. 1.12 Добавляем поддержку звука

Последние изменения страницы Понедельник 25 / Июль, 2022 16:53:00 MSK

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

No records to display

Search Wiki Page

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

Категории

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