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 %
và /
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](https://resources.stdio.vn/content/article/5ef63fef815c8d76c19db26a/resources/res-1601139899-1601139899262.png)
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, Init
và Release
.
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, ¢er, 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