Search…

Sprite Animation trong DirectX 9

Huỳnh Minh TânHuỳnh Minh Tân
27/09/20209 min read
Hướng dẫn tạo tập tin lưu tọa độ các tile từ một sprite sheet có sẵn và hiện thực Sprite animation một cách chi tiết trên nền DirectX 9.

Cài đặt Sprite animation sử dụng đối tượng D3DXSprite trong DirectX9

Hiện thực Sprite animation có nhiều cách, có thể cắt riêng lẻ từng tile rồi hiển thị chúng 1 cách liên tiếp, hoặc thao tác trên 1 sprite sheet lý tưởng (các tile có cùng chiều cao, chiều rộng và cách nhau với 1 tỉ lệ nhất định) muốn có source rect để hiển thị, sử dụng công thức %/ khi đã biết số dòng và số cột của tile.

Bài viết này cài đặt Sprite animation theo cách tổng quát kết hợp giữa file sprite sheet và file XML.

Chuẩn bị project với những thiết lặp ban đầu

Khởi tạo cửa sổ Windows, khởi tạo DirectX, tạo game loop, ... chuẩn bị để load 1 Sprite animation.

Visual studio code structure
Cấu trúc project load Sprite animation

Mở Visual Studio -> tạo mới 1 project và cài đặt môi trường hỗ trợ DirectX9 -> sau đó copy source code bên dưới.

File CGame.h

#pragma once

#define APP_CLASS "SpriteWindow"
#define MAIN_WINDOW_TITLE "Sample_Sprite_Animation"

#include <Windows.h>
#include <d3dx9.h>

// Screen resolution
#define GAME_SCREEN_RESOLUTION_800_600_24   1
#define GAME_SCREEN_RESOLUTION_1024_640_24  3

class CGame
{
private:
	LPDIRECT3D9		_d3d;		// The pointer to our Direct3D interface
	LPDIRECT3DDEVICE9	_d3ddv;		// The pointer to the device class
	LPDIRECT3DSURFACE9	_BackBuffer;

	DWORD		_DeltaTime;			// Time between the last frame and current frame
	int		_Mode;				// Screen mode 
	int		_IsFullScreen;		// Is running in fullscreen mode?
	int		_FrameRate;			// Desired frame rate of game

	int		_ScreenWidth;
	int		_ScreenHeight;

	D3DFORMAT	_BackBufferFormat;

	HINSTANCE	_hInstance;			// Handle of the game instance
	HWND		_hWnd;				// Handle of the Game Window

	LPWSTR		_Name;				// Name of game will be used as Window Class Name

	void		_SetScreenDimension(int Mode);

	static LRESULT CALLBACK _WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

	void _InitWindow();
	void _InitDirectX();

	void render(float DeltaTime);					// Render something
public:
	CGame(HINSTANCE hInstance, LPWSTR Name, int Mode, int IsFullscreen, int FrameRate);
	~CGame();

	// Initialize the game with set parameters
	void Init();

	// Run game
	void Run();

	// Terminat game
	void Terminate();
};

File CGame.cpp

#include <Windows.h>
#include "CGame.h"

CGame::CGame(HINSTANCE hInstance, LPWSTR Name, int Mode, int IsFullScreen, int FrameRate)
{
	_d3d = NULL;
	_d3ddv = NULL;
	_BackBuffer = NULL;
	_Mode = Mode;
	_SetScreenDimension(Mode);
	_Name = Name;
	_IsFullScreen = IsFullScreen;
	_FrameRate = FrameRate;
	_hInstance = hInstance;
}

void CGame::_SetScreenDimension(int Mode)
{
	switch (Mode)
	{
	case GAME_SCREEN_RESOLUTION_800_600_24:
		_ScreenWidth = 800;
		_ScreenHeight = 600;
		_BackBufferFormat = D3DFMT_X8R8G8B8;
	break;

	case GAME_SCREEN_RESOLUTION_1024_640_24:
		_ScreenWidth = 1024;
		_ScreenHeight = 640;
		_BackBufferFormat = D3DFMT_X8R8G8B8;
	break;

	default:
	break;
	}
}

void CGame::_InitWindow()
{
	WNDCLASSEX wc;
	wc.cbSize = sizeof(WNDCLASSEX);

	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.hInstance = _hInstance;

	wc.lpfnWndProc = (WNDPROC)CGame::_WinProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hIcon = NULL;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = _Name;
	wc.hIconSm = NULL;

	RegisterClassEx(&wc);

	DWORD style;
	if (_IsFullScreen)
		style = WS_EX_TOPMOST | WS_VISIBLE | WS_POPUP;
	else
		style = WS_OVERLAPPEDWINDOW;

	_hWnd =
		CreateWindow(
			_Name,
			_Name,
			style,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			_ScreenWidth,
			_ScreenHeight,
			NULL,
			NULL,
			_hInstance,
			NULL);

	if (!_hWnd)
	{
		MessageBox(NULL, L"Can't create Windows", L"Error", MB_OK);
	}

	ShowWindow(_hWnd, SW_SHOWNORMAL);
	UpdateWindow(_hWnd);
}

void CGame::_InitDirectX()
{
	_d3d = Direct3DCreate9(D3D_SDK_VERSION);
	D3DPRESENT_PARAMETERS d3dpp;

	ZeroMemory(&d3dpp, sizeof(d3dpp));

	d3dpp.Windowed = _IsFullScreen ? FALSE : TRUE;

	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

	d3dpp.BackBufferFormat = _BackBufferFormat;
	d3dpp.BackBufferCount = 1;
	d3dpp.BackBufferHeight = _ScreenHeight;
	d3dpp.BackBufferWidth = _ScreenWidth;

	_d3d->CreateDevice(
		D3DADAPTER_DEFAULT,
		D3DDEVTYPE_HAL,
		_hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING,
		&d3dpp,
		&_d3ddv);

	if (_d3ddv == NULL)
	{
		MessageBox(NULL, L"Can't create Direct Device", L"Error", MB_OK);
		return;
	}

	_d3ddv->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &_BackBuffer);
}

void CGame::render(float DeltaTime)
{
///////////////////////////////////////////////////
	// render animation sprite here
///////////////////////////////////////////////////
}

void CGame::Init()
{
	_InitWindow();
	_InitDirectX();
    
///////////////////////////////////////////////////
	// init animation sprite here
///////////////////////////////////////////////////
}

// Main game message loop
void CGame::Run()
{
	MSG msg;
	int done = 0;
	DWORD frame_start = GetTickCount();;

	DWORD tick_per_frame = 1000 / _FrameRate;

	while (!done)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT) done = 1;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		DWORD now = GetTickCount();
		_DeltaTime = now - frame_start;
		if (_DeltaTime >= tick_per_frame)
		{
			frame_start = now;

			if (_d3ddv->BeginScene())
			{
				// clear the window to a deep blue
				_d3ddv->ColorFill(_BackBuffer, NULL, D3DCOLOR_XRGB(0, 40, 100));

				// render
				this->render(_DeltaTime);

				_d3ddv->EndScene();
			}

			_d3ddv->Present(NULL, NULL, NULL, NULL);
		}
	};
}

void CGame::Terminate()
{
	// release all

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

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

///////////////////////////////////////////////////
	// release animation sprite here
///////////////////////////////////////////////////
}

CGame::~CGame()
{
}

LRESULT CALLBACK CGame::_WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}

	return 0;
}

File MainPrg.cpp

#include "CGame.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	CGame game(hInstance, L"Sample Sprite Animation", GAME_SCREEN_RESOLUTION_1024_640_24, 0, 60);

	game.Init();

	game.Run();

	game.Terminate();

	return 0;
}

Xem code mẫu trên Bugs.vn, Entry_SpriteAnimationSheet :: https://www.bugs.vn/io/u1Ix5g1

Cài đặt Sprite animation với file XML

Tạo file CAnimationSprite.h, khai báo class CAnimaionSprite và tạo mới lớp CTile để lưu thông tin x, y, w, h của các tile trong sprite sheet.

#pragma once
#include <Windows.h>
#include <d3dx9.h>
#include <list>

// include lib extend
#include "tinyxml.h"
using namespace std;

class CTile;

class CAnimationSprite
{
public:
	CAnimationSprite(LPDIRECT3DDEVICE9);
	~CAnimationSprite();

	bool Init();

	bool Load(LPWSTR ImagePath, const char* XMLPath, D3DCOLOR Transcolor);

	// DeltaTime; X; Y; Scale; AnimationRate; FlipX
	void Render(float DeltaTime, float X, float Y, float Scale, float AnimationRate, float FlipX = 1);

	void Release();

	// read file XML store list tile
	bool ReadXML(const char* XMLPath);
private:
	list<CTile>		_ListTile;			  // lưu tất cả các tile trong sprite sheet
	int				_IndexTile;			  // chỉ số hiển thị tile theo vòng lặp
	float			_AnimationRate_Index; // chỉ số thời gian deplay cho việc chuyển tile

	// texture store sprite sheet
	LPDIRECT3DTEXTURE9		_Texture;
	// handler to sprite
	LPD3DXSPRITE			_SpriteHandler;
	// store device
	LPDIRECT3DDEVICE9		_d3ddv;
	// sotre infomation sprite
	D3DXIMAGE_INFO			_Info;
};

class CTile
{
public:
	CTile();
	~CTile();

	void SetTile(const char* Name, int X, int Y, int Width, int Height);
	CTile GetTile();

	RECT GETLocaTionTile();
private:
	RECT		_RectTile;
	const char*	_NameTile;
};

Tạo mới file CAnimationSprite.cpp và định nghĩa lớp CTile.

CTile::CTile()
{
	_RectTile.top = 0;
	_RectTile.bottom = 0;
	_RectTile.left = 0;
	_RectTile.right = 0;

	_NameTile = nullptr;
}

CTile::~CTile()
{
}

void CTile::SetTile(const char* Name, int X, int Y, int Width, int Height)
{
	_NameTile = Name;

	_RectTile.left = X;
	_RectTile.top = Y;
	_RectTile.right = Width;
	_RectTile.bottom = Height;
}

CTile CTile::GetTile()
{
	CTile temp;

	temp._NameTile = _NameTile;
	temp._RectTile = _RectTile;

	return temp;
}

RECT CTile::GETLocaTionTile()
{
	RECT rectTemp;
	rectTemp.top = _RectTile.top;
	rectTemp.left = _RectTile.left;
	rectTemp.bottom = _RectTile.bottom;
	rectTemp.right = _RectTile.right;

	return rectTemp;
}

Định nghĩa các phương thức constructor, destructor, InitRelease.

CAnimationSprite::CAnimationSprite(LPDIRECT3DDEVICE9 d3ddv)
{
	_Texture = nullptr;
	_SpriteHandler = nullptr;
	_d3ddv = d3ddv;
	_IndexTile = 0;
	_AnimationRate_Index = 0;
}

CAnimationSprite::~CAnimationSprite()
{
}

bool CAnimationSprite::Init()
{
	// sprite handler associate main Direct3D and Device that how to draw sprite at backbuffer
	HRESULT res = D3DXCreateSprite(_d3ddv, &_SpriteHandler);

	if (res != D3D_OK)
	{
		MessageBox(NULL, L"Can't associate main Direct3D and Device", L"Error", MB_OK);
		return false;
	}

	return true;
}

void CAnimationSprite::Release()
{
	if (_Texture != nullptr)
		_Texture->Release();

	if (_d3ddv != nullptr)
		_d3ddv->Release();

	_ListTile.clear();
}

Để đọc file XML, sử dụng thư viện TinyXML, định nghĩa hàm CAnimationSprite::ReadXML(), tiến hành lưu thông tin tất cả các tile vào _ListTile.

bool CAnimationSprite::ReadXML(const char* XMLPath)
{
	TiXmlDocument doc(XMLPath);

	if (!doc.LoadFile())
	{
		MessageBox(NULL, L"Failed to read file XML\nPlease check path file XML", L"Error", MB_OK);
		return false;
	}

	// get info root
	TiXmlElement* root = doc.RootElement();
	TiXmlElement* tile = nullptr;

	// loop to get element name, x, y, width, height
	for (tile = root->FirstChildElement(); tile != NULL; tile = tile->NextSiblingElement())
	{
		int x, y, w, h;
		const char* nameTileTemp;
		CTile TileTemp;

		// get value from file xml
		nameTileTemp = tile->Attribute("n");
		tile->QueryIntAttribute("x", &x);
		tile->QueryIntAttribute("y", &y);
		tile->QueryIntAttribute("w", &w);
		tile->QueryIntAttribute("h", &h);

		TileTemp.SetTile(nameTileTemp, x, y, w, h);

		// add into ListTile
		_ListTile.push_back(TileTemp);
	};

	return true;
}

Định nghĩa hàm CAnimationSprite::Load(), công việc tương tự như load 1 sprite và điều quan trọng là khởi tạo danh sách các tile bằng việc gọi hàm ReadXML().

bool CAnimationSprite::Load(LPWSTR ImagePath, const char* XMLPath, D3DCOLOR Transcolor)
{
	// get infomation (widht, height) sprite sheet
	HRESULT result = D3DXGetImageInfoFromFile(ImagePath, &_Info);

	if (result != D3D_OK)
	{
		MessageBox(NULL, L"Failed to get information from image file\nPlease check path image.", L"Error", MB_OK);
		return false;
	}

	// create texture from sprite sheet
	result = D3DXCreateTextureFromFileEx(
		_d3ddv,
		ImagePath,
		_Info.Width,
		_Info.Height,
		1,
		D3DUSAGE_DYNAMIC,
		D3DFMT_UNKNOWN,
		D3DPOOL_DEFAULT,
		D3DX_DEFAULT,
		D3DX_DEFAULT,
		Transcolor,
		&_Info,
		NULL,
		&_Texture
	);

	if (result != D3D_OK)
	{
		MessageBox(NULL, L"[ERROR] Failed to create texture from file", L"Error", MB_OK);
		return false;
	}

	// read file XML
	if (!ReadXML(XMLPath))
	{
		PostQuitMessage(WM_QUIT);
	}

	return true;
}

Cuối cùng, định nghĩa phương thức CAnimationSprite::Render(), phương thức này có chức năng hiển thị lần lượt tất cả các tile có trong _ListTile và được lặp lại liên tục để tạo ra hiệu ứng animation.

Trong thực tế khi lập trình tùy theo mục đích sử dụng có thể tùy biến lại cấu trúc hay chức năng của nó. Ví dụ, hiển thị lần lượt các tile có chỉ số từ IndexTileStart đến tile có chỉ số IndexTileEnd trong _ListTile và được lặp lại liên tục, nó sẽ rất hữu dụng khi hiển thị 1 animation có nhiều animation trong 1 sprite sheet.

  • DeltaTime: thời gian chuyển đổi giữa 2 frame.
  • X, Y: vị trí X, Y muốn hiển thị lên màn hình.
  • ScalesSize: tỉ lệ phóng đại.
  • AnimationRate: thời gian deplay chuyển đổi giữa 2 tile.
  • FlipX: lật theo trục Ox, -1 là lật nếu không để trống.
void CAnimationSprite::Render(float DeltaTime, float X, float Y, float ScaleSize, float AnimationRate, float FlipX)
{
	D3DXMATRIX Combined;

	D3DXMATRIX Scale;
	D3DXMATRIX Translate;

	// Initialize the Combined matrix.
	D3DXMatrixIdentity(&Combined);

	// set location
	D3DXVECTOR3 position((float)X, (float)Y, 0);
	// Scale the sprite.
	D3DXMatrixScaling(&Scale, FlipX * ScaleSize, ScaleSize, ScaleSize);
	Combined *= Scale;
	// Translate the sprite
	D3DXMatrixTranslation(&Translate, X, Y, 0.0f);
	Combined *= Translate;
	// Apply the transform.
	_SpriteHandler->SetTransform(&Combined);

	auto l_front = _ListTile.begin();
	advance(l_front, _IndexTile);

	// set position tile
	RECT srect;
	srect.left = l_front->GETLocaTionTile().left;
	srect.top = l_front->GETLocaTionTile().top;
	srect.bottom = srect.top + l_front->GETLocaTionTile().bottom;
	srect.right = srect.left + l_front->GETLocaTionTile().right;

	// get anchor point of sprite
	D3DXVECTOR3 center = D3DXVECTOR3((srect.right - srect.left) / 2, srect.bottom, 0);

	_SpriteHandler->Begin(D3DXSPRITE_ALPHABLEND);

	_SpriteHandler->Draw(
		_Texture,
		&srect,
		&center,
		NULL,    
		D3DCOLOR_XRGB(255, 255, 255)
	);

	_SpriteHandler->End();

	if (_AnimationRate_Index > AnimationRate)
	{
		_AnimationRate_Index = 0;
		_IndexTile = ((_IndexTile + 1) % _ListTile.size());
	}

	_AnimationRate_Index += DeltaTime;
} 

Đến đây đã hoàn thành việc cài đặt lớp CAnimationSprite, thực hiện việc khai báo, khởi tạo 1 thể hiện của CAnimationSprite và gọi hàm render() trong lớp CGame để vẽ.

// Include lớp CAnimationSprite trong CGame.h
#include "CAnimationSprite.h"
// Khai báo hằng resource trong CGame.h
#define SPRITE_TITLE_ALADDIN L"resource/spriteSheet_aladdin_walk.bmp"
#define SPRITE_TITLE_ALADDIN_XML "resource/spriteSheet_aladdin_walk.xml"

// Khai báo 1 Sprite animation trong CGame.h
CAnimationSprite*	_AspAladdin;	

// Khởi tạo giá trị mặc định trong CGame::CGame()
_AspAladdin = NULL;

// Khởi tạo bên trong CGame::Init()
_AspAladdin = new CAnimationSprite(_d3ddv);
_AspAladdin->Init();
_AspAladdin->Load(SPRITE_TITLE_ALADDIN, SPRITE_TITLE_ALADDIN_XML, D3DCOLOR_XRGB(255, 0, 255));

// Gọi hàm trong CGame::render()
_AspAladdin->Render(DeltaTime, 350, 420, 2.5, 50, -1);
//// Vẽ lên màn hình tọa độ (350, 420)
//// Tỉ lệ scale: 2.5
//// AnimationRate: 50
//// Flip tile theo trục Ox

// Giải phóng đối tượng trong CGame::Terminate()
_AspAladdin->Release();

// Thu hồi vùng nhớ trong CGame::~CGame()
delete _AspAladdin;

Download demo

STDIOSpriteAnimationSheet_VS2019.zip

Bài chung series

IO Stream

IO Stream Co., Ltd

developer@iostream.co
383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024