Tạo chuyển động trong game cho các nhân vật sẽ làm cho game của bạn trở nên sinh động hơn. Ý tưởng đơn giản vẫn là vẽ tuần tự từng ảnh một theo thời gian. Tuy nhiên việc lưu trữ các ảnh rời rạc sẽ gây khó khăn trong việc quản lý các Animation, thông qua đó chúng ta sẽ sử dụng một công cụ khá hiệu quả đó là Sprite.
Sprite Sprite là một đối tượng đồ họa 2D được vẽ lên màn hình. Các sprite kết hợp với nhau sẽ tạo nên khung cảnh cho game. Có thể di chuyển nó thông qua cách đặt tọa độ trong hàm vẽ lên màn hình. Có 2 cách vẽ sprite với Direct3D. Cả 2 phương pháp đều yêu cầu ta cung cấp các thông tin: vị trí, kích thước, tốc độ và các thuộc tính riêng khác. Cách 1: Load 1 ảnh sprite vào trong surface và sau đó vẽ lên backbuffer sử dụng StretchRect như ở bài trước. Đây là cách đơn giản nhưng sẽ rất khó sử dụng cho các project phức tạp.
Cách 2: Sử dụng D3DXSprite để giữ những sprite trong Direct3D. Đây là cách thường được sử dụng nhất. D3DXSprite sử dụng texture thay vì surface để chứa bức ảnh làm sprite. Chúng ta sẽ đi qua cả 2 cách trong 2 chương trình mẫu tiếp theo.
//sprite structure
typedef struct
{
//các biến dùng để update tọa độ x, y cảu sprite suốt quá trình update khung hình (frame)
int x , y ;
int width , height ;
int movex , movey ;
//biến dùng để giữ giá trị frame hiện tại của chuyển động, curframe được update liên tục trong vòng lặp game và khi nó đạt giá trị lastframe nó sẽ gán lặp lại giá trị 0, cứ thế lặp lại liên tục.
int curframe , lastframe ;
//giới hạn thời gian hiển thị của mỗi frame
int animdelay , animcount ;
} SPRITE ;
Chương trình Anim_Sprite Chương trình Anim_Sprite sử dụng cách vẽ sprite thứ nhất, vẽ lên màn hình con mèo chuyển động. Con mèo chuyển động có 6 khung hình và có dạng giống như đang chuyển động đi qua màn hình. 6 khung hình (frame) “cat.bmp” với kích thước 96x96 và có một nền màu đen với giá trị RGB(0,0,0) sẽ lần lượt được vẽ lên surface để tạo chuyển động.
Kết quả chương trình:
Chúng ta sẽ tạo một project tên Anim_Sprite và thêm file source “winmain.cpp” như đã làm ở các bài trước, thêm các thư viện “d3d9.lib” và “d3d9x.lib”.
Code “winmain.cpp”
//winmain.cpp - windows framework source code file
#include <d3dx9.h>
#include <d3d9.h>
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib, "d3d9.lib")
#include<time.h>
#include<stdio.h>
#include"dxgraphics.h"
#include"game.h"
//window event callback function
LRESULT WINAPI WinProc ( HWND hWnd , UINT msg , WPARAM wParam , LPARAM lParam )
{
switch ( msg )
{
case WM_DESTROY :
//release the direct3d device
if ( d3ddev != NULL )
d3ddev -> Release ();
//release the direct3d object
if ( d3d != NULL )
d3d -> Release ();
//call the "front-end" shutdown function
Game_End ( hWnd );
//tell Windows to kill this program
PostQuitMessage ( 0 );
return 0 ;
}
return DefWindowProc ( hWnd , msg , wParam , lParam );
}
//Các hàm hỗ trợ để khởi động Window
ATOM MyRegisterClass ( HINSTANCE hInstance )
{
//Create the window class structure
WNDCLASSEX wc ;
wc . cbSize = sizeof ( WNDCLASSEX );
//điền tham số hàm vào struct
wc . style = CS_HREDRAW | CS_VREDRAW ;
wc . lpfnWndProc = ( WNDPROC ) WinProc ;
wc . cbClsExtra = 0 ;
wc . cbWndExtra = 0 ;
wc . hInstance = hInstance ;
wc . hIcon = NULL ;
wc . hCursor = LoadCursor ( NULL , IDC_ARROW );
wc . hbrBackground = ( HBRUSH ) GetStockObject ( BLACK_BRUSH );
wc . lpszMenuName = NULL ;
wc . lpszClassName = APPTITLE ;
wc . hIconSm = NULL ;
//đăng ký lớp cửa sổ
return RegisterClassEx ( & wc );
}
//Đầu vào ứng dụng Window
int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpCmdLine , int nCmdShow )
{
MSG msg ;
//Đăng ký lớp cửa sổ
MyRegisterClass ( hInstance );
//khởi động ứng dụng
HWND hWnd ;
//set up the screen in windowed or fullscreen mode?
DWORD style ;
if ( FULLSCREEN )
style = WS_EX_TOPMOST | WS_VISIBLE | WS_POPUP ;
else
style = WS_OVERLAPPED ;
//Tạo một cửa sổ
hWnd = CreateWindow ( APPTITLE ,
APPTITLE ,
style , //WINDOW STYLE
CW_USEDEFAULT , //X POSITION OF WINDOW
CW_USEDEFAULT , //Y POSITION OF WINDOW
SCREEN_WIDTH ,
SCREEN_HEIGHT ,
NULL , //PARENT WINDOW
NULL , //MENU
hInstance , //application instance
NULL );
//Kiểm tra lỗi nếu không mở được cửa sổ
if ( ! hWnd )
return false ;
//hiển thị cửa sổ
ShowWindow ( hWnd , nCmdShow );
UpdateWindow ( hWnd );
//Khởi tạo game
if ( ! Init_Direct3D ( hWnd , SCREEN_WIDTH , SCREEN_HEIGHT , FULLSCREEN ))
return 0 ;
//initialize the game
if ( ! Game_Init ( hWnd ))
{
MessageBox ( hWnd , "Error initializing the game" , "Error" , MB_OK );
return 0 ;
}
//vòng lặp thông điệp chính
int done = 0 ;
while ( ! done )
{
if ( PeekMessage ( & msg , NULL , 0 , 0 , PM_REMOVE ))
{
//kiểm tra điều kiện thoát
if ( msg . message == WM_QUIT )
done = 1 ;
//giải mã thông điệp và chuyển lại cho WinProc
TranslateMessage ( & msg );
DispatchMessage ( & msg );
}
else
//xử lý game
Game_Run ( hWnd );
}
return msg . wParam ;
}
Bây giờ thêm vào project một file header “dxgraphics.h” và file cpp “dxgraphics.cpp”. Code lần lượt cho 2 hàm trên:
//dxgraphics.h - direct3d framework header file
#ifndef _DXGRAPHICS_H
#define _DXGRAPHICS_H
//function prototypes
int Init_Direct3D ( HWND , int , int , int );
LPDIRECT3DSURFACE9 LoadSurface ( char * , D3DCOLOR );
//variable declarations
extern LPDIRECT3D9 d3d ;
extern LPDIRECT3DDEVICE9 d3ddev ;
extern LPDIRECT3DSURFACE9 backbuffer ;
#endif // !_DXGRAPHICS_H
//dxgraphics.cpp - direct3d framework source code file
#include <d3dx9.h>
#include <d3d9.h>
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib, "d3d9.lib")
#include"dxgraphics.h"
//variable declarations
LPDIRECT3D9 d3d = NULL ;
LPDIRECT3DDEVICE9 d3ddev = NULL ;
LPDIRECT3DSURFACE9 backbuffer = NULL ;
int Init_Direct3D ( HWND hwnd , int width , int height , int fullscreen )
{
//initialize Direct3D
d3d = Direct3DCreate9 ( D3D_SDK_VERSION );
if ( d3d == NULL )
{
MessageBox ( hwnd , "Error initializing Direct3D" , "Error" , MB_OK );
return 0 ;
}
//set Direct3D presentation paramaters
D3DPRESENT_PARAMETERS d3dpp ;
ZeroMemory ( & d3dpp , sizeof ( d3dpp ));
d3dpp . Windowed = ( ! fullscreen );
d3dpp . SwapEffect = D3DSWAPEFFECT_COPY ;
d3dpp . BackBufferFormat = D3DFMT_X8R8G8B8 ;
d3dpp . BackBufferCount = 1 ;
d3dpp . BackBufferWidth = width ;
d3dpp . BackBufferHeight = height ;
d3dpp . hDeviceWindow = hwnd ;
//create Direct3D device
d3d -> CreateDevice ( D3DADAPTER_DEFAULT , D3DDEVTYPE_HAL , hwnd , D3DCREATE_SOFTWARE_VERTEXPROCESSING , & d3dpp , & d3ddev );
if ( d3ddev == NULL )
{
MessageBox ( hwnd , "Error creating Direct3D device" , "Error" , MB_OK );
return 0 ;
}
//clear the backbuffer to black
d3ddev -> Clear ( 0 , NULL , D3DCLEAR_TARGET , D3DCOLOR_XRGB ( 0 , 0 , 0 ), 1.0 f , 0 );
//create pointer to the back buffer
d3ddev -> GetBackBuffer ( 0 , 0 , D3DBACKBUFFER_TYPE_MONO , & backbuffer );
return 1 ;
}
LPDIRECT3DSURFACE9 LoadSurface ( char * filename , D3DCOLOR transcolor )
{
LPDIRECT3DSURFACE9 image = NULL ;
D3DXIMAGE_INFO info ;
HRESULT result ;
//get width and height from bitmap file
result = D3DXGetImageInfoFromFile ( filename , & info );
if ( result != D3D_OK )
return NULL ;
//create surface
result = d3ddev -> CreateOffscreenPlainSurface ( info . Width , info . Height , D3DFMT_X8R8G8B8 , D3DPOOL_DEFAULT , & image , NULL );
if ( result != D3D_OK )
return NULL ;
//load surface from file into newly created surface
result = D3DXLoadSurfaceFromFile ( image , NULL , NULL , filename , NULL , D3DX_DEFAULT , transcolor , NULL );
//make sure file was loaded okay
if ( result != D3D_OK )
return NULL ;
//return okay
return image ;
}
Tiếp tục thêm vào project một file header “game.h” và file cpp “game.cpp”. Code lần lượt cho 2 hàm trên:
//Anim_Sprite program header file
#ifndef _GAME_H
#define _GAME_H
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")
#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include"dxgraphics.h"
//application title
#define APPTITLE "Anim_Sprite"
//screen setup
#define FULLSCREEN 0 //1=fullscreen 0=windowed
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
//Các macro để đọc phím - Chế độ full màn hình
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000)?1:0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000)?1:0)
//function prototypes
int Game_Init ( HWND );
void Game_Run ( HWND );
void Game_End ( HWND );
//sprite structure
typedef struct
{
int x , y ;
int width , height ;
int movex , movey ;
int curframe , lastframe ;
int animdelay , animcount ;
} SPRITE ;
#endif // !_GAME_H
//Anim_Sprite program source code file
#include"game.h"
LPDIRECT3DSURFACE9 kitty_image [ 7 ];
SPRITE kitty ;
//timing variable
long start = GetTickCount ();
//initialize the game
int Game_Init ( HWND hwnd )
{
char s [ 20 ];
int n ;
//set random number seed
srand ( time ( NULL ));
//load the sprite animation
for ( n = 0 ; n < 6 ; n ++ )
{
sprintf_s ( s , "cat%d.bmp" , n + 1 );
kitty_image [ n ] = LoadSurface ( s , D3DCOLOR_XRGB ( 255 , 0 , 255 ));
if ( kitty_image [ n ] == NULL )
return 0 ;
}
//initialize the sprite's properties
kitty . x = 100 ;
kitty . y = 150 ;
kitty . width = 96 ;
kitty . height = 96 ;
kitty . curframe = 0 ;
kitty . lastframe = 5 ;
kitty . animdelay = 2 ;
kitty . animcount = 0 ;
kitty . movex = 8 ;
kitty . movey = 0 ;
return 1 ;
}
//the main game loop
void Game_Run ( HWND hwnd )
{
RECT rect ;
//make sure the Direct3d device is valid
if ( d3ddev == NULL )
return ;
//after short delay, ready for next frame?
//this keeps the game running at a steady frame rate
if ( GetTickCount () - start >= 30 )
{
//reset timing
start = GetTickCount ();
//move the sprite
kitty . x += kitty . movex ;
kitty . y += kitty . movey ;
//"wrap"the sprite at screen edges
if ( kitty . x > SCREEN_WIDTH - kitty . width )
kitty . x = 0 ;
if ( kitty . x < 0 )
kitty . x = SCREEN_WIDTH - kitty . width ;
//has animation delay reached threshold?
if ( ++ kitty . animcount > kitty . animdelay )
{
//reset counter
kitty . animcount = 0 ;
//animate the sprite
if ( ++ kitty . curframe > kitty . lastframe )
kitty . curframe = 0 ;
}
}
//start rendering
if ( d3ddev -> BeginScene ())
{
//erase the entire background
d3ddev -> ColorFill ( backbuffer , NULL , D3DCOLOR_XRGB ( 0 , 0 , 0 ));
//set the sprite's rect for drawing
rect . left = kitty . x ;
rect . top = kitty . y ;
rect . right = kitty . x + kitty . width ;
rect . bottom = kitty . y + kitty . height ;
//draw sprite
d3ddev -> StretchRect ( kitty_image [ kitty . curframe ], NULL , backbuffer , & rect , D3DTEXF_NONE );
//stop rendering
d3ddev -> EndScene ();
}
//display the back buffer on the screen
d3ddev -> Present ( NULL , NULL , NULL , NULL );
//check for escape key(to exit program)
if ( KEY_DOWN ( VK_ESCAPE ))
PostMessage ( hwnd , WM_DESTROY , 0 , 0 );
}
void Game_End ( HWND hwnd )
{
int n ;
//free the surface
for ( n = 0 ; n < 6 ; n ++ )
kitty_image [ n ] -> Release ();
}
Giờ là đến phần load background. Nếu chúng ta muốn con mèo được vẽ lên một nền không phải đen. Ta có thêm file “background.bmp” vào project.
Đầu tiên, thêm dòng này ở gần trên cùng của “game.cpp” với một vài biến được khai báo. LPDIRECT3DSURFACE9 back ;
Trong “Game_Init()”, thêm vài dòng để load ảnh nền lên một surface mới: back = LoadSurface ( "background.bmp" , NULL );
Trong “Game_Run()”, ẩn dòng “ColorFill” và thay bằng lời gọi hàm “StretchRect” như sau: d3ddev -> StretchRect ( back , NULL , backbuffer , NULL , D3DTEXF_NONE );
Cuối cùng, thêm 1 dòng ở “Game_End()” để giải phóng bộ nhớ đã sử dụng bởi background surface. back -> Release ();
Vẽ sprite trong suốt Sử dụng D3DXSprite.
Tạo đối tượng Sprite Handler : để quản lý, gọi hàm thao tác sprite.
LPD3DXSPRITE sprite_handler ;
result = D3DXCreateSprite ( d3ddev , & sprite_handler );
Bắt đầu Sprite Handler: Việc đầu tiên phải làm đó là khóa surface để sprite có thể vẽ bằng cách gọi hàm D3DXSprite.Begin. D3DXSPRITE_ALPHABLEND hỗ trợ vẽ trong suốt nếu không thì để giá trị NULL. Ví dụ:
sprite_handler -> Begin ( D3DXSPRITE_ALPHABLEND );
Vẽ sprite : sử dụng hàm Draw(). Cách khai báo như sau:
HRESULT Draw
(
LPDIRECT3DTEXTURE9 pTexture ,
CONST RECT * pSrcRect ,
CONST D3DXVECTOR3 * pCenter ,
CONST D3DXVECTOR3 * pPosition ,
D3DCOLOR Color
);
Trong đó:
pTexture: texture chứa sprite pSrcRect: diện tích cần hiển thị pCenter: tâm dùng để vẽ, xoay (NULL: mặc định là góc trên bên trái hình ảnh). pPosition: vị trí của sprite Color: màu thay thế (không ảnh hưởng khi vẽ trong suốt) D3DXVECTOR3 là một kiểu dữ liệu bao gồm 3 biến x, y, z kiểu float. Dừng Sprite Handler
sprite_handler -> End ();
Tải Sprite Image thông qua texture Trong game, thường ta sẽ sử dụng surface để hiển thị hình nền và sử dụng texture cho những sprite thể hiện các đối tượng game, nhân vật, phi thuyền, kẻ thù. Ta cần thực hiện hàm LoadTexture để được trả về texture cho mình.
LPDIRECT3DTEXTURE9 LoadTexture ( char * filename , D3DCOLOR transcolor )
{
//con trỏ texture
LPDIRECT3DTEXTURE9 texture = NULL ;
//struct để đọc thông tin file ảnh
D3DXIMAGE_INFO info ;
//trả về giá trị windows thông thường
HRESULT result ;
//get width and height from bitmap file
result = D3DXGetImageInfoFromFile ( filename , & info );
if ( result != D3D_OK )
return NULL ;
//create new texture by loading file bitmap
result = D3DXCreateTextureFromFileEx (
d3ddev , //đối tượng Direct3D
filename , //tên tệp ảnh
info . Width ,
info . Height ,
1 , //kết nối level
D3DPOOL_DEFAULT , //kiểu surface
D3DFMT_UNKNOWN , //định dạng surface
D3DPOOL_DEFAULT , //lớp bộ nhớ cho texture
D3DX_DEFAULT , //bộ lọc hình ảnh
D3DX_DEFAULT , //bộ lọc mip
transcolor , //màu chỉ ra trong suốt
& info , //thông tin tệp ảnh
NULL , //đổ màu
& texture //texture đích
);
//make sure file was loaded okay
if ( result != D3D_OK )
return NULL ;
//return okay
return texture ;
}
Chương trình Trans_Sprite Chương trình Trans_Sprite giải thích rõ hơn cách vẽ sprite trong suốt với Direct3D. Ta cần sao chép những tập tin sau từ Anim_Sprite vào project mới tạo: winmain.cpp, dxgraphics.h, dxgraphics.cpp. Sau đó tạo thêm các file “game.h” và “game.cpp” như sau: game.h
//Anim_Sprite program header file
#ifndef _GAME_H
#define _GAME_H
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9math.h>
#pragma comment(lib, "d3d9.lib")
#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include"dxgraphics.h"
//application title
#define APPTITLE "Trans_Sprite"
//screen setup
#define FULLSCREEN 0 //1=fullscreen 0=windowed
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
//Các macro để đọc phím - Chế độ full màn hình
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000)?1:0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) * 0x8000)?1:0)
//function prototypes
int Game_Init ( HWND );
void Game_Run ( HWND );
void Game_End ( HWND );
//sprite structure
typedef struct
{
int x , y ;
int width , height ;
int movex , movey ;
int curframe , lastframe ;
int animdelay , animcount ;
} SPRITE ;
#endif // !_GAME_H
game.cpp
#include"game.h"
LPDIRECT3DTEXTURE9 kitty_image [ 7 ];
SPRITE kitty ;
LPDIRECT3DSURFACE9 back ;
LPD3DXSPRITE sprite_handler ;
HRESULT result ;
//timing variable
long start = GetTickCount ();
//initialize the game
int Game_Init ( HWND hwnd )
{
char s [ 20 ];
int n ;
//set random number seed
srand ( time ( NULL ));
//create sprite handler
result = D3DXCreateSprite ( d3ddev , & sprite_handler );
if ( result != D3D_OK )
return 0 ;
//load the sprite animation
for ( n = 0 ; n < 6 ; n ++ )
{
sprintf_s ( s , "cat%d.bmp" , n + 1 );
//tải texture với màu hồng là màu trong suốt
kitty_image [ n ] = LoadTexture ( s , D3DCOLOR_XRGB ( 0 , 0 , 0 ));
if ( kitty_image [ n ] == NULL )
return 0 ;
}
//tải hình nền
back = LoadSurface ( "background.bmp" , NULL );
//initialize the sprite's properties
kitty . x = 100 ;
kitty . y = 150 ;
kitty . width = 96 ;
kitty . height = 96 ;
kitty . curframe = 0 ;
kitty . lastframe = 5 ;
kitty . animdelay = 2 ;
kitty . animcount = 0 ;
kitty . movex = 8 ;
kitty . movey = 0 ;
return 1 ;
}
//the main game loop
void Game_Run ( HWND hwnd )
{
RECT rect ;
//make sure the Direct3d device is valid
if ( d3ddev == NULL )
return ;
//after short delay, ready for next frame?
//this keeps the game running at a steady frame rate
if ( GetTickCount () - start >= 30 )
{
//reset timing
start = GetTickCount ();
//move the sprite
kitty . x += kitty . movex ;
kitty . y += kitty . movey ;
//"wrap"the sprite at screen edges
if ( kitty . x > SCREEN_WIDTH - kitty . width )
kitty . x = 0 ;
if ( kitty . x < 0 )
kitty . x = SCREEN_WIDTH - kitty . width ;
//has animation delay reached threshold?
if ( ++ kitty . animcount > kitty . animdelay )
{
//reset counter
kitty . animcount = 0 ;
//animate the sprite
if ( ++ kitty . curframe > kitty . lastframe )
kitty . curframe = 0 ;
}
}
//start rendering
if ( d3ddev -> BeginScene ())
{
//erase the entire background
d3ddev -> StretchRect ( back , NULL , backbuffer , NULL , D3DTEXF_NONE );
//start sprite handler
sprite_handler -> Begin ( D3DXSPRITE_ALPHABLEND );
//Tạo vector để cập nhật ví trí của sprite
D3DXVECTOR3 position (( float ) kitty . x , ( float ) kitty . y , 0 );
//draw sprite
sprite_handler -> Draw ( kitty_image [ kitty . curframe ], NULL , NULL , & position , D3DCOLOR_XRGB ( 255 , 255 , 255 ));
//stop drawing
sprite_handler -> End ();
//stop rendering
d3ddev -> EndScene ();
}
//display the back buffer on the screen
d3ddev -> Present ( NULL , NULL , NULL , NULL );
//check for escape key(to exit program)
if ( KEY_DOWN ( VK_ESCAPE ))
PostMessage ( hwnd , WM_DESTROY , 0 , 0 );
}
void Game_End ( HWND hwnd )
{
int n ;
//free the surface
for ( n = 0 ; n < 6 ; n ++ )
if ( kitty_image [ n ] != NULL )
kitty_image [ n ] -> Release ();
if ( back != NULL )
back -> Release ();
if ( sprite_handler != NULL )
sprite_handler -> Release ();
}
Thay đổi dxgraphics.h: thêm những dòng sau vào dxgraphics.h để được như sau
int Init_Direct3D ( HWND , int , int , int );
LPDIRECT3DTEXTURE9 LoadTexture ( char * , D3DCOLOR );
LPDIRECT3DSURFACE9 LoadSurface ( char * , D3DCOLOR );
Thay đổi dxgraphics.cpp: thêm hàm LoadTexture để chương trình có thể sử dụng nó.
Kết quả chương trình:
Tổng kết Chúng ta đã cùng nhau tìm hiểu việc tạo chuyển động trong game với kỹ thuật load Sprite, đặc biệt là 2 chương trình Anim_Sprite và Trans_Sprite …Các bạn hãy luyện tập code lại 2 chương trình này trong bài để nắm và hiểu rõ hơn về cách hoạt động của nó.😉 Hãy truy cập vào Series Make Game - TuiTuCode để học tiếp những bài thú vị khác nữa. Nếu có thắc mắc các bạn cứ bình luận bên dưới hoặc gửi thắc mắc về page TuiTuCode để các ad giải đáp. Pie~