Загрузка...
 
Печать

DirectX Graphics (DirectX 9). Анимированные спрайты (Animated Sprites)

Язык программирования: С++
Платформа: Win32

Закрыть
noteОбрати внимание

В литературе по DirectX Graphics и в исходных кодах примеров можно встретить множество отсылок именно к Direct3D. Часто пишут Direct3D, подразумевая его более современный аналог DirectX Graphics. И наоборот. Таким образом часто (но не всегда) эти два названия синонимичны.


Т.к. наш сайт посвящён созданию компьютерных игр, ряд функций будут созданы "с прицелом" на дальнейшую разработку игрового движка.

Содержание

Intro

В этой статье мы научимся создавать и применять 2D-спрайты, которые широко применяются при создании 2D-игр средствами Direct3D.1 Спрайты представляют собой маленькие растры (bitmaps; часто с альфаканалом прозрачности), которые выводятся на экран и обозначают определённые игровые объекты (например космический корабль или фигурку игрока/противника в 2D-платформере). Все 2D-игры используют спрайты наряду с т.н. тайлами (англ. "tiles" - плитки). Тайлы применяются для заполнения фона игровой сцены в большинстве 2D-игр. Статья также расскажет о том, как контролировать анимацию спрайта (если есть) и как перемещать спрайт по экрану. С точки зрения Direct3D 9 (и выше) спрайт представляет собой прямоугольный 3D-объект с текстурой, состоящий из двух треугольников с одной общей стороной.

Техника отрисовки анимированных спрайтов средствами Direct3D

Существует 2 способа вывода спрайтов средствами Direct3D. Оба они предполагают отслеживание положения (позиции) спрайта, его размера, скорости и других свойств.
Первый способ - простой. Загружаем изображение спрайта в D3D-поверхность (surface) и выводим путём вызова функции StretchRect.
Второй способ сложнее, но более продвинутый. Применяем специальный объект D3DXSprite, который специально создан для работы со спрайтами. D3DXSprite для хранения изображений спрайта использует текстуры вместо поверхностей. В любом случае загрузка изображения в текстуру не сложнее загрузки оного в поверхность.
Воспользуемся вторым способом и создадим Проект приложения, выводящего на экран анимированный спрайт.

Объект D3DXSprite

  • Представляет собой специальный дескриптор (handler), содержащий функции отрисовки спрайта из файла-текстуры (с примененим различных трансформаций).
  • Объявляется, например, так:
LPD3DXSPRITE sprite_handler;

Далее созданный объект применяется путём вызова функции D3DXCreateSprite, которая "присоединяет" дескриптор спрайта к основному объекту устройства Direct3D и его устройству, давая знать, с какими параметрами следует отрисовывать спрайты в бэкбуфер. Вот её прототип:

Прототип функции D3DXCreateSprite
HRESULT WINAPI D3DXCreateSprite(LPDIRECT3DDEVICE9 pDevice, LPD3DXSPRITE *ppSprite);

Вот пример её применения:

result = D3DXCreateSprite(d3ddev, &sprite_handler);

Применение объекта D3DXSprite позволяет изменять спрайт различными способами а также отрисовывать его с использованием аппаратного 3D-ускорения (если есть). Спрайты с прозрачностью также легко отрисовываются путём указания альфа-цвета (он же альфа-канал, канал прозрачности) у изображения-источника. Чёрный (0,0,0) наиболее часто используется для цвета прозрачности. Иногда также использую розовый цвет (255,0,255) т.к. он редко применяется в самих изображениях.

Начало вывода объекта D3DXSprite

Спрайты могут рендериться сразу после вызова функции BeginScene у основного объекта устройства Direct3D. Вот её прототип:

HRESULT Begin(DWORD Flags);

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

sprite_handler->Begin(D3DXSPRITE_ALPHABLEND);

Отрисовка спрайта (Drawing the sprite)

...с применением объекта D3DXSPRITE чуть сложнее чем оная с примнением поверхностей (где просто блиттируют изображение, указав прямоугольники источника и получателя). В то же время D3DXSPRITE отрисовывает изображение путём вызова всего одной функции Draw, которая выполняет все трансформации. Поняв принцип её работы, можно легко выполнять указание прозрачности (transparency), масштабирование (scaling) и вращение (rotation) путём указания нужных параметров. Вот её прототип:

Прототип функции Draw
HRESULT Draw(
 LPDIRECT3DTEXTURE9 pTexture,
 CONST RECT *pSrcRect,
 CONST D3DXVECTOR3 *pCenter,
 CONST D3DVECTIOR3 *pPosition,
 D3DCOLOR Color
);

Вот описание её параметров:

Параметр Описание
pTexture Указатель на текстуру, являющуюся исходным изображением для спрайта.
*pSrcRect Прямоугольная область. Применяется для указания участка текстуры, выбираемого (tiled out) в качестве изображения спрайта. Изображения с несколькими текстурами (тайлами) широко применялись в игрокодинге конца 90-х - начала 2000-х. Кроме того, текстура с алфавитом лежит в основе русификации многих игр, в том числе на игровых консолях Sony PlayStation.
*pCenter Точка вращения спрайта.
*pPosition Позиция спрайта. Здесь обычно указываются координаты по осям X и Y.
Color Изменения цвета изображения выводимого спрайта. Не влияет на цвет прозрачности.

D3DXVECTOR3 - это новый тип данных, появившийся в DirectX 9.0b. Он включает в себя 3 переменных члена: x, y и z.

typedef struct D3DXVECTOR3
{
 FLOAT x;
 FLOAT y;
 FLOAT z;
} D3DXVECTOR3;

При перемещении спрайта по 2D-поверхности экрана нужны лишь координаты x и y.
Через пару абзацев мы рассмотрим реальный пример применения функции Draw.

Остановка дескриптора спрайта (Stopping the Sprite Handler)

Сразу после завершения отрисовки спрайта, но ДО вызова EndScene, необходимо вызвать спецметод D3DXSPRITE.End для разблокировки поверхности (чтобы её могли также применить другие процессы). Вот синтаксис:

HRESULT End(VOID);

Вот пример применения:

sprite_handler->End();

Загрузка изображения спрайта (Loading the Sprite Image)

В стародавние времена процедуры-блиттеры для 2D-спрайта писали на "голом" ассемблере. Теперь всё это выполняют встроенные методы Direct3D.
Первое, на что нужно обратить внимание при использовании D3DXSPRITE, это то, что данный интерфейс для отрисовки спрайта применяет текстуру, а не поверхность. Поэтому здесь самым "ходовым" объектом будет LPDIRECT3DTEXTURE9. Технически это также быстро, как и рендеринг с помощью поверхностей (при использовании аппаратного ускорения).

Создаём объект текстуры

Объявим объект текстуры, в который позднее загрузим изображение спрайта:

LPDIRECT3DTEXTURE9 texture = NULL;

Получаем размеры файла изображения. Функция D3DXGetImageInfoFromFile

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

D3DXIMAGE_INFO info;
result = D3DXGetImageInfoFromFile("image.bmp", &info);

При наличии файла с картинкой, в (заранее созданную) структуру info буду записаны ширина (width) и высота (height) изображения в пикселах. Эти данные необходимы для следующего шага.

Загружаем изображение спрайта из файла растрового изображения. Функция D3DXCreateTextureFromFileEx

Загружаем изображение из файла прямо в объект текстуры с помощью функции D3DXCreateTextureFromFileEx. Вот её прототип:

Прототип функции D3DXCreateTextureFromFileEx
HRESULT WINAPI D3DXCreateTextureFromFileEx(
 LPDIRECT3DDEVICE9 pDevice,
 LPCSTR pSrcFile,
 UINT Width,
 UINT Height,
 UINT MipLevels,
 DWORD Usage,
 D3DFORMAT Format,
 D3DPOOL Pool,
 DWORD Filter,
 DWORD MipFilter,
 D3DCOLOR ColorKey,
 D3DXIMAGE_INFO *pSrcInfo,
 PALETTEENTRY *pPalette,
 LPDIRECT3DTEXTURE9 *ppTexture
);

Выглядит внушительно. На деле большинство её параметров выставляются по умолчанию либо в NULL.

Объединяем вышеперечисленные фрагменты кода в одну авторскую функцию-обёртку (wrapper-function) LoadTexture

Функция выполняет все шаги по подготовке к загрузке текстуры из файла + непосредственно производит загрузку:

Авторская функция-обёртка LoadTexture
LPDIRECT3DTEXTURE9 LoadTexture(char *filename, D3DCOLOR transcolor)
{
 // Указатель на объект текстуры
 LPDIRECT3DTEXTURE9 texture = NULL;
 
 // Структура для хранения информации о файле изображения
 D3DXIMAGE_INFO info;
 
 // Стандартное Windows-значение, возвращаемое функцией
 HRESULT result;

 // Получаем ширину (width) и высоту (height) изображения из загружаемого файла
 result = D3DXGetImageInfoFromFile(filename, &info);
 if(result != D3D_OK)
  return NULL;

 // Создаём новую текстуру путём загрузки битмапа (=файла растрового изображения)
 D3DCreateTextureFromFileEx(
  d3ddev,  // Объект устройства Direct3D
  filename,  // Имя файла ращстрового изображения
  info.Width,  // Ширина растрового изображения
  info.Height,  // Высота растрового изображения
  1,  // Число мип-мэп уровней (mip-map levels; 1 - значит без цепочек)
  D3DPOOL_DEFAULT,  // Тип поверхности (стандартная)
  D3DFMT_UNKNOWN,  // Формат цветности поверхности (D3DFMT_UNKNOWN = формат по умолчанию)
  D3DPOOL_DEFAULT,  // Пул памяти для текстуры
  D3DX_DEFAULT,  // Фильтр изображения
  D3DX_DEFAULT,  // Мип-фильтр изображения
  transcolor,  // Ключевой цвет прозрачности
  &info,  // Структура с информацией о загруженном файле изобаржения
  NULL,  // Цветовая палитра
  &texture);  // Результирующий объект текстуры

 // Проверяем, что изображение загружено успешно
 if(result != D3D_OK)
  return NULL;

 return texture;
)

Пример приложения, выводящего на экран анимированный спрайт

Создадим Проект приложения, демонстрирующий работу со спрайтами с применением объекта D3DXSPRITE. За основу возьмём исходный код Проекта из статьи DirectX Graphics (DirectX 9). Поверхности и растры (финальный вариант Проекта D3D9Surface01 в конце статьи), который, в свою очередь, основан на исходном коде базового приложения Windows из статьи Создание приложений (Cpp, Win32)). Мы продолжим разносить код в отдельные файлы, увеличив степень его модульности. Будут созданы отдельные пары файлов .cpp и .h для Windows-функций, DirectX-функций и функций самой игры. Хорошо структурированный код - отличная основа будущего игрового движка.

Перед началом на компьютер необходимо установить следующие программные компоненты:

  • MS Visual C++ 2010 Express;
  • DirectX SDK 9.

Все эти штуки:

  • + инструкции по их установке ты найдёшь в разделе "Софт" нашего сайта;
  • Бесплатны;
  • Без труда гуглятся.

Создаём Проект приложения D3D9ASprite01

  • Создай пустой Проект с именем D3D9ASprite01. Проект автоматически разместится внутри Решения с таким же именем.

Весь процесс подробно расписан в статьях

Добавляем в Проект файл исходного кода WinMain.cpp

В этот раз WinMain.cpp будет содержать только код, специфичный для Windows-приложения.
Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.

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

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

  • В только что созданном и открытом файле WinMain.cpp набираем исходный код простейшего оконного приложения, взятого из статьи Создание приложений (Cpp, Win32):
WinMain.cpp
// Файл: WinMain.cpp 
// Исходный код Windows-фреймворка
// www.igrocoder.ru 2021
// При использовании просьба указывать ссылку на источник.

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include <time.h>
#include <stdio.h>
#include "dxfunc.h"
#include "game.h"

// Прототип функции WinMain
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow);
// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения
	CoInitialize(NULL);

	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=NULL;		// Курсор при наведении на окно
	wndClass.hbrBackground=NULL;	// Закрашиваем окно белым цветом
	wndClass.lpszMenuName=NULL;		// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",			// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW, // Основной стиль окна
		0, 0,					// Координаты X и Y
		iWidth,	// Ширина окна
		iHeight,	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		wndClass.hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные

  if(!hWnd)
    return FALSE;

  ShowWindow(hWnd, SW_NORMAL);
  UpdateWindow(hWnd);

  // Очищаем структуру сообщения
  ZeroMemory(&msg, sizeof(MSG));

  if(GameInit(hWnd) == TRUE) 
  {
	  while (true) 
	  {
		  if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) 
		  {
			  TranslateMessage(&msg);
			  DispatchMessage(&msg);
			  if (msg.message == WM_QUIT) break;
		  }
		  else
		  {
		   // Главный цикл программы (mainloop)
		   GameRun(hWnd);
		  }
	  }
  }

    CoUninitialize();

  //Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	return (msg.wParam);
}

// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		GameEnd(hWnd);
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		return 0;
	}
		// В случае любых других сообщений...
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

Добавляем в Проект заголовочный файл dxfunc.h

dxfunc.h содержит объявления DirectX-функций, реализованных в dxfunc.cpp.
ОК, приступаем.

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

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

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

#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9math.h>

// Прототипы функций
HRESULT d3d9Init(IDirect3D9 **d3d, 
                IDirect3DDevice9 **d3ddev,
                HWND hWnd, 
				DWORD iWidth,
				DWORD iHeight,
				BOOL bFullScreen
				);
LPDIRECT3DSURFACE9 LoadSurface(char *, D3DCOLOR);
LPDIRECT3DTEXTURE9 LoadTexture(char *, D3DCOLOR);

// Объявления переменных
extern LPDIRECT3D9 d3d;
extern LPDIRECT3DDEVICE9 d3ddev;
extern LPDIRECT3DSURFACE9 backbuffer;

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

Авторская функция d3d9Init выполняет инициализацию Direct3D и впервые создана в статье DirectX Graphics (DirectX 9). Поверхности и растры. Шестой её параметр представляет собой булево значение, отслеживающее использование полноэкранного режиме. При bFullScreen=TRUE приложение работает в полноэкранном режиме.
В этот раз из WinMain.cpp сюда также "переехали" объявления объектов Direct3D. Перед каждым стоит служебное слово "extern", означающее их "открытость" для применения в функциях за пределами dxfunc.h .

Добавляем в Проект файл исходного кода dxfunc.cpp

В dxfunc.cpp содержатся реализации функций, объявленных в dxfunc.h.
ОК, приступаем.

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

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

  • В только что созданном и открытом файле dxfunc.cpp набираем следующий код:
dxfunc.cpp
// Файл: dxfunc.cpp
// Реализация DX-функций, объявленных в dxfunc.h.
// Сборник DirectX-функций

#include "dxfunc.h"

// Объявления переменных
LPDIRECT3D9 d3d = NULL;
LPDIRECT3DDEVICE9 d3ddev = NULL;
LPDIRECT3DSURFACE9 backbuffer = NULL;

HRESULT d3d9Init(IDirect3D9 **d3d, 
                IDirect3DDevice9 **d3ddev,
                HWND hWnd, 
				DWORD iWidth,
				DWORD iHeight,
				BOOL bFullScreen)
{
  // Инициализируем Direct3D
 if(( *d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
 {
	return E_FAIL;
 }

 // Заполняем основные (общие) параметры представления
  D3DPRESENT_PARAMETERS d3dpp;
  ZeroMemory( &d3dpp, sizeof( d3dpp ) );

  d3dpp.BackBufferWidth = iWidth;
  d3dpp.BackBufferHeight = iHeight;
  d3dpp.EnableAutoDepthStencil = TRUE;
  d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
  d3dpp.hDeviceWindow = hWnd;


  // Запрос на отображение в полноэкранном режиме
  int  iRes;
  if (!bFullScreen)
      iRes=MessageBox(hWnd, "Перейти в полноэкранный режим?", "Screen", MB_YESNO | MB_ICONQUESTION);
  else
	  iRes = IDYES;

  if(iRes == IDYES)
  {
      //////////////////////////////////////////////////////////
	  // Полноэкранный режим
      //////////////////////////////////////////////////////////
	  // Установка параметров полноэкранного режима
	  d3dpp.Windowed         = FALSE;
	  d3dpp.BackBufferCount = 1;
      d3dpp.BackBufferFormat = D3DFMT_R5G6B5;
	  d3dpp.SwapEffect       = D3DSWAPEFFECT_FLIP;
      d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
      d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;  // Direct3D сам выбирает частоту показа.
	  // Обычно она равна частоте кадров.
  } 
  else 
  {
  // Доп. параметры для оконного режима
  // Получить формат пикселя
  D3DDISPLAYMODE d3ddm;
  (*d3d)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

  // Установка параметров оконного режима
  d3dpp.Windowed         = TRUE;
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.SwapEffect       = D3DSWAPEFFECT_DISCARD;
  }

 if(FAILED((*d3d)->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, d3ddev)))
 {
	 return E_FAIL;
 }

 // Очищаем бэкбуфер в чёрный цвет
 (*d3ddev)->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

 // Создаём указатель на бэкбуфер
 (*d3ddev)->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);
}

LPDIRECT3DSURFACE9 LoadSurface(char *filename, D3DCOLOR transcolor)
{
	LPDIRECT3DSURFACE9 image = NULL;
	D3DXIMAGE_INFO info;
	HRESULT result;

	// Получаем из файла длину и высоту изображения
	result = D3DXGetImageInfoFromFile(filename, &info);
	if(result != D3D_OK)
		return NULL;

	// Создаём поверхность
	result = d3ddev->CreateOffscreenPlainSurface(
		info.Width,  // Ширина изображения
		info.Height,  // Высота изображения
		D3DFMT_X8R8G8B8,  // Формат цветности поверхности
		D3DPOOL_DEFAULT,  // Используемый пул памяти
		&image,  // Указатель на поверхность
		NULL);  // Зарезервирован (всегда NULL)

	if(result != D3D_OK)
		return NULL;

	// Загружаем изображение из файла в созданную поверхность
	result = D3DXLoadSurfaceFromFile(
		image,  // Поверхность-получатель
		NULL,  // Палитра-получатель
		NULL,  // Прямоугольник-получатель
		filename,  // Имя файла-источника
		NULL,  // Прямоугольник-источник
		D3DX_DEFAULT,  // Метод фильтрации изображения
		transcolor,  // Цвет прозрачности (0 если без неё)
		NULL);  // Инфо о файле изображения (обычно NULL)

	if(result != D3D_OK)
		return NULL;

	return image;
}

LPDIRECT3DTEXTURE9 LoadTexture(char *filename, D3DCOLOR transcolor)
{
	// Указатель на текстуру
	LPDIRECT3DTEXTURE9 texture = NULL;

	// Структура, куда сохраним инфу о файле растрового изображения (=битмапе)
	D3DXIMAGE_INFO info;

	// Стандартное значение, возвразаемое в ОС Windows
	HRESULT result;

	// Получаем ширину и высоту изображения из битмапа
	result = D3DXGetImageInfoFromFile(filename, &info);
	if(result != D3D_OK)
		return NULL;

	// Создаём текстуру путём загрузки битмапа
	D3DXCreateTextureFromFileEx(
		d3ddev,  // Объект устройства Direct3D
		filename,  // Имя файла битмапа
		info.Width,  // Ширина битмапа
		info.Height,  // Высота битмапа
		1,  // Кол-во мипмап-уровней (1 - значит без цепочки)
		D3DPOOL_DEFAULT,  // Тип поверхности (стандартная)
		D3DFMT_UNKNOWN,  // Формат цветности поверхности (по умолчанию)
		D3DPOOL_DEFAULT,  // Пул памяти для текстуры
		D3DX_DEFAULT,  // Фильтр изображения
		D3DX_DEFAULT,  // Мип-фильтр
		transcolor,  // Ключевой цвет для прозрачности
		&info,  // Инфо о битмапе (загружено выше из файла битмапа)
		NULL,  // Цветовая палитра
		&texture  // Указатель на конечный объект текстуры
		);

	// Проверяем, что битмап успешно загружен
	if(result != D3D_OK)
		return NULL;

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

На этом код инициализации окна и DirectX-штук завершается и начинается код самой программы (в будущем - игры).

Добавляем в Проект заголовочный файл Game.h

Game.h содержит объявления DirectX-функций, реализованных в Game.cpp (его создадим чуть позже).
ОК, приступаем.

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

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

  • В только что созданном и открытом файле Game.h набираем следующий код:
Game.h
// Файл: Game.h 
// Объявления функций, специфичных для игры, реализации которых
// размещены в Game.cpp

#ifndef _GAME_H_
#define _GAME_H_

#include "dxfunc.h" // Для инициализации Direct3D + подключения d3d9.h
#include <time.h> // Для инициализации таймера
#include <stdio.h>
#include <stdlib.h>

// Заголовок приложения (выводится на тулбаре окна)
#define APPTITLE "Animated Sprite Test"

// Настройки видеорежима
#define FULLSCREEN 1
#define iWidth 640
#define iHeight 480

// Макрос асинхронного считывания нажатий клавиатуры
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1:0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1:0)

// Прототипы игровых функций
bool GameInit(HWND);  // Инициализирует игру.
void GameRun(HWND);
void GameEnd(HWND);

// Структура для хранения информации о спрайте
typedef struct {
	int x,y;
    int width, height;
	int movex, movey;
	int curframe, lastframe;
	int animdelay, animcount;
} SPRITE;

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

Видим, что сюда "переехали" многие объявления из WinMain.cpp . Например внутриигровые функции GameInit, GameRun и GameEnd. Здесь же задаются начальные настройки видеорежима.

Добавляем в Проект файл исходного кода Game.cpp

В Game.cpp содержатся реализации функций, объявленных в Game.h.
ОК, приступаем.

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

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

  • В только что созданном и открытом файле Game.cpp набираем следующий код:
dxfunc.cpp
// Файл: Game.cpp 
// Реализация функций, специфичных для игры, объявленных в Game.h

#include "Game.h"

LPDIRECT3DTEXTURE9 test_image[7];
SPRITE TestSprite;
LPDIRECT3DSURFACE9 back;
LPD3DXSPRITE SpriteHandler;

HRESULT result;

// Переменные таймера
long start = GetTickCount();

// Инициализация игры
bool GameInit(HWND hWnd)
{
	char s[20];
	int n;

	// Устанавливаем генератор случайных чисел
	// на основе текущего времени
	srand(time(NULL));

	//  Создаём объект дескриптора спрайта
	result = D3DXCreateSprite(d3ddev, &SpriteHandler);
	if(result != D3D_OK)
		return 0;

	// Загружаем анимацию спрайта
	for(n=0; n<6; n++)
	{
		// Приводим имя файла к нужному виду
		sprintf(s,"test%d.jpg",n+1);

		// Загружаем текстуру с розовым цветом прозрачности
		test_image[n] = LoadTexture(s, D3DCOLOR_XRGB(255,0,255));
		if(test_image[n] == NULL)
			return 0;
	}

	// Загружаем фоновое изображение
	back = LoadSurface("background.bmp", NULL);

	// Инициализируем свойства спрайта
	TestSprite.x = 100;
	TestSprite.y = 150;
	TestSprite.width = 96;
	TestSprite.height = 96;
	TestSprite.curframe = 0;
	TestSprite.lastframe = 5;
	TestSprite.animdelay = 2;
	TestSprite.animcount = 0;
	TestSprite.movex = 8;
	TestSprite.movey = 0;

	// Возвращаем ОК
	return 1;
}

// Главный цикл игрового приложения
void GameRun(HWND hWnd)
{
	// Проверяем корректность созданного объекта устройства Direct3D
	if(d3ddev == NULL)
		return;

	// Готов ли следующий кадр?
	// Поддерживает заданную частоту кадров (framerate) в приложении
	if(GetTickCount() - start >= 30)
	{
		// Сбрасываем таймер в 0
		start = GetTickCount();

		// Перемещаем спрайт
		TestSprite.x += TestSprite.movex;
		TestSprite.y += TestSprite.movey;

		// При приближении спрайта к краям окна "переносим" его на противоположную сторону
		if(TestSprite.x > iWidth - TestSprite.width)
			TestSprite.x=0;
		if(TestSprite.x < 0)
			TestSprite.x = iWidth - TestSprite.width;

		// В случае достижения паузы в анимации определённой величины...
		if(++TestSprite.animcount > TestSprite.animdelay)
		{
			// Обнуляем счётчик кадров
			TestSprite.animcount = 0;

			// Анимируем спрайт
			if(++TestSprite.curframe > TestSprite.lastframe)
				TestSprite.curframe = 0;
		}
	}

	// Начинаем рендеринг
	if(d3ddev->BeginScene())
	{
		// Очищаем текущий фон (background)
		d3ddev->StretchRect(back, NULL, backbuffer, NULL, D3DTEXF_NONE);

		// Начинаем отрисовку спрайта через его дескриптор
		SpriteHandler->Begin(D3DXSPRITE_ALPHABLEND);

		// Создаём вектор для обновления положения спрайта на экране
		D3DXVECTOR3 position((float)TestSprite.x, (float)TestSprite.y, 0);

		// Рисуем спрайт
		SpriteHandler->Draw(
			test_image[TestSprite.curframe],
			NULL,
			NULL,
			&position,
			D3DCOLOR_XRGB(255, 255, 255)
			);

		// Заканчиваем отрисовку спрайта через его дескриптор
		SpriteHandler->End();

		// Завершаем рендеринг
		d3ddev->EndScene();
	}

	// Выводим содержимое бэкбуфера на экран
	d3ddev->Present(NULL, NULL, NULL, NULL);

	// При нажатии Esc завершаем работу приложения
	if(KEY_DOWN(VK_ESCAPE))
		PostMessage(hWnd, WM_DESTROY, 0, 0);
}

// Освобождаем память, стирая ранее выделененые ресурсы
void GameEnd(HWND hWnd)
{
	int n;

	// Удаляем поверхность
	for(n=0; n<6; n++)
	{
		if(test_image[n] != NULL)
		{
			test_image[n]->Release();
		}
	}

	if(back != NULL)
		back->Release();

	if(SpriteHandler != NULL)
		SpriteHandler->Release();
}
  • Сохрани Решение (Файл->Сохранить все).

Так-то всё. Осталось скомпилировать Проект, подготовить спрайт и фоновое изображение.

Готовим Проект D3D9ASprite01 к компиляции

Устанавливаем многобайтовую кодировку (multibyte encoding)

Закрыть
noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...

  • В Обозревателе решений щёлкаем по названию только что созданного Проекта D3D9ASprite01.
  • Во всплывающем меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём "Свойства конфигурации", в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
  • Жмём ОК.

Image

Отключаем инкрементную компоновку (incremental linking)

Иначе при копиляции возможны ошибки вроде этой:

Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден
  • В Обозревателе решений щёлкаем по названию только что созданного Проекта D3D9ASprite01.
  • Во всплывающем меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение "Нет (/INCREMENTAL:NO)".
  • Жмём ОК.

  • Сохрани Решение (File -> Save All).

Указываем пути к DirectX SDK 9

Если попытаться скомпилировать Проект D3D9ASprite01 в таком виде, то ничего не выйдет.
В заголовочном файле исходного кода dxfunc.h видим директиву включения (#include) заголовочного файлов d3d9.h и d3dx9.h:

Фрагмент dxfunc
...
#include <d3d9.h>
#include <d3dx9.h>
...

На данном этапе MSVC++2010 ничего не знает об их местоположении. В статье Настройка MS Visual C plus plus 2010 и DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Проделаем те же операции для нашего Проекта D3D9ASprite01.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект D3D9ASprite01.
  • Убедись, что DirectX SDK 9 установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.

В Обозревателе решений видим: "Решение "D3D9ASprite01"", а строкой ниже жирным шрифтом название Проекта (тоже D3D9ASprite01).

  • Жмём правой кнопкой мыши по названию Проекта D3D9ASprite01. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.

Image

  • В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++. В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.


Указываем каталог включений (include) DirectX SDK 9

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."

Image

  • В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным файлам DirectX SDK 9 (include). В нашем случае это C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Жмём "ОК".


Указываем каталог библиотек (lib) DirectX SDK 9

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к 32-разрядным версиям файлов библиотек DirectX SDK 9 (lib). В нашем случае это C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
  • Жмём "ОК".

Image

  • На Странице свойств тоже жмём "ОК".

  • Сохрани Решение (File -> Save All).

Готово.

Указываем пути к Windows SDK

Помимо указания путей к заголовкам (include) и библиотекам (lib) DirectX SDK, для любого DirectX-Проекта также необходимо казать пути к заголовкам (include) и библиотекам (lib) Windows SDK. Самое смешное, что в MS Visual C++ 2010 эти пути указываются по умолчанию для каждого создаваемого Проекта. В этом нетрудно убедиться, если ещё раз открыть Проект -> Свойства -> Свойства конфигурации - >Каталоги VC++ -> Каталоги включения -> Изменить. В окне "Каталоги включения" в нижней (недоступной для редактирования) части видим список "Унаследованные значения", где в третьей строке стоит значение:

$(WindowsSdkDir)include

В результате видим, что добавленные пути к DirectX SDK (в обоих окнах: include и lib) расположены вверху списка, а пути к Windows SDK - в недоступной области, на несколько строк ниже.
Но заголовочным файлам DirectX SDK 9 "жизненно важно", чтобы они включались после включений заголовков Windows SDK.

Закрыть
noteВажно!

Каталоги, пути к которым указаны в окнах "Каталоги включения" и "Каталоги библиотек" при компиляции считываются один за другим по списку сверху вниз. Поэтому для корректного указания путей надо разместить пути к Windows SDK выше, а к DirectX SDK - ниже по списку (чтобы они считывались последними).

В данной ситуации мы не можем поднять пути к Windows SDK, прописанные по умолчанию при создании Проекта, т.к. они расположены в специальной нередактируемой области (ограничение бесплатной версии MSVC++2010 Express).
Но можем схитрить и добавить ещё раз пути к тем же самым каталогам Windows SDK, подняв эти строки выше строк DirectX SDK.
Выше мы указывали пути к DirectX SDK 9. Для путей к Windows SDK это делается аналогично. Начнём.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект D3D9ASprite01.
  • Убедись, что Windows SDK (в нашем случае версия 7) установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.

В Обозревателе решений видим: "Решение "D3D9ASprite01"", а строкой ниже жирным шрифтом название Проекта (тоже D3D9ASprite01).

  • Жмём правой кнопкой мыши по названию Проекта D3D9Init01. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.

Image

  • В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++. В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.


Указываем каталог включений (include) Windows SDK

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."

Image

  • В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Меняй порядок считывания каталогов включений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".

Каталог включений DirectX SDK (9) должен всегда стоять в самом конце списка, как на этом скриншоте:
Image

  • Жмём "ОК".


Указываем каталог библиотек (lib) Windows SDK

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к папке с файлами библиотек (lib) Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".

Каталог библиотек DirectX SDK (9) должен всегда стоять в самом конце списка, как на этом скриншоте:
Image

  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".

  • Сохрани Решение (File -> Save All).

Прописываем библиотеки d3d9.lib и d3dx9.lib в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)

Т.к. мы создаём исполняемое приложение (исполняемый .exe-файл), а не библиотеку, то после компиляции полученный объектный модуль сразу линкуется путём вызова компоновщика (=linker). Так вот, этот самый компоновщик по ранее прописанным каталогам нужные библиотеки не ищет. Поэтому библиотеки d3d9.lib и d3dx9.lib необходимо явно указывать в списке дополнительных зависимостей линкера (=компоновщика). Причём в дальнейшем, по мере роста функционала движка, не только их.
d3d9.lib и d3dx9.lib расположены в папке с установленным DirectX SDK 9 (в нашем случае по пути C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86), путь к которому мы прописали выше.
ОК, начинаем.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект D3D9ASprite01.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Ввод (Configuration Properties -> Linker -> Input).
  • В правой части, напротив строки "Дополнительные зависимости" жмём кнопку с чёрным треугольником.
  • В всплывающем списке жмём "Изменить".
  • В появившемся окне "Дополнительные зависимости" в верхнем поле ввода прописываем имена требуемых библиотек:

d3d9.lib
d3dx9.lib

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

Отключаем использование компоновщиком библиотеки libci.dll

Да, даже на данном этапе при компиляции Проект D3D9ASprite01 выдаст ошибку. Библиотека libci.dll использовалась в VisualStudio когда-то очень давно. И в современных версях IDE её нет. Тем не менее компоновщик почти всегда вызывает её при компиляции, ругаясь на её отсутствие. Самый простой способ это исправить - запретить использовать libci.dll по умолчанию.
ОК, начнём.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект D3D9ASprite01.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Командная строка (Configuration Properties -> Linker -> Command Promt).

В правой части, внизу, видим поле ввода "Дополнительные параметры".

  • Пишем в него строку: /NODEFAULTLIB:libci
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все).

Компилируем Проект D3D9ASprite01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9ASprite01.
  • Во всплывающем контекстном меню выбери "Перестроить".

Если весь код был введён без ошибок, по завершении перекомпиляции в логе в нижней части IDE будет следующая строка.

========== Перестроение всех: успешно: 1, с ошибками: 0, пропущено: 0 ==========
  • Перейди в каталог Проекта (в нашем случае C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9ASprite01\Debug) и запусти скомпилированный исполняемый файл D3D9ASprite01.exe .

Появится окно размером 640х480 с белым фоном, которое, спустя секунду, закроется. А всё из-за этого фрагмента кода в Game.cpp:

Фрагмент Game.cpp
...
	// Загружаем анимацию спрайта
	for(n=0; n<6; n++)
	{
		sprintf(s,"test%d.bmp",n+1);
		test_image[n] = LoadSurface(s, D3DCOLOR_XRGB(255,0,255));
		if(test_image[n] == NULL)
			return 0;
	}
...

Если переменная kitty_imagen окажется NULL (а она окажется, т.к. загружаемые файлы изображения пока никто не готовил), то приложение досрочно завершит работу.
Если закомментировать последний условный переход if вот так:

Фрагмент Game.cpp
...
	//	if(test_image[n] == NULL)
	//		return 0;
	}
...

...то после перекомпиляции и повторного запуска приложения окно не закроется.

  • Раскомментируй данный переход if.

Программа ищет (в папке со своим исполняемым файлом) .bmp-файлы с именами test1.bmp, test2.bmp и т.д.

Рис. 1 Пример спрайтщита стоимостью 9 уе. Источник: https://graphicriver.net/item/game-character-sprite-07/9814759?utm_source=sharepi
Рис. 1 Пример спрайтщита стоимостью 9 уе. Источник: https://graphicriver.net/item/game-character-sprite-07/9814759?utm_source=sharepi
Рис. 2 Спрайтщит с пещерным человеком. Источник: https://opengameart.org/content/running-caveman-spritesheet
Рис. 2 Спрайтщит с пещерным человеком. Источник: https://opengameart.org/content/running-caveman-spritesheet

Готовим файлы изображений для анимации

Для покадровой анимации спрайта нужны изображения всех его положений, в том числе прорисовка динамики движения (фигурка при ходьбе может двигать руками и ногами). Вообще, для анимации подойдут любые файлы формата .bmp или .jpg одинакового размера. Этом могут быть несколько отдельных битмап-файлов, либо один большой битмап с тайловыми спрайтами (spritesheet(external link)). С точки зрения производительности загружать с жёсткого диска в память несколько изображений - не самое удачное решение. Совсем не редкость, когда в игре существуют десятки, а то и сотни спрайтов. Загрузка каждого из них в качестве индивидуального и самостоятельного изображения будет потреблять много памяти и вычислительной мощности. Для того, чтобы избежать этих неудобств, и используют spritesheet’ы. Если загуглить этот термин, то поисковик выдаст десятки готовых изображений (иногда их называют Sprite set или Lineart Drawing) с рядами фигурок персонажа в разных позах.
Изображения, размещаемые на спрайтщите могут быть созданы следующими способами:

    • Рендеринг 3DS Max или любом другом 3D-редакторе, оснащённым функцией рендеринга (самый распространённый способ).
    • В Фотошопе, и в частности с помощью его модуля Animate.
    • Сканирование сктечей, отрисованных вручную и их последующая векторизация (например в Corel Draw).

Захват тайла с битмапа-спрайтщита

Изображение-источник (см Рис.1) состоит их строк (rows) и колонок (columns) тайлов (от англ. "tile" - плитка). Для захвата нужного тайла необходимо вычислить позицию его верхнего левого угла относительно общего битмапа-спрайтщита. Затем прямоугольный участок нужного размера (размер спрайта задаётся заранее) копируется в прямоугольник-источник (source rectangle).
Сперва необходимо определить позицию тайла по оси X. Это выполняется с помощью операции вычисления остатка деления чисел (modulus, %). Например, если текущим кадром анимации является 20-й, и в спрайтщите 5 колонок, то операция modulus выдаст начальную позицию нужного тайла по горизонтали (путём перемножения остатка деления на ширину (width) спрайта).
Вычисление верхней границы нужного тайла вычисляется простым делением текущего кадра анимации на число колонок в спрайтщите. Далее результат деления умножается на высоту (height) спрайта. Т.е. если в спрайтщите 5 колонок, то 20-й тайл будет в строке 4 столбца 5. Вот пример псевдокода:
left = (текущий кадр % число колонок) * ширина спрайта
top = (текущий фрейм / число колонок) * высота спрайта

Создание битмапа с тайловыми спрайтами (Drawing a Tiled Sprite; Spritesheet Animation)

Работа над статьёй приостановлена

Причина: код м-ра Harbour успешно компилируется, но при запуске приложения его окно через секунду закрывается. З аосновы был взят Проект ASprite01, созданный выше. Код Game.cpp заменён на следующий:

Game.cpp (Проект ASprite01)
// Файл: Game.cpp 
// Реализация функций, специфичных для игры, объявленных в Game.h

#include "Game.h"

LPDIRECT3DTEXTURE9 spritesheet_caveman;
SPRITE caveman;
LPDIRECT3DSURFACE9 back;
LPD3DXSPRITE SpriteHandler;

HRESULT result;

// Переменные таймера
long start = GetTickCount();

// Инициализация игры
bool GameInit(HWND hWnd)
{
	// Устанавливаем генератор случайных чисел
	// на основе текущего времени
	srand(time(NULL));

	//  Создаём объект дескриптора спрайта
	result = D3DXCreateSprite(d3ddev, &SpriteHandler);
	if(result != D3D_OK)
		return 0;

	// Загружаем спрайтщит-текстуру, с розовым цветом в качестве цвета прозрачности
	spritesheet_caveman = LoadTexture("spritesheet_caveman.png", D3DCOLOR_XRGB(255, 0, 255));
//	if(spritesheet_caveman == NULL)
//		return 0;

	// Загружаем фоновое изображение
	back = LoadSurface("bg_test.jpg", NULL);

	// Инициализируем свойства спрайта
	caveman.x = 100;
	caveman.y = 180;
	caveman.width = 50;
	caveman.height = 64;
	caveman.curframe = 1;
	caveman.lastframe = 11;
	caveman.animdelay = 3;
	caveman.animcount = 0;
	caveman.movex = 5;
	caveman.movey = 0;

	// Возвращаем ОК
	return 1;
}

// Главный цикл игрового приложения
void GameRun(HWND hWnd)
{
	// Проверяем корректность созданного объекта устройства Direct3D
	if(d3ddev == NULL)
		return;

	// Готов ли следующий кадр?
	// Поддерживает заданную частоту кадров (framerate) в приложении
	if(GetTickCount() - start >= 30)
	{
		// Сбрасываем таймер в 0
		start = GetTickCount();

		// Перемещаем спрайт
		caveman.x += caveman.movex;
		caveman.y += caveman.movey;

		// При приближении спрайта к краям окна "переносим" его на противоположную сторону
		if(caveman.x > iWidth - caveman.width)
			caveman.x=0;
		if(caveman.x < 0)
			caveman.x = iWidth - caveman.width;

		// В случае достижения паузы в анимации определённой величины...
		if(++caveman.animcount > caveman.animdelay)
		{
			// Обнуляем счётчик кадров
			caveman.animcount = 0;

			// Анимируем спрайт
			if(++caveman.curframe > caveman.lastframe)
				caveman.curframe = 0;
		}
	}

	// Начинаем рендеринг
	if(d3ddev->BeginScene())
	{
		// Очищаем текущий фон (background)
		d3ddev->StretchRect(back, NULL, backbuffer, NULL, D3DTEXF_NONE);

		// Начинаем отрисовку спрайта через его дескриптор
		SpriteHandler->Begin(D3DXSPRITE_ALPHABLEND);

		// Создаём вектор для обновления положения спрайта на экране
		D3DXVECTOR3 position((float)caveman.x, (float)caveman.y, 0);

		// Настраиваем прямоугольник (rect) для выборки тайлов из спрайтщита
		RECT srcRect;
		int columns = 8;
		srcRect.left = (caveman.curframe % columns) * caveman.width;
		srcRect.top = (caveman.curframe / columns) * caveman.height;
		srcRect.right = srcRect.left + caveman.width;
		srcRect.bottom = srcRect.top + caveman.height;

		// Рисуем спрайт
		SpriteHandler->Draw(
			spritesheet_caveman,
			&srcRect,
			NULL,
			&position,
			D3DCOLOR_XRGB(255, 255, 255)
			);

		// Заканчиваем отрисовку спрайта через его дескриптор
		SpriteHandler->End();

		// Завершаем рендеринг
		d3ddev->EndScene();
	}

	// Выводим содержимое бэкбуфера на экран
	d3ddev->Present(NULL, NULL, NULL, NULL);

	// При нажатии Esc завершаем работу приложения
	if(KEY_DOWN(VK_ESCAPE))
		PostMessage(hWnd, WM_DESTROY, 0, 0);
}

// Освобождаем память, стирая ранее выделененые ресурсы
void GameEnd(HWND hWnd)
{
	if(spritesheet_caveman != NULL)
		spritesheet_caveman->Release();

	if(back != NULL)
		back->Release();

	if(SpriteHandler != NULL)
		SpriteHandler->Release();
}

В ходе выяснения причин было выяснено, что выполнение "спотыкается" при выполнении этого фрагмента:

Фрагмент Game.cpp (Проект ASprite01)
...
	//  Создаём объект дескриптора спрайта
	result = D3DXCreateSprite(d3ddev, &SpriteHandler);
	if(result != D3D_OK)
		return 0;
...

При закомментировании условного перехода if показывается окно с белым фоном. Причины неизвестны.

Источники:


1. Harbour J.S. Beginning Game Programming. - Thomson Course Technology. 2005

Contributors to this page: slymentat .
Последнее изменение страницы Понедельник 12 / Апрель, 2021 12:23:53 MSK автор slymentat.

Помочь проекту

yoomoney.ru (бывший Яндекс-деньги): 410011791055108