STDIO
Tìm kiếm gần đây
    • Nội dung
    • QR Code
    • 0
    • 0
    • Sao chép

    Sprite Animation trong DirectX 9

    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.
    02/11/2017
    27/09/2020
    9 phút đọc
    Sprite Animation trong 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

    0 Bình luận
    Lập Trình Game

    Lập Trình Game

    Kiến thức, kỹ thuật, kinh nghiệm lập trình game.

    Đề xuất

    Giới Thiệu về Sprite - Sprite Sheet - Sprite Animation
    Giới thiệu về Sprite - Sprite Sheet - Sprite Animation, hướng dẫn 1 số ...
    Load Sprite trong DirectX 9
    Hiện thực load Sprite từ cơ bản đến các thao tác nâng cao như flip, ...

    Khám phá

    Cài Đặt DirectX 9
    Hướng dẫn cài đặt DirectX 9 trên môi trường Windows.
    Khởi Tạo Cửa Sổ với DirectX 9
    Hướng dẫn khởi tạo cửa sổ làm việc với DirectX 9.
    Animation Trong Cocos2d-x 3.x.x
    Bài viết nằm trong loạt bài viết chương trình Tự Học Cocos2d-x 3.x.x. ...
    Sprite & 2D Animation
    Tìm hiểu về sprite, sprite sheet và 2D Animation trong game.
    Audio Sprite Và Audio Sprite Với HTML5
    Audio Sprite (hay có người gọi là Sound Sprite) Sprite Sheet hỗ trợ phát ...
    Action Cho Sprite Trong Cocos2d-x 3.x.x
    Tìm hiểu về action của Sprite trong Cocos2d-x 3.x.x.
    Các Khái Niệm trong Game
    Các khái niệm hữu ích từ cơ bản đến nâng cao trong lập trình game: ...
    Tìm Hiểu về Game Loop - Vòng Lặp Game
    Game loop là 1 vòng lắp được lặp đi lặp lại liên tục, giữ cho game vận ...
    Khi bạn nhấn vào liên kết sản phẩm do STDIO đề xuất và mua hàng, STDIO có thể nhận được hoa hồng. Điều này hỗ trợ STDIO tạo thêm nhiều nội dung hữu ích. Tìm hiểu thêm.
    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020