netlib.narod.ru< Назад | Оглавление | Далее >

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

Как вы уже видели в предыдущей главе, нарисовать небольшой прямоугольный текстурированный полигон, который замечательно подойдет для представления ваших блоков, совсем не сложно. Вы можете выполнить это с помощью специального объекта D3DX с именем ID3DXSprite. В главе 6, «Создаем ядро игры», я упоминал интерфейс ID3DXSprite, когда обсуждал графическое ядро; теперь у вас есть шанс поближе познакомиться с этим интерфейсом.

У объекта ID3DXSprite одна задача — рисовать на экране прямоугольные полигоны, используя назначенную вами текстуру. Конечно же, в предоставленной текстуре будут упакованы блоки.

Чтобы начать использовать блоки в Direct3D, объявите экземпляр объекта ID3DXSprite и используйте для его инициализации функцию D3DXCreateSprite:

// g_pD3DDevice = ранее инициализированный объект устройства
ID3DXSprite *pSprite = NULL;

if(FAILED(D3DXCreateSprite(g_pD3DDevice, &pSprite))) {
    // Произошла ошибка
}

Как видите, функция D3DXCreateSprite получает два параметра — указатель на ранее инициализированный объект трехмерного устройства и указатель на создаваемый объект ID3DXSprite. После этого вызова функции все готово к работе. Вам необходимо только загрузить текстуру, представляющую блок (или блоки), который вы собираетесь рисовать и воспользоваться функцией ID3DXSprite::Draw для рисования блока:

HRESULT ID3DXSprite::Draw(
     IDirect3DTexture9 *pSrcTexture,     // Используемая текстура
     CONST RECT        *pSrcRect,        // Прямоугольник источника
     CONST D3DXVECTOR2 *pScaling,        // Вектор масштабирования
     CONST D3DXVECTOR2 *pRotationCenter, // Центр вращения
     FLOAT             Rotation,         // Угол поворота
     CONST D3DVECTOR2  *pTranslation,    // Вектор перемещения
     D3DCOLOR          Color);           // Модуляция цвета

Трюк в использовании функции Draw заключается в конструировании структуры RECT для прямоугольника источника, содержащей координаты фрагмента внутри текстуры, который вы хотите использовать как блок. Например, у вас есть текстура, размером 256 × 256 пикселей, содержащая 64 блока (каждый размером 32 × 32 пикселя), упакованных в 8 строк и 8 столбцов (как показано на рис. 7.2).


Рис. 7.2. 64 блока, упорядоченные в таблицу 8 Х 8

Рис. 7.2. 64 блока, упорядоченные в таблицу 8 × 8


 

СОВЕТ
Чтобы повысить эффективность, упаковывайте в используемую для блоков текстуру столько блоков, сколько можете поместить и располагайте их в виде строк и столбцов. Например, если у вас 64 блока размером 32 × 32 пиксела, создайте растровое изображение для хранения 8 строк и 8 столбцов блоков, что дает вам текстуру размером 256 × 256 пикселей. Такое упорядочивание блоков показано на рис. 7.2. Блоки последовательно нумеруются, начиная с верхнего левого угла. Блок, находящийся в левом верхнем углу — это блок 0. Справа от него находится блок 1 и так далее, по всем строкам, пока не доберемся до блока 63 (нижнего правого блока). В этом случае вы ссылаетесь на блок по его номеру и позволяете процедуре рисования блока самой вычислять его координаты в текстуре.

Если вы рисуете второй слева и третий сверху блок, прямоугольник источника будет занимать координаты от 64, 96 до 96, 128. Поскольку размер всех блоков 32 × 32, необходимо знать только координаты верхнего левого угла блока в текстуре; для вычисления нижней и правой координат блока достаточно просто прибавить 32. Вот как инициализируется структура RECT с использованием этих значений:

RECT SrcRect;
SrcRect.left   = 64;                // Левая координата блока
SrcRect.top    = 96;                // Верхняя координата блока
SrcRect.right  = SrcRect.left + 32; // Добавляем ширину блока
SrcRect.bottom = SrcRect.top  + 32; // Добавляем высоту блока

Обратите внимание, что если pSrcRect присвоить значение NULL, то функция рисования будет использовать в качестве блока всю текстуру целиком. Параметр pScaling — это вектор, определяющий как вы хотите масштабировать размеры блока. Если вы не будете использовать масштабирование, укажите NULL; в ином случае сконструируйте вектор, содержащий коэффициенты масштабирования.

СОВЕТ
Возможность масштабировать блоки в Direct3D открывает некоторые удивительные возможности. Вы можете использовать небольшие изображения, скажем 16 × 16 пикселей, и рисовать их в большем размере, например, 64 × 64 пикселя. При рисовании блока текстура растягивается, чтобы соответствовать масштабированному блоку. Применяя для карт такие масштабируемые блоки вы можете значительно увеличить размеры карт и в то же время сэкономить память, используемую для текстур блоков. Я подробнее расскажу об этой техинке в разделе «Библиотека для работы с большими растрами» этой главы.

Масштабирование — это единственная из специальных возможностей работы с блоками, которую я собираюсь использовать. Так что вы можете присвоить pRotationCenter значение NULL, а Rotation — 0,0.

Заслуживает внимания параметр pTransform, представляющий собой вектор, который сообщает объекту спрайта в каком месте (в экранных координатах от 0 до ширины или высоты окна) рисовать блок. Обратите внимание, что все блоки рисуются сверху вниз и слева направо, подразумевая, что началу координат соответствует левый верхний угол блока.

Последний аргумент функции Draw — это Color, являющийся значением типа D3DCOLOR, используемым для корректировки выходной текстуры. Обычно вы здесь задаете значение 0xFFFFFFFF, чтобы блоки рисовались точно так же, как они выглядят в текстуре, но, используя макрос D3DCOLOR_RGBA вы можете при рисовании изменять цвета или альфа-значения блоков.

Например, чтобы нарисовать блок с половинным альфа-значением, используйте:

D3DCOLOR_RGBA(255,255,255,127); // 127 = половина

Как видите, значения берутся из диапазона от 0 (цвет или альфа-значение удаляется) до 255 (цвет или альфа-значение остается неизменным). Точно так же, как мы меняли альфа-значение, можно полностью вырезать красную составляющую, используя следующий код:

D3DCLOR_RGBA(0,255,255,255); // 0 = нет цвета

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

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

// Убедитесь, что перед вызовом этой функции вы вызвали
// IDirect3DDevice::BeginScene и загрузили текстуру,
// используемую для хранения блоков.
// pSprite = ранее инициализированный объект ID3DXSprite
void DrawTile(float SrcX, float SrcY,
              float DestX, float DestY,
              float TileWidth, float TileHeight
              float ScaleX, float ScaleY,
              IDirect3DTexture9 *pTileTexture,
              D3DCOLOR Color)
{
    RECT SrcRect; // Прямоугольник источника

    // Задаем прямоугольник источника
    SrcRect.left   = SrcX;
    SrcRect.top    = SrcY;
    SrcRect.right  = SrcRect.left + TileWidth;
    SrcRect.bottom = SrcRect.top  + TileHeight;

    // Рисуем блок, используя заданные координаты,
    // цвет и масштаб. Если вы хотите, чтобы блок
    // был нарисован в исходном масштабе, установите
    // для ScaleX и ScaleY значения 1.0
    pSprite->Draw(pTileTexture, &SrcRect,
                  &D3DXVECTOR2(ScaleX, ScaleY), NULL, 0.0f,
                  &D3DXVECTOR2(DestX, DestY), Color);
}

 

ПРИМЕЧАНИЕ
Как видите, я добавил в функцию DrawTile возможность масштабирования блоков. Это мощная техника, которую я использую дальше в этой главе в разделе «Библиотека для работы с большими растрами».

Хотя использование объекта ID3DXSprite для рисования блоков сперва может показаться странным (и слегка неоптимизированным), я уверяю вас, что он хорошо справляется со своей работой. Далее у нас есть один путь — построить специальный класс, который будет работать с блоками за вас, включая их загрузку и рисование.

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

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

class cTiles
{
  private:
    cGraphics *m_Graphics; // Родительский объект cGraphics

    long m_NumTextures;   // Количество текстур
    cTexture *m_Textures; // Массив cTexture

    short *m_Widths;      // Массив широт блоков
    short *m_Heights;     // Массив высот блоков
    short *m_Columns;     // Количество столбцов в текстуре

  public:
    cTiles();
    ~cTiles();

    // Функции для создания и освобождения интерфейса блока
    BOOL Create(cGraphics *Graphics, long NumTextures);
    BOOL Free();

    // Функции для загрузки и освобождения отдельной текстуры
    BOOL Load(long TextureNum, char *Filename,
              short TileWidth = 0, short TileHeight = 0,
              D3DCOLOR Transparent = 0,
              D3DFORMAT Format = D3DFMT_A1R5G5B5);
    BOOL Free(long TextureNum=-1);

    // Функции для получения размеров блока
    // и количества блоков в текстуре
    long GetWidth(long TextureNum);
    long GetHeight(long TextureNum);
    long GetNum(long TextureNum);

    // Разрешение и запрещение прозрачности
    BOOL SetTransparent(BOOL Enabled = TRUE);

    // Рисование блока в указанном месте
    BOOL Draw(long TextureNum, long TileNum,
              long ScreenX, long ScreenY,
              D3DCOLOR Color = 0xFFFFFFFF,
              float XScale = 1.0f, float YScale = 1.0f);
};

 

ПРИМЕЧАНИЕ
Все открытые функции класса cTiles возвращают значение типа BOOL; TRUE означает успешное завершение, а FALSE — возникновение непредвиденной ошибки.

Представленный здесь класс cTiles работает выделяя массив объектов cTexture, в которых будет храниться блочная графика. Код класса cTiles находится на прилагаемом к книге компакт-диске в папке с примерами к данной главе (загляните в папку \BookCode\Chap07\). В следующих разделах приведен код открытых функций, описание того, что они делают, и того, как их вызывать.

cTiles::Create

BOOL cTiles::Create(
     cGraphics *Graphics, // Ранее инициализированный объект cGraphics
     long NumTextures);   // Количество создаваемых объектов текстур

Первая функция, cTiles::Create, выделяет память для массива объектов cTexture, в которых будет храниться блочная графика. Убедитесь, что передаете функции Create ранее инициализированный объект cGraphics и указываете количество текстур, достаточное для хранения блоков.

BOOL cTiles::Create(cGraphics *Graphics, long NumTextures)
{
    Free(); // Освобождаем все от существующих данных

    // Проверка ошибок
    if((m_Graphics = Graphics) == NULL)
        return FALSE;
    if((m_NumTextures = NumTextures) == NULL)
        return FALSE;

    // Выделяем память для объектов текстур
    if((m_Textures = new cTexture[m_NumTextures]) == NULL)
        return FALSE;

    // Выделяем память для высот, широт и количества столбцов
    m_Widths = new long[m_NumTextures];
    m_Heights = new long[m_NumTextures];
    m_Columns = new long[m_NumTextures];

    return TRUE; // Успешное завершение!
}

cTiles::Free

BOOL cTiles::Free();

Функция не получает параметров, поскольку освобождает все ресурсы и объекты класса. Никакие вызовы Load, Draw или Free не будут работать, пока экземпляр класса cTiles не будет заново инициализирован вызовом cTiles::Create.

BOOL cTiles::Free()
{
    m_Graphics = NULL;

    // Освобождаем все текстуры
    if(m_NumTextures) {
        for(short i = 0; i < m_NumTextures; i++)
            m_Textures[i].Free();
    }
    delete [] m_Textures;
    m_Textures = NULL;

    // Освобождаем массивы высот широт и столбцов
    delete [] m_Widths;
    delete [] m_Heights;
    delete [] m_Columns;
    m_Widths = m_Heights = m_Columns = NULL;
    m_NumTextures = 0;

    return TRUE;
}

cTiles::Load

BOOL cTilesLoad(
     long      TextureNum,  // Номер текстуры, куда загружается графика
     char      *Filename,   // Имя загружаемого файла с изображением (*.bmp)
     short     TileWidth,   // Ширина блоков в изображении
     short     TileHeight,  // Высота блоков в изображении
     D3DCOLOR  Transparent, // Прозрачный цвет (установите альфа=255)
     D3DFORMAT Format);     // Формат хранения

Функция cTiles::Load выполняет загрузку текстуры в указанный элемент массива текстур. Например, если вы создали объект cTiles для использования пяти текстур, можно указать любой элемент от 0 до 4, в который и будет загружена текстура. На все текстуры ссылаются по их индексу в массиве текстур.

Загружая файл текстуры вы должны указать размер хранящихся в текстуре блоков (измеряется в пикселях). Эти блоки должны быть упакованы в текстуру слева направо и сверху вниз, причем первый блок начинается с верхнего левого пикселя текстуры. Например, у вас может быть текстура, содержащая 64 блока, каждый размером 32 × 32 пикселя. Это означает, что в текстуре 8 строк и 8 столбцов блоков, как показано на рис. 7.2.

Последние два параметра полезны только если вы используете копирование с учетом прозрачности. Присвойте параметру Transparent допустимое значение D3DCOLOR (используйте D3DCOLOR_RGBA или аналогичный макрос, убедившись, что указали альфа-значение 255) и либо оставьте для Format значение по умолчанию D3DFMT_A1R5G5B5, либо задайте собтвенный формат из предоставляемого Direct3D списка допустимых форматов.

Вот код функции Load:

BOOL cTiles::Load(long TextureNum, char *Filename,
                  short TileWidth, short TileHeight,
                  D3DCOLOR Transparent, D3DFORMAT Format)
{
    // Проверка ошибок
    if(TextureNum >= m_NumTextures || m_Textures == NULL)
        return FALSE;

    // Освобождаем требуемую текстуру
    Free(TextureNum);

    // Загружаем текстуру
    if(m_Textures[TextureNum].Load(m_Graphics, Filename,
                                   Transparent, Format) == FALSE)
        return FALSE;

    // Сохраняем значение ширины (получаем ширину текстуры,
    // если не задан параметр TileWidth).
    if(!TileWidth)
        m_Widths[TextureNum] = m_Textures[TextureNum].GetWidth();
    else
        m_Widths[TextureNum] = TileWidth;

    // Сохраняем значение высоты (получаем высоту текстуры,
    // если не задан параметр TileHeight).
    if(!TileHeight)
        m_Heights[TextureNum] = m_Textures[TextureNum].GetHeight();
    else
        m_Heights[TextureNum] = TileHeight;

    // Вычисляем склько столбцов блоков находится в текстуре.
    // Это используется для ускорения вычислений при рисовании блоков.
    m_Columns[TextureNum] = m_Textures[TextureNum].GetWidth() /
        m_Widths[TextureNum];

    return TRUE;
}

cTiles::Free

BOOL cTiles::Free(long TextureNum); // Номер освобождаемой текстуры

Функция освобождает отдельную текстуру из массива, оставляя возможность ее повторного использования путем вызова cTiles::Load. Просто укажите номер освобождаемой текстуры в аргументе TextureNum. Функцию можно использовать для освобождения старых текстур и выделения места для новых.

BOOL cTiles::Free(long TextureNum)
{
    // Проверка ошибок
    if(TextureNum >= m_NumTextures || m_Textures == NULL)
        return FALSE;

    // Освобождаем ресурсы отдельной текстуры
    m_Textures[TextureNum].Free();

    return TRUE;
}

cTiles::GetWidth, cTiles::GetHeight и cTiles::GetNum

long cTiles::GetWidth(long TextureNum); // Номер интересующей текстуры
long cTiles::GetHeight(long TextureNum);
long cTiles::GetNum(long TextureNum);

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

long cTiles::GetWidth(long TextureNum)
{
    // Проверка ошибок
    if(TextureNum >= m_NumTextures || m_Widths == NULL)
        return 0;

    return m_Widths[TextureNum];
}

long cTiles::GetHeight(long TextureNum)
{
    // Проверка ошибок
    if(TextureNum >= m_NumTextures || m_Heights == NULL)
        return 0;

    return m_Heights[TextureNum];
}

long cTiles::GetNum(long TextureNum)
{
    // Проверка ошибок
    if(TextureNum >= m_NumTextures || m_Textures == NULL ||
       m_Columns == NULL || m_Widths == NULL || m_Heights == NULL)
        return 0;

    return m_Columns[TextureNum] +
           m_Textures[TextureNum].GetHeight() /
           m_Heights[TextureNum];
}

cTiles::SetTransparent

BOOL cTiles::SetTransparent(BOOL Enabled); // Разрешить/запретить

Функция cTiles::SetTransparent разрешает или запрещает альфа-проверку, а это значит, что текстуры, загруженные с соответствующим цветовым ключом и форматом когда разрешено, будут использовать копирование с учетом прозрачности. По умолчанию параметру Enable присваивается TRUE. Взгляните на код функции SetTransparent:

BOOL cTiles::SetTransparent(BOOL Enabled)
{
    // Проверка ошибок
    if(m_Graphics == NULL)
        return FALSE;

    return m_Graphics->EnableAlphaTesting(Enabled);
}

cTiles::Draw

BOOL cTiles::Draw(
     long     TextureNum, // Номер рисуемой текстуры
     long     TileNum,    // Номер рисуемого блока
     long     ScreenX,    // Координата X
     long     ScreenY,    // Координата Y
     D3DCOLOR Color,      // Значение модулирования цвета
     float    XScale,     // Коэффициент масштабирования по x
     float    YScale);    // Коэффициент масштабирования по y

Эта функция используется для рисования ваших блоков (через метод Blit вашего объекта ID3DXSprite). Как только текстура загружена в массив, вы можете рисовать отдельные, содержащиеся в ней блоки. Все блоки нумеруются, начиная с нуля; нумерация идет с верхнего левого угла и увеличивается слева направо и сверху вниз. Например, в текстуре с 64 блоками (в сетке 8 × 8) нумерация идет от 0 до 63. Блок 0 — это верхний левый блок, под ним располагается блок 8, а справа от него — блок 1.

При рисовании вам надо указать экранные координаты в переменных типа long (позднее они будут преобразованы в значения float), а также коэффициенты масштабирования (для увеличения или уменьшения размера блока относительно исходного). Как упоминалось ранее, значение модуляции применяется для изменения цвета или альфа-значения с использованием интерфейса ID3DXSprite.

BOOL cTiles::Draw(long TextureNum, long TileNum,
                  long ScreenX, long ScreenY,
                  D3DCOLOR Color, float XScale, float YScale)
{
    long SrcX, SrcY;

    // Проверка ошибок
    if(m_Graphics == NULL)
        return FALSE;
    if(TextureNum >= m_NumTextures || m_Textures == NULL)
        return FALSE;

    // Вычисление координат источника блока в текстуре
    SrcX = (TileNum % m_Columns[TextureNum]) * m_Widths[TextureNum];
    SrcY = (TileNum / m_Columns[TextureNum]) * m_Heights[TextureNum];

    return m_Textures[TextureNum].Blit(ScreenX, ScreenY,
                                       SrcX, SrcY,
                                       m_Widths[TextureNum],
                                       m_Heights[TextureNum],
                                       XScale, YScale);
}

Использование класса работы с блоками

Вот пример, который загружает две текстуры с блоками. Первая текстура содержит 64 блока, каждый размером 32 × 32 пикселя. Вторая текстура содержит 16 блоков, каждый размером 64 × 64 пикселя.

// Graphics = ранее инициализированный объект cGraphics
cTiles Tile;

// Создаем класс блоков с местом для двух текстур
Tile.Create(Graphics, 2);

// Загружаем обе текстуры, указывая,
// что черный цвет будет прозрачным
Tile.Load(0, "Tiles1.bmp", 32, 32,
          D3DCOLOR_RGBA(0,0,0,255), D3DFMT_A1R5G5B5);
Tile.Load(1, "Tiles2.bmp", 64, 64,
          D3DCOLOR_RGBA(0,0,0,255), D3DFMT_A8R8G8B8);

// Рисуем пару блоков из первой текстуры без прозрачности
Tile.SetTransparent(FALSE);

// Блок 0 (в точке 128, 128) и 3 (в точке 0, 0)
Tile.Draw(0, 0, 128, 128);
Tile.Draw(0, 3, 0, 0);

// Рисуем пару блоков из второй текстуры с прозрачностью
Tile.SetTransparent(TRUE);

// Блок 1 (в точке 28, 18) и 16 (в точке 100, 90)
Tile.Draw(1, 1, 28, 18);
Tile.Draw(1, 16, 100, 90);

// Освобождаем класс работы с блоками и текстуры
Tile.Free();

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


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz