Создание класса для представления блоков

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

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

class TileClass
{
private:
int*m_iValue;
intm_iNumLayers;
float *m_fRotX;
float *m_fSize;

public:
TileClass();
~TileClass();
int iGetValue(int layer);
void vSetValue(int value, int layer);
float fGetRot(int layer);
void vSetRotation(float fRot, int layer);
float fGetSize(int layer);
void vSetSize(float fSize, int layer);
void vSetNumLayers(int layers);
};

В классе есть четыре закрытых члена. Первый из них, m_iValue, хранит значение блока. Предположим, у вас есть 1000 загруженных в память растровых изображений, предназначенных для рисования блоков. Значение равное 1, указывает, что для блока используется первое загруженное в память растровое изображение. Таким образом, значение блока — это ни что иное, как индекс в массиве растровых изображений.

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

Следующим в поле нашего внимания попадает член класса с именем m_fRotX. Эта переменная определяет угол поворота рассматриваемого блока. Она действительно полезна, чтобы добавить вашим картам разнообразия не добавляя нового содержимого. Чтобы создать полностью новую графику вам достаточно просто повернуть блок на 90 или больше градусов. Я использую здесь значение с плавающей точкой только потому, что в последнее время работаю исключительно с трехмерной графикой. Если вы создаете библиотеку для двухмерной блочной графики, то можете использовать здесь целое число.

Теперь мы переходим к члену данных m_fSize. Эта переменная содержит размер блока. Для трехмерного мира размер измеряется в единицах трехмерной системы координат. Для двухмерного мира размер задается в пикселах. Если вы используете блоки 64 x 64 точки, размер будет равен 64. Обратите внимание — я предполагаю, что блоки будут квадратными. Если вы решите использовать прямоугольные блоки, вам придется использовать для хранения их размера две переменные, например m_fSizeX и m_fSizeY.

Переменные класса уже продефилировали перед вами, так что теперь настало время для функций класса. Первые две функции, которые вы видите — это конструктор и деструктор класса. В них нет ничего специального — обычная чепуха С++.

Первая заслуживающая отдельного упоминания функция класса называется iGetValue(). Она применяется для получения значения указанного слоя блока. Это наиболее часто вызываемая функция класса, поскольку обращение к ней происходит каждый раз, когда рисуется данный блок карты. Приведенная в примере функция возвращает при вызове значение целочисленной переменной m_iValue. Я предпочитаю этот метод, поскольку использую возвращаемое число в качестве индекса в моем массиве данных блоков. Вы можете захотеть, чтобы функция возвращала дескриптор растрового изображения, структуру данных или какой-либо другой тип данных, используемых в вашей библиотеке.

Следующая функция класса, vSetValue(), получает два параметра. Первый параметр является целым числом, определяющим значение блока, которое вы хотите установить. Второй параметр задает слой в котором будет установлено это значение. Значения хранятся в переменной класса m_iValue.

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

Следующая функция, vSetRotation(), получает два параметра. Первый параметр задает угол поворота в градусах. Второй параметр определяет слой, к которому относится эта информация о повороте изображения. Сделанные изменения сохраняются в переменной класса m_fRotX.

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

Следующая функция, vSetSize(), получает два параметра. Первый параметр устанавливает размер блока в единицах трехмерной системы координат или в пикселах (для двухмерной графики). Второй параметр указывает слой, к которому эта информация относится. Полученные значения сохраняются в переменной класса m_fSize.

Последняя, но не менее важная функция класса — это vSetNumLayers(). Ей передается единственный параметр с именем layers. Главное назначение этой функции — установка количества слоев блока для хранения номеров растровых изображений, углов поворота и размеров.

Реализация класса
Приведенный ниже фрагмент кода содержит реализацию класса для блочной графики.

#include "TileClass.h"

// Конструктор
TileClass::TileClass()
{
// Инициализация внутренних переменных
m_iNumLayers = 0;
m_iValue = NULL;
m_fRotX = NULL;
m_fSize = NULL;
}
// Деструктор
TileClass::~TileClass()
{
// Освобождаем буфер слоев, если он был выделен
if(m_iValue)
delete [] m_iValue;
if(m_fRotX)
delete [] m_fRotX;
if(m_fSize)
delete [] m_fSize;
}
// Установка количества слоев
void TileClass::vSetNumLayers(int layers)
{
// Освобождаем ранее выделенные буферы слоев
if(m_iValue)
delete [] m_iValue;
if(m_fRotX)
delete [] m_fRotX;
if(m_fSize)
delete [] m_fSize;
// Выделяем память для буфера слоев
m_iValue = new int[layers];
memset(m_iValue, 0, layers * sizeof(int));

m_fRotX = new float[layers];
memset(m_fRotX, 0,layers * sizeof(int));

m_fSize = new float[layers];
memset(m_fSize, 0,layers * sizeof(int));

// Устанавливаем количество слоев
m_iNumLayers = layers;
}
// Получение значения блока
int TileClass::iGetValue(int layer)
{
// Проверяем правильность указанного номера слоя
if(layer >= m_iNumLayers) {
return(-1);
}
// Возвращаем значение
return(m_iValue[layer]);
}
// Установка значения блока
void TileClass::vSetValue(int value, int layer)
{
// Проверяем правильность указанного номера слоя
if(layer >= m_iNumLayers) {
return;
}
// Устанавливаем значение
m_iValue[layer] = value;
}
// Установка угла поворота
void TileClass::vSetRotation(float fRot, int layer)
{
// Проверяем правильность указанного номера слоя
if(layer >= m_iNumLayers) {
return;
}
m_fRotX[layer] = fRot;
}
// Установка размера блока
void TileClass::vSetSize(float fSize, int layer)
{
// Проверяем правильность указанного номера слоя
if(layer >= m_iNumLayers) {
return;
}
m_fSize[layer] = fSize;
}
// Получение угла поворота
float TileClass::fGetRot(int layer)
{
// Проверяем правильность указанного номера слоя
if(layer >= m_iNumLayers) {
return(-1.0f);
}
return(m_fRotX[layer]);
}
// Получение размера блока
float TileClass::fGetSize(int layer)
{
// Проверяем правильность указанного номера слоя
if(layer >= m_iNumLayers) {
return(-1.0f);
}
return(m_fSize[layer]);
}

Первой реализованной функцией является конструктор класса. В нем выполняется инициализация некоторых переменных класса. Внутри функции я устанавливаю количество слоев, равным 0. Я также присваиваю указателям на значения, размеры и углы поворота константу NULL. Это сделано для того, чтобы предотвратить случайное освобождение не выделенной ранее памяти, вызванное тем, что при создании экземпляра переменным могут быть присвоены случайные значения.

Следующая функция — это деструктор класса. Она просто проверяет была ли выделена какая-нибудь область памяти, и, если да, то освобождает ее.

Затем следует код функции TileClass::vSetNumLayers(), устанавливающей количество слоев блока. Поскольку вы можете указать в качестве количества слоев любое осмысленное число, главная задача этой функции заключается в выделении необходимой для каждого слоя памяти. Сначала функция освобождает выделенную ранее память. Затем она выделяет память для переменных класса m_iValue, m_fRotX и m_fSize. Как только эта задача выполнена, выделенная память заполняется нулями с помощью вызова функции memset(). Помните, что эта функция должна быть вызвана перед первым использованием объекта блока. Если вы попытаетесь получить данные несуществующего слоя, класс вернет ошибку.

Далее следует наиболее часто вызываемый метод класса, TileClass::iGetValue(). Первая часть кода функции проверяет, не пытается ли программист получить значение, которого не существует. Если из кода убрать эту проверку, программа может аварийно завершиться из-за попытки обращения к несуществующей памяти. Затем функция возвращает значение переменной класса m_iValue, относящееся к указанному слою.

Следующая функция, TileClass::vSetValue(), применяется для изменения значения указанного слоя блока. Вероятно, вы не будете использовать эту функцию слишком часто в игре, но она будет очень часто использоваться в вашей программе редактирования карт. Внутри функции производится не слишком много работы; она проверяет, что указан допустимый номер слоя, и затем устанавливает сооответствующее слою значение в члене данных класса m_iValue.

Следующие две функции, TileClass::vSetRotation() и TileClass::vSetSize, работают точно так же, как функция установки значения слоя, за исключением того, что они изменяют переменные класса m_fRotX и m_fSize.

Последние две функции, TileClass::fGetRot() и TileClass::fGetSize(), работают аналогично функции получения значения блока, за исключением того, что они возвращают значения членов данных класса m_fRotX и m_fSize.

Вот м все, что пока можно сказать о классе для представления блоков. Чтобы удостовериться, что я охватил основные возможности, посмотрите на приведенный далее пример, чтобы увидеть как разработанный класс для представления блоков используется без класса диспетчера карты.

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

void main()
{
int iMapWidth = 10;
int iMapHeight = 10;
TileClass *Tiles;
int iBMPToRender;

// Выделяем память для блоков
Tiles = new TileClass[(iMapWidth * iMapHeight)];

//
// Цикл выполняет перебор всех блоков
// и нинциализирует каждый из них
//
for(int i = 0; i < (iMapWidth * iMapHeight); i++) {
// Выделяем для каждого блока один слой
Tiles[i].vSetNumLayers(1);
// Присваиваем каждому блоку значение 0
Tiles[i].vSetValue(0, 0);
// Устанавливаем размер блока равным 64 пикселам
Tiles[i].vSetSize(64, 0);
}

//
// Отображение блоков с использованием
// фиктивной функции визуализации
//
// Отображение горизонтальных рядов
for(int y = 0; y < iMapHeight; y++) {
// Отображение блоков в каждом ряду
for(int x = 0; x < iMapWidth; x++) {
// Отображение конкретного блока
iBMPToRender = Tiles[x + (y * iMapWidth)].iGetValue(0);
vRenderTile(x, y, iBMPToRender);
}
}
}

Первый пункт, на который следует обратить внимание — это выделение памяти для хранения карты мира. Вместо выделения массива целых чисел, я выделяю массив объектов класса TileClass. Вам необходимо достаточное количество блоков, чтобы хранить одно значение для каждой позиции на карте, поэтому для того, чтобы выяснить, сколько блоков выделить, я умножаю ширину карты на ее высоту.

В следующем фрагменте кода в цикле осуществляется перебор и инициализация всех только что созданных объектов TileClass. Сперва в коде количество слоев устанавливается равным 1. Это позволяет задавать для каждого блока одно значение. Затем, значение каждого блока устанавливается равным 0. Последняя часть кода устанавливает размер каждого блока, равным 64 единицам.

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

Если хотите, перед продолжением чтения поиграйтесь немного с приведенным кодом. Лично я, перед тем как перейти к следующей теме собираюсь поиграть в America's Army. Если хотите, зарегистрируйтесь и попробуйте найти меня; имя моего игрока — LostLogic.