第7章 Windows游戏输入消息处理
1. 键盘消息处理
之前提到的窗口过程函数有两参数与消息输出有关——wParam和llParam
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
当键盘消息触发时,wParam的值为按下按键的虚拟键码,lParam则存储按键的相关状态的信息,因此,如果要对键盘输入的消息进行处理,就可以用一个switch来判断wParam中的内容并进行相关的处理。
下面示例程序让玩家以上下左右方向键来控制人物的移动,使用透明遮罩法进行透明贴图:
#include <windows.h>
#include <tchar.h>//使用swprintf_s函数所需的头文件 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 #define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】Windows消息处理之 键盘消息处理 " //为窗口标题定义的宏 HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与两个全局内存DC句柄
HBITMAP g_hSprite[]={NULL},g_hBackGround=NULL; //定义位图句柄数组用于存储四张方向图,以及定义存储背景图的句柄
DWORD g_tPre=,g_tNow=; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
int g_iNum=,g_iX=,g_iY=; //g_iNum用来记录图号,g_iX,g_iY分别表示贴图的横纵坐标
int g_iDirection=;//g_iDirection为人物移动方向,这里我们中以0,1,2,3代表人物上,下,左,右方向上的移动 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数
BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化
VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写
BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
//【1】窗口创建四步曲之一:开始设计一个完整的窗口类
WNDCLASSEX wndClass = { }; //用WINDCLASSEX定义了一个窗口类
wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式
wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针
wndClass.cbClsExtra = ; //窗口类的附加内存,取0就可以了
wndClass.cbWndExtra = ; //窗口的附加内存,依然取0就行了
wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。
wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,,,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。
wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄
wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类
if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
return -; //【3】窗口创建四步曲之三:正式创建窗口
HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //【4】窗口创建四步曲之四:窗口的移动、显示与更新
MoveWindow(hwnd,,,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处
ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口
UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE
if (!Game_Init (hwnd))
{
MessageBox(hwnd, L"资源初始化失败", L"消息窗口", ); //使用MessageBox函数,创建一个消息窗口
return FALSE;
} //【5】消息循环过程
MSG msg = { }; //定义并初始化msg
while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环
{
if( PeekMessage( &msg, , , , PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage( &msg ); //将虚拟键消息转换为字符消息
DispatchMessage( &msg ); //分发一个消息给窗口程序。
}
else
{
g_tNow = GetTickCount(); //获取当前系统时间
if(g_tNow-g_tPre >= ) //当此次循环运行与上次绘图时间相差0.05秒时再进行重绘操作
Game_Paint(hwnd);
} } //【6】窗口类的注销
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类
return ;
} LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{ switch( message ) //switch语句开始
{ case WM_KEYDOWN: //按下键盘消息
//判断按键的虚拟键码
switch (wParam)
{
case VK_ESCAPE: //按下【Esc】键
DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息
PostQuitMessage( ); //结束程序
break;
case VK_UP: //按下【↑】键
//根据按键加入人物移动的量(每次按下一次按键移动10个单位),来决定人物贴图坐标的X与Y值,接着判断坐标是否超出窗口区域,若有则进行修正
g_iY -= ;
g_iDirection = ;
if(g_iY < )
g_iY = ;
break;
case VK_DOWN: //按下【↓】键
g_iY += ;
g_iDirection = ;
if(g_iY > WINDOW_HEIGHT-)
g_iY = WINDOW_HEIGHT-;
break;
case VK_LEFT: //按下【←】键
g_iX -= ;
g_iDirection = ;
if(g_iX < )
g_iX = ;
break;
case VK_RIGHT: //按下【→】键
g_iX += ;
g_iDirection = ;
if(g_iX > WINDOW_WIDTH-)
g_iX = WINDOW_WIDTH-;
break;
}
break; //跳出该switch语句 case WM_DESTROY: //若是窗口销毁消息
Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理
PostQuitMessage( ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句
return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程
} return ; //正常退出
} //-----------------------------------【Game_Init( )函数】--------------------------------------
// 描述:初始化函数,进行一些简单的初始化
//------------------------------------------------------------------------------------------------
BOOL Game_Init( HWND hwnd )
{
HBITMAP bmp; g_hdc = GetDC(hwnd);
g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //设定人物贴图初始位置和移动方向
g_iX = ;
g_iY = ;
g_iDirection = ;
g_iNum = ; SelectObject(g_mdc,bmp);
//加载各张跑动图及背景图,这里以0,1,2,3来代表人物的上,下,左,右移动
g_hSprite[] = (HBITMAP)LoadImage(NULL,L"go1.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE);
g_hSprite[] = (HBITMAP)LoadImage(NULL,L"go2.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE);
g_hSprite[] = (HBITMAP)LoadImage(NULL,L"go3.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE);
g_hSprite[] = (HBITMAP)LoadImage(NULL,L"go4.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE);
g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); Game_Paint(hwnd);
return TRUE;
} //-----------------------------------【Game_Paint( )函数】--------------------------------------
// 描述:绘制函数,在此函数中进行绘制操作
//--------------------------------------------------------------------------------------------------
VOID Game_Paint( HWND hwnd )
{
//先在mdc中贴上背景图
SelectObject(g_bufdc,g_hBackGround);
BitBlt(g_mdc,,,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,,,SRCCOPY); //按照目前的移动方向取出对应人物的连续走动图,并确定截取人物图的宽度与高度
SelectObject(g_bufdc,g_hSprite[g_iDirection]);
BitBlt(g_mdc,g_iX,g_iY,,,g_bufdc,g_iNum*,,SRCAND);
BitBlt(g_mdc,g_iX,g_iY,,,g_bufdc,g_iNum*,,SRCPAINT);
//将最后的画面显示在窗口中
BitBlt(g_hdc,,,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,,,SRCCOPY); g_tPre = GetTickCount(); //记录此次绘图时间
g_iNum++;
if(g_iNum == )
g_iNum = ; } //-----------------------------------【Game_CleanUp( )函数】--------------------------------
// 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作
//---------------------------------------------------------------------------------------------------
BOOL Game_CleanUp( HWND hwnd )
{
//释放资源对象
DeleteObject(g_hBackGround);
for (int i=;i<;i++)
{
DeleteObject(g_hSprite[i]);
}
DeleteDC(g_bufdc);
DeleteDC(g_mdc);
ReleaseDC(hwnd,g_hdc);
return TRUE;
}
要点:
根据键盘输入的消息来改变贴图坐标,从而在下次绘制时改变贴图的位置,产生一种移动的效果。
2. 鼠标消息处理
对于鼠标消息,lParam参数的值可分为高字节和低字节两个部分,其中高字节存储的是光标所在的X坐标值,低字节存储Y坐标值。可以通过下面两个宏来取得鼠标的坐标值:
Function:Retrieves the low-order word from the specified value
WORD LOWORD(
DWORD dwValue
);
Function:Retrieves the high-order word from the specified 32-bit value.
WORD HIWORD(
DWORD dwValue
);
WORD:A 16-bit unsigned integer. The range is 0 through 65535 decimal. This type is declared in WinDef.h as follows: typedef unsigned short WORD;
DWORD:A 32-bit unsigned integer. The range is 0 through 4294967295 decimal. This type is declared in IntSafe.h as follows: typedef unsigned long DWORD;
wParam参数的值记录着鼠标按键及键盘Ctrl键与Shift键的状态信息,一般通过定义在 "WINUSER.H" 中的测试标志与wParam参数来检查上述按键的按下状态。比如某个鼠标消息发生时,要测试鼠标左键是否也被按下,就把wParam拿着和某种消息&(逻辑与)一下:
if(wParam & MK_LBUTTON) //单击了鼠标左键
{
//鼠标左键被按下的消息处理代码
}
比如要测试鼠标左键与Shift键的按下状态,那么程序可以这么写:
if(wParam & MK_LBUTTON) //单击了鼠标左键
{
if( wParam & MK_CONTROL ) //单击了鼠标左键,也按下了Ctrl键
{
}
else
{
}
}
else
{
}
3. 滚轮消息
滚轮转动消息WM_MOUSEWHEEL。当滚轮消息发生时,lParam参数中的值同样记录光标所在的位置的,而wParam参数则分为高字节和低字节两部分,低字节部分存储鼠标键与Shift、Ctrl键的状态信息,高字节部分的值会是“120”或“-120”,120表示向前滚动,-120则表示向后转动。
4. 鼠标相关常用函数(详细查mdsn文档)
setCursorPos函数来设定光标位置,将窗口坐标转换到屏幕坐标函数ClientToScreen,将屏幕坐标转换为窗口坐标的ScreenToClient函数,显示与隐藏鼠标光标函数ShowCursor,获取窗口外鼠标消息的函数SetCapture,释放窗口取得窗外鼠标消息函数ReleaseCapture,限制鼠标光标移动区域函数ClipCursor,取得窗口外部区域函数GetWindowRect以及取得内部区域函数GetClientRect
下面是一个综合的程序:
RECT rect;
POINT lt,rb; GetClientRect(hWnd,&rect); //取得窗口内部矩形 //将矩形左上角坐标存入lt中
lt.x=rect.left;
lt.y=rect.top;
//将矩形右下角坐标存入rb中
rb.x=rect.right;
rb.y=rect.bottom;
//将lt和rb的窗口坐标转换为屏幕坐标
ClientToScreen(hWnd,<);
ClientToScreen(hWnd,&rb);
//以屏幕坐标重新设定矩形区域
rect.left=lt.x;
rect.top=lt.y;
rect.right=rb.x;
rect.bottom=rb.y;
//限制鼠标光标移动区域
ClipCursor(&rect);
因为限制鼠标光标移动区域的ClipCursor函数中输入的矩形区域必须是屏幕坐标,因此如果取得的是窗口内部区域,那么还必须将那个窗口坐标转换为屏幕坐标。
The POINT structure defines the x- and y- coordinates of a point.
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT;
The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle.
typedef struct _RECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT;
国际惯例,还是来个完整的示例程序,要实现的效果是这样的:
将第一张图贴到背景图上,用鼠标控制其移动,单击鼠标发射弹幕
#include <windows.h>
#include <tchar.h>//使用swprintf_s函数所需的头文件 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 #define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】Windows消息处理之 鼠标消息处理 " //为窗口标题定义的宏 struct SwordBullets //SwordBullets结构体代表剑气(子弹)
{
int x,y; //剑气(子弹)坐标
bool exist; //剑气(子弹)是否存在
}; //-----------------------------------【全局变量声明部分】-------------------------------------
HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄
HBITMAP g_hSwordMan=NULL,g_hSwordBlade=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源
DWORD g_tPre=,g_tNow=; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
int g_iX=,g_iY=,g_iXnow=,g_iYnow=; //g_iX,g_iY代表鼠标光标所在位置,g_iXnow,g_iYnow代表当前人物坐标,也就是贴图的位置
int g_iBGOffset=,g_iBulletNum=; //g_iBGOffset为滚动背景所要裁剪的区域宽度,g_iBulletNum记录剑侠现有剑气(子弹)数目
SwordBullets Bullet[]; //声明一个“SwordBullets”类型的数组,用来存储剑侠发出的剑气(子弹) //-----------------------------------【全局函数声明部分】-------------------------------------
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数
BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化
VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写
BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 //-----------------------------------【WinMain( )函数】--------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
//【1】窗口创建四步曲之一:开始设计一个完整的窗口类
WNDCLASSEX wndClass = { }; //用WINDCLASSEX定义了一个窗口类
wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式
wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针
wndClass.cbClsExtra = ; //窗口类的附加内存,取0就可以了
wndClass.cbWndExtra = ; //窗口的附加内存,依然取0就行了
wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。
wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,,,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。
wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄
wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类
if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
return -; //【3】窗口创建四步曲之三:正式创建窗口
HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //【4】窗口创建四步曲之四:窗口的移动、显示与更新
MoveWindow(hwnd,,,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处
ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口
UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE
if (!Game_Init (hwnd))
{
MessageBox(hwnd, L"资源初始化失败", L"消息窗口", ); //使用MessageBox函数,创建一个消息窗口
return FALSE;
} //【5】消息循环过程
MSG msg = { }; //定义并初始化msg
while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环
{
if( PeekMessage( &msg, , , , PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage( &msg ); //将虚拟键消息转换为字符消息
DispatchMessage( &msg ); //分发一个消息给窗口程序。
}
else
{
g_tNow = GetTickCount(); //获取当前系统时间
if(g_tNow-g_tPre >= ) //当此次循环运行与上次绘图时间相差0.1秒时再进行重绘操作
Game_Paint(hwnd);
} } //【6】窗口类的注销
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类
return ;
} //-----------------------------------【WndProc( )函数】--------------------------------------
// 描述:窗口过程函数WndProc,对窗口消息进行处理
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message ) //switch语句开始
{ case WM_KEYDOWN: //按下键盘消息
//判断按键的虚拟键码
switch (wParam)
{
case VK_ESCAPE: //按下【Esc】键
DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息
PostQuitMessage( ); //结束程序
break;
} break; case WM_LBUTTONDOWN: //单击鼠标左键消息
for(int i=;i<;i++)
{
if(!Bullet[i].exist)
{
Bullet[i].x = g_iXnow; //剑气(子弹)x坐标
Bullet[i].y = g_iYnow + ; //剑气(子弹)y坐标
Bullet[i].exist = true;
g_iBulletNum++; //累加剑气(子弹)数目
break;
}
} case WM_MOUSEMOVE: //鼠标移动消息
//对X坐标的处理
g_iX = LOWORD(lParam); //取得鼠标X坐标
if(g_iX > WINDOW_WIDTH-) //设置临界坐标
g_iX = WINDOW_WIDTH-;
else if(g_iX < )
g_iX = ;
//对Y坐标的处理
g_iY = HIWORD(lParam); //取得鼠标Y坐标
if(g_iY > WINDOW_HEIGHT-)
g_iY = WINDOW_HEIGHT-;
else if(g_iY < -)
g_iY = -;
break; case WM_DESTROY: //若是窗口销毁消息
Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理
PostQuitMessage( ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句
return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程
}
return ; //正常退出
} //-----------------------------------【Game_Init( )函数】--------------------------------------
// 描述:初始化函数,进行一些简单的初始化
//------------------------------------------------------------------------------------------------
BOOL Game_Init( HWND hwnd )
{
HBITMAP bmp; g_hdc = GetDC(hwnd);
g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //设定人物贴图初始值,鼠标位置初始值
g_iX = ;
g_iY = ;
g_iXnow = ;
g_iYnow = ; SelectObject(g_mdc,bmp);
//加载各张跑动图及背景图
g_hSwordMan = (HBITMAP)LoadImage(NULL,L"swordman.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE);
g_hSwordBlade = (HBITMAP)LoadImage(NULL,L"swordblade.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE);
g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); POINT pt,lt,rb;
RECT rect;
//设定光标位置
pt.x = ;
pt.y = ;
ClientToScreen(hwnd,&pt);
SetCursorPos(pt.x,pt.y); ShowCursor(false); //隐藏鼠标光标 //限制鼠标光标移动区域
GetClientRect(hwnd,&rect); //取得窗口内部矩形
//将矩形左上点坐标存入lt中
lt.x = rect.left;
lt.y = rect.top;
//将矩形右下坐标存入rb中
rb.x = rect.right;
rb.y = rect.bottom;
//将lt和rb的窗口坐标转换为屏幕坐标
ClientToScreen(hwnd,<);
ClientToScreen(hwnd,&rb);
//以屏幕坐标重新设定矩形区域
rect.left = lt.x;
rect.top = lt.y;
rect.right = rb.x;
rect.bottom = rb.y;
//限制鼠标光标移动区域
ClipCursor(&rect); Game_Paint(hwnd);
return TRUE;
} //-----------------------------------【Game_Paint( )函数】--------------------------------------
// 描述:绘制函数,在此函数中进行绘制操作
//--------------------------------------------------------------------------------------------------
VOID Game_Paint( HWND hwnd )
{
//先在mdc中贴上背景图
SelectObject(g_bufdc,g_hBackGround);
BitBlt(g_mdc,,,g_iBGOffset,WINDOW_HEIGHT,g_bufdc,WINDOW_WIDTH-g_iBGOffset,,SRCCOPY);
BitBlt(g_mdc,g_iBGOffset,,WINDOW_WIDTH-g_iBGOffset,WINDOW_HEIGHT,g_bufdc,,,SRCCOPY); wchar_t str[] = {}; //计算剑侠的贴图坐标,设定每次进行剑侠贴图时,其贴图坐标(g_iXnow,g_iYnow)会以10个单位慢慢向鼠标光标所在的目的点(x,y)接近,直到两个坐标相同为止
if(g_iXnow < g_iX)//若当前贴图X坐标小于鼠标光标的X坐标
{
g_iXnow += ;
if(g_iXnow > g_iX)
g_iXnow = g_iX;
}
else //若当前贴图X坐标大于鼠标光标的X坐标
{
g_iXnow -=;
if(g_iXnow < g_iX)
g_iXnow = g_iX;
} if(g_iYnow < g_iY) //若当前贴图Y坐标小于鼠标光标的Y坐标
{
g_iYnow += ;
if(g_iYnow > g_iY)
g_iYnow = g_iY;
}
else //若当前贴图Y坐标大于于鼠标光标的Y坐标
{
g_iYnow -= ;
if(g_iYnow < g_iY)
g_iYnow = g_iY;
} //贴上剑侠图
SelectObject(g_bufdc,g_hSwordMan);
TransparentBlt(g_mdc,g_iXnow,g_iYnow,,,g_bufdc,,,,,RGB(,,)); //剑气(子弹)的贴图,先判断剑气(子弹)数目“g_iBulletNum”的值是否为“0”。若不为0,则对剑气(子弹)数组中各个还存在的剑气(子弹)按照其所在的坐标(b[i].x,b[i].y)循环进行贴图操作
SelectObject(g_bufdc,g_hSwordBlade);
if(g_iBulletNum!=)
for(int i=;i<;i++)
if(Bullet[i].exist)
{
//贴上剑气(子弹)图
TransparentBlt(g_mdc,Bullet[i].x-,Bullet[i].y+,,,g_bufdc,,,,,RGB(,,)); //设置下一个剑气(子弹)的坐标。剑气(子弹)是从右向左发射的,因此,每次其X轴上的坐标值递减10个单位,这样贴图会产生往左移动的效果。而如果剑气(子弹)下次的坐标已超出窗口的可见范围(h[i].x<0),那么剑气(子弹)设为不存在,并将剑气(子弹)总数g_iBulletNum变量值减1.
Bullet[i].x -= ;
if(Bullet[i].x < )
{
g_iBulletNum--;
Bullet[i].exist = false;
}
} HFONT hFont;
hFont=CreateFont(,,,,,,,,GB2312_CHARSET,,,,,TEXT("微软雅黑")); //创建字体
SelectObject(g_mdc,hFont); //选入字体到g_mdc中
SetBkMode(g_mdc, TRANSPARENT); //设置文字背景透明
SetTextColor(g_mdc,RGB(,,)); //设置文字颜色 //在左上角进行文字输出
swprintf_s(str,L"鼠标X坐标为%d ",g_iX);
TextOut(g_mdc,,,str,wcslen(str));
swprintf_s(str,L"鼠标Y坐标为%d ",g_iY);
TextOut(g_mdc,,,str,wcslen(str)); //贴上背景图
BitBlt(g_hdc,,,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,,,SRCCOPY); g_tPre = GetTickCount(); g_iBGOffset += ; //让背景滚动量+5
if(g_iBGOffset==WINDOW_WIDTH)//如果背景滚动量达到了背景宽度值,就置零
g_iBGOffset = ;
} //-----------------------------------【Game_CleanUp( )函数】--------------------------------
BOOL Game_CleanUp( HWND hwnd )
{
//释放资源对象
DeleteObject(g_hBackGround);
DeleteDC(g_bufdc);
DeleteDC(g_mdc);
ReleaseDC(hwnd,g_hdc);
return TRUE;
}
第8章 物理建模与粒子系统初步
关于物理建模这块,并没有涉及到新的函数的使用,主要是利用物理和数学知识来模拟匀速,加速运动,重力系统和摩擦力系统,这里要好好读读作者的源码才行。
使用结构体来定义粒子,如下面这个结构体snow便是用来定义“雪花”粒子的:
struct snow
{
int x; //雪花的x坐标
int y; //雪花的Y坐标
BOOL exist; //雪花是否存在
}
定义完粒子的结构体后,便可以实例化一个粒子数组,可以用如下两种方法来进行:
在结构体声明时的尾部加上我们需要实例化的对象:
struct snow
{
int x; //雪花的x坐标
int y; //雪花的Y坐标
BOOL exist; //雪花是否存在
} SnowFlowers [];
或者在完成结构体定义后,再单独进行定义:
snow SnowFlowers []
雪花飞舞示例程序:
#include <windows.h>
#include <tchar.h>//使用swprintf_s函数所需的头文件
#include <time.h> //使用获取系统时间time()函数需要包含的头文件 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 //-----------------------------------【宏定义部分】--------------------------------------------
#define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 雪花飞舞demo" //为窗口标题定义的宏
#define PARTICLE_NUMBER 80 //表示粒子数量的宏,以方便修改粒子数量 //-----------------------------------【全局结构体定义部分】-------------------------------------
struct SNOW
{
int x; //雪花的 X坐标
int y; //雪花的Y坐标
BOOL exist; //雪花是否存在
}; //-----------------------------------【全局变量声明部分】-------------------------------------
HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄
HBITMAP g_hSnow=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源
DWORD g_tPre=,g_tNow=; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
RECT g_rect; //定义一个RECT结构体,用于储存内部窗口区域的坐标
SNOW SnowFlowers[PARTICLE_NUMBER]; //雪花粒子数组
int g_SnowNum=; //定义g_SnowNum用于计数 //-----------------------------------【全局函数声明部分】-------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数
BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化
VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写
BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 //-----------------------------------【WinMain( )函数】--------------------------------------
// 描述:Windows应用程序的入口函数,我们的程序从这里开始
//------------------------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
//【1】窗口创建四步曲之一:开始设计一个完整的窗口类
WNDCLASSEX wndClass = { }; //用WINDCLASSEX定义了一个窗口类
wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式
wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针
wndClass.cbClsExtra = ; //窗口类的附加内存,取0就可以了
wndClass.cbWndExtra = ; //窗口的附加内存,依然取0就行了
wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。
wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,,,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。
wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄
wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类
if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
return -; //【3】窗口创建四步曲之三:正式创建窗口
HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //【4】窗口创建四步曲之四:窗口的移动、显示与更新
MoveWindow(hwnd,,,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处
ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口
UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE
if (!Game_Init (hwnd))
{
MessageBox(hwnd, L"资源初始化失败", L"消息窗口", ); //使用MessageBox函数,创建一个消息窗口
return FALSE;
} //【5】消息循环过程
MSG msg = { }; //定义并初始化msg
while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环
{
if( PeekMessage( &msg, , , , PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage( &msg ); //将虚拟键消息转换为字符消息
DispatchMessage( &msg ); //分发一个消息给窗口程序。
}
else
{
g_tNow = GetTickCount(); //获取当前系统时间
if(g_tNow-g_tPre >= ) //当此次循环运行与上次绘图时间相差0.06秒时再进行重绘操作
Game_Paint(hwnd);
} } //【6】窗口类的注销
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类
return ;
} //-----------------------------------【WndProc( )函数】--------------------------------------
// 描述:窗口过程函数WndProc,对窗口消息进行处理
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{ switch( message ) //switch语句开始
{
case WM_KEYDOWN: //按键消息
if(wParam==VK_ESCAPE) //按下【Esc】键
PostQuitMessage();
break; case WM_DESTROY: //若是窗口销毁消息
Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理
PostQuitMessage( ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句
return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程
} return ; //正常退出
} //-----------------------------------【Game_Init( )函数】--------------------------------------
// 描述:初始化函数,进行一些简单的初始化
//------------------------------------------------------------------------------------------------
BOOL Game_Init( HWND hwnd )
{
srand((unsigned)time(NULL)); //用系统时间初始化随机种子 HBITMAP bmp; g_hdc = GetDC(hwnd);
g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象 SelectObject(g_mdc,bmp);//将空位图对象放到g_mdc中 //载入位图资源
g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE);
g_hSnow = (HBITMAP)LoadImage(NULL,L"snow.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE); GetClientRect(hwnd,&g_rect); //取得内部窗口区域的大小 Game_Paint(hwnd);
return TRUE;
} //-----------------------------------【Game_Paint( )函数】--------------------------------------
// 描述:绘制函数,在此函数中进行绘制操作
//--------------------------------------------------------------------------------------------------
VOID Game_Paint( HWND hwnd )
{ //先在mdc中贴上背景图
SelectObject(g_bufdc,g_hBackGround);
BitBlt(g_mdc,,,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,,,SRCCOPY); //创建粒子
if(g_SnowNum< PARTICLE_NUMBER) //当粒子数小于规定的粒子数时,便产生新的粒子,设定每个粒子的属性值
{
SnowFlowers[g_SnowNum].x = rand()%g_rect.right; //将粒子的X坐标设为窗口中水平方向上的任意位置
SnowFlowers[g_SnowNum].y = ; //将每个粒子的Y坐标都设为"0",即从窗口上沿往下落
SnowFlowers[g_SnowNum].exist = true; //设定粒子存在
g_SnowNum++; //每产生一个粒子后进行累加计数
} //首先判断粒子是否存在,若存在,进行透明贴图操作
for(int i=;i<PARTICLE_NUMBER;i++)
{
if(SnowFlowers[i].exist) //粒子还存在
{
//贴上粒子图
SelectObject(g_bufdc,g_hSnow);
TransparentBlt(g_mdc,SnowFlowers[i].x,SnowFlowers[i].y,,,g_bufdc,,,,,RGB(,,)); //随机决定横向的移动方向和偏移量
if(rand()%==)
SnowFlowers[i].x+=rand()%; //x坐标加上0~5之间的一个随机值
else
SnowFlowers[i].x-=rand()%; //y坐标加上0~5之间的一个随机值
//纵方向上做匀速运动
SnowFlowers[i].y+=; //纵坐标加上10
//如果粒子坐标超出了窗口长度,就让它以随机的x坐标出现在窗口顶部
if(SnowFlowers[i].y > g_rect.bottom)
{
SnowFlowers[i].x = rand()%g_rect.right;
SnowFlowers[i].y = ;
}
} }
//将mdc中的全部内容贴到hdc中
BitBlt(g_hdc,,,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,,,SRCCOPY); g_tPre = GetTickCount(); //记录此次绘图时间
} //-----------------------------------【Game_CleanUp( )函数】--------------------------------
// 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作
//---------------------------------------------------------------------------------------------------
BOOL Game_CleanUp( HWND hwnd )
{
//释放资源对象
DeleteObject(g_hBackGround);
DeleteObject(g_hSnow);
DeleteDC(g_bufdc);
DeleteDC(g_mdc);
ReleaseDC(hwnd,g_hdc);
return TRUE;
}
星光绽放示例程序:
模拟一个爆炸特效,爆炸点由随机数产生一个位置,爆炸后会出现很多星光以不同的速度向四方飞散,当粒子飞出窗口或者超出时间后便会消失。首先用结构体来构造出星光粒子:
struct FLYSTAR
{
int x; //星光所在的x坐标
int y; //y坐标
int vx; //星光x方向的速度
int vy; //y方向速度
int lasted; //星光存在的时间
BOOL exist; //星光是否存在
};
完整程序:
#include <windows.h>
#include <tchar.h>//使用swprintf_s函数所需的头文件
#include <time.h> //使用获取系统时间time函数需要包含的头文件 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 #define WINDOW_WIDTH 932 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 700 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 星光绽放demo" //为窗口标题定义的宏
#define FLYSTAR_NUMBER 100 //表示粒子数量的宏,以方便修改粒子数量
#define FLYSTAR_LASTED_FRAME 60 //表示粒子持续帧数的宏,以方便修改每次星光绽放持续的时间 struct FLYSTAR
{
int x; //星光所在的x坐标
int y; //星光所在的y坐标
int vx; //星光x方向的速度
int vy; //星光y方向的速度
int lasted; //星光存在的时间
BOOL exist; //星光是否存在
}; HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄
HBITMAP g_hStar=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源
DWORD g_tPre=,g_tNow=; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
RECT g_rect; //定义一个RECT结构体,用于储存内部窗口区域的坐标
FLYSTAR FlyStars[FLYSTAR_NUMBER]; //粒子数组
int g_StarNum=; //定义g_StarNum用于计数 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数
BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化
VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写
BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 //-----------------------------------【WinMain( )函数】--------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
//【1】窗口创建四步曲之一:开始设计一个完整的窗口类
WNDCLASSEX wndClass = { }; //用WINDCLASSEX定义了一个窗口类
wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式
wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针
wndClass.cbClsExtra = ; //窗口类的附加内存,取0就可以了
wndClass.cbWndExtra = ; //窗口的附加内存,依然取0就行了
wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。
wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,,,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。
wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄
wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类
if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
return -; //【3】窗口创建四步曲之三:正式创建窗口
HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //【4】窗口创建四步曲之四:窗口的移动、显示与更新
MoveWindow(hwnd,,,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(150,20)处
ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口
UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE
if (!Game_Init (hwnd))
{
MessageBox(hwnd, L"资源初始化失败", L"消息窗口", ); //使用MessageBox函数,创建一个消息窗口
return FALSE;
} //【5】消息循环过程
MSG msg = { }; //定义并初始化msg
while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环
{
if( PeekMessage( &msg, , , , PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage( &msg ); //将虚拟键消息转换为字符消息
DispatchMessage( &msg ); //分发一个消息给窗口程序。
}
else
{
g_tNow = GetTickCount(); //获取当前系统时间
if(g_tNow-g_tPre >= ) //当此次循环运行与上次绘图时间相差0.03秒时再进行重绘操作
Game_Paint(hwnd);
} } //【6】窗口类的注销
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类
return ;
} //-----------------------------------【WndProc( )函数】--------------------------------------
// 描述:窗口过程函数WndProc,对窗口消息进行处理
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{ switch( message ) //switch语句开始
{
case WM_KEYDOWN: //按键消息
if(wParam==VK_ESCAPE) //按下【Esc】键
PostQuitMessage();
break; case WM_DESTROY: //若是窗口销毁消息
Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理
PostQuitMessage( ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句
return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程
} return ; //正常退出
} //-----------------------------------【Game_Init( )函数】--------------------------------------
// 描述:初始化函数,进行一些简单的初始化
//------------------------------------------------------------------------------------------------
BOOL Game_Init( HWND hwnd )
{ srand((unsigned)time(NULL)); //用系统时间初始化随机种子
HBITMAP bmp; g_hdc = GetDC(hwnd);
g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象 SelectObject(g_mdc,bmp);//将空位图对象放到g_mdc中 //载入位图背景
g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE);
g_hStar = (HBITMAP)LoadImage(NULL,L"star.bmp",IMAGE_BITMAP,,,LR_LOADFROMFILE); GetClientRect(hwnd,&g_rect); //取得内部窗口区域的大小 Game_Paint(hwnd);
return TRUE;
} //-----------------------------------【Game_Paint( )函数】--------------------------------------
// 描述:绘制函数,在此函数中进行绘制操作
//--------------------------------------------------------------------------------------------------
VOID Game_Paint( HWND hwnd )
{ //先在mdc中贴上背景图
SelectObject(g_bufdc,g_hBackGround);
BitBlt(g_mdc,,,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,,,SRCCOPY); //创建粒子
if(g_StarNum == ) //随机设置爆炸点
{
int x=rand()%g_rect.right;
int y=rand()%g_rect.bottom; for(int i=;i<FLYSTAR_NUMBER;i++) //产生星光粒子
{
FlyStars[i].x = x;
FlyStars[i].y = y;
FlyStars[i].lasted = ; //设定该粒子存在的时间为零
//按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。
if(i%==)
{
FlyStars[i].vx = -(+rand()%);
FlyStars[i].vy = -(+rand()%);
}
if(i%==)
{
FlyStars[i].vx = +rand()%;
FlyStars[i].vy = +rand()%;
}
if(i%==)
{
FlyStars[i].vx = -(+rand()%);
FlyStars[i].vy = +rand()%;
}
if(i%==)
{
FlyStars[i].vx = +rand()%;
FlyStars[i].vy = -(+rand()%);
}
FlyStars[i].exist = true; //设定粒子存在
}
g_StarNum = FLYSTAR_NUMBER; //全部粒子由for循环设置完成后,我们将粒子数量设为FLYSTAR_NUMBER,代表目前有FLYSTAR_NUMBER颗星光
} //显示粒子并更新下一帧的粒子坐标
for(int i=;i<FLYSTAR_NUMBER;i++)
{
if(FlyStars[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(FlyStars[i].x,FlyStars[i].y)进行贴图操作
{
SelectObject(g_bufdc,g_hStar);
TransparentBlt(g_mdc,FlyStars[i].x,FlyStars[i].y,,,g_bufdc,,,,,RGB(,,)); //计算下一次贴图的坐标
FlyStars[i].x+=FlyStars[i].vx;
FlyStars[i].y+=FlyStars[i].vy; //在每进行一次贴图后,将粒子的存在时间累加1.
FlyStars[i].lasted++;
//进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减
if(FlyStars[i].x<=- || FlyStars[i].x>g_rect.right || FlyStars[i].y<=- || FlyStars[i].y>g_rect.bottom || FlyStars[i].lasted>FLYSTAR_LASTED_FRAME)
{
FlyStars[i].exist = false; //删除星光粒子
g_StarNum--; //递减星光总数
}
}
} //将mdc中的全部内容贴到hdc中
BitBlt(g_hdc,,,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,,,SRCCOPY); g_tPre = GetTickCount(); //记录此次绘图时间
} //-----------------------------------【Game_CleanUp( )函数】--------------------------------
// 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作
//---------------------------------------------------------------------------------------------------
BOOL Game_CleanUp( HWND hwnd )
{
//释放资源对象
DeleteObject(g_hBackGround);
DeleteObject(g_hStar);
DeleteDC(g_bufdc);
DeleteDC(g_mdc);
ReleaseDC(hwnd,g_hdc);
return TRUE;
}
参考博文:
【Visual C++】游戏开发笔记十二 游戏输入消息处理(一) 键盘消息处理
【Visual C++】游戏开发笔记十三 游戏输入消息处理(二) 鼠标消息处理
【Visual C++】游戏开发笔记十八 游戏基础物理建模(一) 匀速与加速运动
【Visual C++】游戏开发笔记二十 游戏基础物理建模(二) 重力系统的模拟
【Visual C++】游戏开发笔记二十一 游戏基础物理建模(三) 摩擦力系统模拟