第7章 Windows游戏输入消息处理
1. 键盘消息处理
之前提到的窗口过程函数有两参数与消息输出有关——wParam和llParam
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
当键盘消息触发时,wParam的值为按下按键的虚拟键码,lParam则存储按键的相关状态的信息,因此,如果要对键盘输入的消息进行处理,就可以用一个switch来判断wParam中的内容并进行相关的处理。
下面示例程序让玩家以上下左右方向键来控制人物的移动,使用透明遮罩法进行透明贴图:
1 #include <windows.h> 2 #include <tchar.h>//使用swprintf_s函数所需的头文件 3 4 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 5 6 #define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 7 #define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度 8 #define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】Windows消息处理之 键盘消息处理 " //为窗口标题定义的宏 9 10 HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与两个全局内存DC句柄 11 HBITMAP g_hSprite[4]={NULL},g_hBackGround=NULL; //定义位图句柄数组用于存储四张方向图,以及定义存储背景图的句柄 12 DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间 13 int g_iNum=0,g_iX=0,g_iY=0; //g_iNum用来记录图号,g_iX,g_iY分别表示贴图的横纵坐标 14 int g_iDirection=0;//g_iDirection为人物移动方向,这里我们中以0,1,2,3代表人物上,下,左,右方向上的移动 15 16 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数 17 BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化 18 VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写 19 BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 20 21 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) 22 { 23 //【1】窗口创建四步曲之一:开始设计一个完整的窗口类 24 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类 25 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 26 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 27 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 28 wndClass.cbClsExtra = 0; //窗口类的附加内存,取0就可以了 29 wndClass.cbWndExtra = 0; //窗口的附加内存,依然取0就行了 30 wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 31 wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标 32 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 33 wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄 34 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 35 wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 36 37 //【2】窗口创建四步曲之二:注册窗口类 38 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 39 return -1; 40 41 //【3】窗口创建四步曲之三:正式创建窗口 42 HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow 43 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, 44 WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); 45 46 //【4】窗口创建四步曲之四:窗口的移动、显示与更新 47 MoveWindow(hwnd,250,80,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处 48 ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口 49 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 50 51 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE 52 if (!Game_Init (hwnd)) 53 { 54 MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 55 return FALSE; 56 } 57 58 //【5】消息循环过程 59 MSG msg = { 0 }; //定义并初始化msg 60 while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环 61 { 62 if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 63 { 64 TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 65 DispatchMessage( &msg ); //分发一个消息给窗口程序。 66 } 67 else 68 { 69 g_tNow = GetTickCount(); //获取当前系统时间 70 if(g_tNow-g_tPre >= 50) //当此次循环运行与上次绘图时间相差0.05秒时再进行重绘操作 71 Game_Paint(hwnd); 72 } 73 74 } 75 76 //【6】窗口类的注销 77 UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类 78 return 0; 79 } 80 81 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 82 { 83 84 switch( message ) //switch语句开始 85 { 86 87 case WM_KEYDOWN: //按下键盘消息 88 //判断按键的虚拟键码 89 switch (wParam) 90 { 91 case VK_ESCAPE: //按下【Esc】键 92 DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息 93 PostQuitMessage( 0 ); //结束程序 94 break; 95 case VK_UP: //按下【↑】键 96 //根据按键加入人物移动的量(每次按下一次按键移动10个单位),来决定人物贴图坐标的X与Y值,接着判断坐标是否超出窗口区域,若有则进行修正 97 g_iY -= 10; 98 g_iDirection = 0; 99 if(g_iY < 0) 100 g_iY = 0; 101 break; 102 case VK_DOWN: //按下【↓】键 103 g_iY += 10; 104 g_iDirection = 1; 105 if(g_iY > WINDOW_HEIGHT-135) 106 g_iY = WINDOW_HEIGHT-135; 107 break; 108 case VK_LEFT: //按下【←】键 109 g_iX -= 10; 110 g_iDirection = 2; 111 if(g_iX < 0) 112 g_iX = 0; 113 break; 114 case VK_RIGHT: //按下【→】键 115 g_iX += 10; 116 g_iDirection = 3; 117 if(g_iX > WINDOW_WIDTH-75) 118 g_iX = WINDOW_WIDTH-75; 119 break; 120 } 121 break; //跳出该switch语句 122 123 case WM_DESTROY: //若是窗口销毁消息 124 Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理 125 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 126 break; //跳出该switch语句 127 128 default: //若上述case条件都不符合,则执行该default语句 129 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程 130 } 131 132 return 0; //正常退出 133 } 134 135 //-----------------------------------【Game_Init( )函数】-------------------------------------- 136 // 描述:初始化函数,进行一些简单的初始化 137 //------------------------------------------------------------------------------------------------ 138 BOOL Game_Init( HWND hwnd ) 139 { 140 HBITMAP bmp; 141 142 g_hdc = GetDC(hwnd); 143 g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC 144 g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC 145 bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); 146 147 //设定人物贴图初始位置和移动方向 148 g_iX = 150; 149 g_iY = 350; 150 g_iDirection = 3; 151 g_iNum = 0; 152 153 SelectObject(g_mdc,bmp); 154 //加载各张跑动图及背景图,这里以0,1,2,3来代表人物的上,下,左,右移动 155 g_hSprite[0] = (HBITMAP)LoadImage(NULL,L"go1.bmp",IMAGE_BITMAP,480,216,LR_LOADFROMFILE); 156 g_hSprite[1] = (HBITMAP)LoadImage(NULL,L"go2.bmp",IMAGE_BITMAP,480,216,LR_LOADFROMFILE); 157 g_hSprite[2] = (HBITMAP)LoadImage(NULL,L"go3.bmp",IMAGE_BITMAP,480,216,LR_LOADFROMFILE); 158 g_hSprite[3] = (HBITMAP)LoadImage(NULL,L"go4.bmp",IMAGE_BITMAP,480,216,LR_LOADFROMFILE); 159 g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); 160 161 Game_Paint(hwnd); 162 return TRUE; 163 } 164 165 //-----------------------------------【Game_Paint( )函数】-------------------------------------- 166 // 描述:绘制函数,在此函数中进行绘制操作 167 //-------------------------------------------------------------------------------------------------- 168 VOID Game_Paint( HWND hwnd ) 169 { 170 //先在mdc中贴上背景图 171 SelectObject(g_bufdc,g_hBackGround); 172 BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY); 173 174 //按照目前的移动方向取出对应人物的连续走动图,并确定截取人物图的宽度与高度 175 SelectObject(g_bufdc,g_hSprite[g_iDirection]); 176 BitBlt(g_mdc,g_iX,g_iY,60,108,g_bufdc,g_iNum*60,108,SRCAND); 177 BitBlt(g_mdc,g_iX,g_iY,60,108,g_bufdc,g_iNum*60,0,SRCPAINT); 178 //将最后的画面显示在窗口中 179 BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY); 180 181 g_tPre = GetTickCount(); //记录此次绘图时间 182 g_iNum++; 183 if(g_iNum == 8) 184 g_iNum = 0; 185 186 } 187 188 //-----------------------------------【Game_CleanUp( )函数】-------------------------------- 189 // 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作 190 //--------------------------------------------------------------------------------------------------- 191 BOOL Game_CleanUp( HWND hwnd ) 192 { 193 //释放资源对象 194 DeleteObject(g_hBackGround); 195 for (int i=0;i<4;i++) 196 { 197 DeleteObject(g_hSprite[i]); 198 } 199 DeleteDC(g_bufdc); 200 DeleteDC(g_mdc); 201 ReleaseDC(hwnd,g_hdc); 202 return TRUE; 203 }
要点:
根据键盘输入的消息来改变贴图坐标,从而在下次绘制时改变贴图的位置,产生一种移动的效果。
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;
国际惯例,还是来个完整的示例程序,要实现的效果是这样的:
将第一张图贴到背景图上,用鼠标控制其移动,单击鼠标发射弹幕
1 #include <windows.h> 2 #include <tchar.h>//使用swprintf_s函数所需的头文件 3 4 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 5 6 #define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 7 #define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度 8 #define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】Windows消息处理之 鼠标消息处理 " //为窗口标题定义的宏 9 10 struct SwordBullets //SwordBullets结构体代表剑气(子弹) 11 { 12 int x,y; //剑气(子弹)坐标 13 bool exist; //剑气(子弹)是否存在 14 }; 15 16 //-----------------------------------【全局变量声明部分】------------------------------------- 17 HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄 18 HBITMAP g_hSwordMan=NULL,g_hSwordBlade=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源 19 DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间 20 int g_iX=0,g_iY=0,g_iXnow=0,g_iYnow=0; //g_iX,g_iY代表鼠标光标所在位置,g_iXnow,g_iYnow代表当前人物坐标,也就是贴图的位置 21 int g_iBGOffset=0,g_iBulletNum=0; //g_iBGOffset为滚动背景所要裁剪的区域宽度,g_iBulletNum记录剑侠现有剑气(子弹)数目 22 SwordBullets Bullet[30]; //声明一个“SwordBullets”类型的数组,用来存储剑侠发出的剑气(子弹) 23 24 25 //-----------------------------------【全局函数声明部分】------------------------------------- 26 //------------------------------------------------------------------------------------------------ 27 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数 28 BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化 29 VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写 30 BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 31 32 //-----------------------------------【WinMain( )函数】-------------------------------------- 33 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) 34 { 35 //【1】窗口创建四步曲之一:开始设计一个完整的窗口类 36 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类 37 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 38 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 39 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 40 wndClass.cbClsExtra = 0; //窗口类的附加内存,取0就可以了 41 wndClass.cbWndExtra = 0; //窗口的附加内存,依然取0就行了 42 wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 43 wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标 44 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 45 wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄 46 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 47 wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 48 49 //【2】窗口创建四步曲之二:注册窗口类 50 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 51 return -1; 52 53 //【3】窗口创建四步曲之三:正式创建窗口 54 HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow 55 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, 56 WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); 57 58 //【4】窗口创建四步曲之四:窗口的移动、显示与更新 59 MoveWindow(hwnd,250,80,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处 60 ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口 61 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 62 63 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE 64 if (!Game_Init (hwnd)) 65 { 66 MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 67 return FALSE; 68 } 69 70 //【5】消息循环过程 71 MSG msg = { 0 }; //定义并初始化msg 72 while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环 73 { 74 if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 75 { 76 TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 77 DispatchMessage( &msg ); //分发一个消息给窗口程序。 78 } 79 else 80 { 81 g_tNow = GetTickCount(); //获取当前系统时间 82 if(g_tNow-g_tPre >= 5) //当此次循环运行与上次绘图时间相差0.1秒时再进行重绘操作 83 Game_Paint(hwnd); 84 } 85 86 } 87 88 //【6】窗口类的注销 89 UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类 90 return 0; 91 } 92 93 //-----------------------------------【WndProc( )函数】-------------------------------------- 94 // 描述:窗口过程函数WndProc,对窗口消息进行处理 95 //------------------------------------------------------------------------------------------------ 96 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 97 { 98 switch( message ) //switch语句开始 99 { 100 101 case WM_KEYDOWN: //按下键盘消息 102 //判断按键的虚拟键码 103 switch (wParam) 104 { 105 case VK_ESCAPE: //按下【Esc】键 106 DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息 107 PostQuitMessage( 0 ); //结束程序 108 break; 109 } 110 111 break; 112 113 case WM_LBUTTONDOWN: //单击鼠标左键消息 114 for(int i=0;i<30;i++) 115 { 116 if(!Bullet[i].exist) 117 { 118 Bullet[i].x = g_iXnow; //剑气(子弹)x坐标 119 Bullet[i].y = g_iYnow + 30; //剑气(子弹)y坐标 120 Bullet[i].exist = true; 121 g_iBulletNum++; //累加剑气(子弹)数目 122 break; 123 } 124 } 125 126 case WM_MOUSEMOVE: //鼠标移动消息 127 //对X坐标的处理 128 g_iX = LOWORD(lParam); //取得鼠标X坐标 129 if(g_iX > WINDOW_WIDTH-317) //设置临界坐标 130 g_iX = WINDOW_WIDTH-317; 131 else if(g_iX < 0) 132 g_iX = 0; 133 //对Y坐标的处理 134 g_iY = HIWORD(lParam); //取得鼠标Y坐标 135 if(g_iY > WINDOW_HEIGHT-283) 136 g_iY = WINDOW_HEIGHT-283; 137 else if(g_iY < -200) 138 g_iY = -200; 139 break; 140 141 case WM_DESTROY: //若是窗口销毁消息 142 Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理 143 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 144 break; //跳出该switch语句 145 146 default: //若上述case条件都不符合,则执行该default语句 147 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程 148 } 149 return 0; //正常退出 150 } 151 152 //-----------------------------------【Game_Init( )函数】-------------------------------------- 153 // 描述:初始化函数,进行一些简单的初始化 154 //------------------------------------------------------------------------------------------------ 155 BOOL Game_Init( HWND hwnd ) 156 { 157 HBITMAP bmp; 158 159 g_hdc = GetDC(hwnd); 160 g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC 161 g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC 162 bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); 163 164 //设定人物贴图初始值,鼠标位置初始值 165 g_iX = 300; 166 g_iY = 100; 167 g_iXnow = 300; 168 g_iYnow = 100; 169 170 SelectObject(g_mdc,bmp); 171 //加载各张跑动图及背景图 172 g_hSwordMan = (HBITMAP)LoadImage(NULL,L"swordman.bmp",IMAGE_BITMAP,317,283,LR_LOADFROMFILE); 173 g_hSwordBlade = (HBITMAP)LoadImage(NULL,L"swordblade.bmp",IMAGE_BITMAP,100,26,LR_LOADFROMFILE); 174 g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); 175 176 POINT pt,lt,rb; 177 RECT rect; 178 //设定光标位置 179 pt.x = 300; 180 pt.y = 100; 181 ClientToScreen(hwnd,&pt); 182 SetCursorPos(pt.x,pt.y); 183 184 ShowCursor(false); //隐藏鼠标光标 185 186 //限制鼠标光标移动区域 187 GetClientRect(hwnd,&rect); //取得窗口内部矩形 188 //将矩形左上点坐标存入lt中 189 lt.x = rect.left; 190 lt.y = rect.top; 191 //将矩形右下坐标存入rb中 192 rb.x = rect.right; 193 rb.y = rect.bottom; 194 //将lt和rb的窗口坐标转换为屏幕坐标 195 ClientToScreen(hwnd,<); 196 ClientToScreen(hwnd,&rb); 197 //以屏幕坐标重新设定矩形区域 198 rect.left = lt.x; 199 rect.top = lt.y; 200 rect.right = rb.x; 201 rect.bottom = rb.y; 202 //限制鼠标光标移动区域 203 ClipCursor(&rect); 204 205 Game_Paint(hwnd); 206 return TRUE; 207 } 208 209 //-----------------------------------【Game_Paint( )函数】-------------------------------------- 210 // 描述:绘制函数,在此函数中进行绘制操作 211 //-------------------------------------------------------------------------------------------------- 212 VOID Game_Paint( HWND hwnd ) 213 { 214 //先在mdc中贴上背景图 215 SelectObject(g_bufdc,g_hBackGround); 216 BitBlt(g_mdc,0,0,g_iBGOffset,WINDOW_HEIGHT,g_bufdc,WINDOW_WIDTH-g_iBGOffset,0,SRCCOPY); 217 BitBlt(g_mdc,g_iBGOffset,0,WINDOW_WIDTH-g_iBGOffset,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY); 218 219 wchar_t str[20] = {}; 220 221 //计算剑侠的贴图坐标,设定每次进行剑侠贴图时,其贴图坐标(g_iXnow,g_iYnow)会以10个单位慢慢向鼠标光标所在的目的点(x,y)接近,直到两个坐标相同为止 222 if(g_iXnow < g_iX)//若当前贴图X坐标小于鼠标光标的X坐标 223 { 224 g_iXnow += 10; 225 if(g_iXnow > g_iX) 226 g_iXnow = g_iX; 227 } 228 else //若当前贴图X坐标大于鼠标光标的X坐标 229 { 230 g_iXnow -=10; 231 if(g_iXnow < g_iX) 232 g_iXnow = g_iX; 233 } 234 235 if(g_iYnow < g_iY) //若当前贴图Y坐标小于鼠标光标的Y坐标 236 { 237 g_iYnow += 10; 238 if(g_iYnow > g_iY) 239 g_iYnow = g_iY; 240 } 241 else //若当前贴图Y坐标大于于鼠标光标的Y坐标 242 { 243 g_iYnow -= 10; 244 if(g_iYnow < g_iY) 245 g_iYnow = g_iY; 246 } 247 248 //贴上剑侠图 249 SelectObject(g_bufdc,g_hSwordMan); 250 TransparentBlt(g_mdc,g_iXnow,g_iYnow,317,283,g_bufdc,0,0,317,283,RGB(0,0,0)); 251 252 //剑气(子弹)的贴图,先判断剑气(子弹)数目“g_iBulletNum”的值是否为“0”。若不为0,则对剑气(子弹)数组中各个还存在的剑气(子弹)按照其所在的坐标(b[i].x,b[i].y)循环进行贴图操作 253 SelectObject(g_bufdc,g_hSwordBlade); 254 if(g_iBulletNum!=0) 255 for(int i=0;i<30;i++) 256 if(Bullet[i].exist) 257 { 258 //贴上剑气(子弹)图 259 TransparentBlt(g_mdc,Bullet[i].x-70,Bullet[i].y+100,100,33,g_bufdc,0,0,100,26,RGB(0,0,0)); 260 261 //设置下一个剑气(子弹)的坐标。剑气(子弹)是从右向左发射的,因此,每次其X轴上的坐标值递减10个单位,这样贴图会产生往左移动的效果。而如果剑气(子弹)下次的坐标已超出窗口的可见范围(h[i].x<0),那么剑气(子弹)设为不存在,并将剑气(子弹)总数g_iBulletNum变量值减1. 262 Bullet[i].x -= 10; 263 if(Bullet[i].x < 0) 264 { 265 g_iBulletNum--; 266 Bullet[i].exist = false; 267 } 268 } 269 270 HFONT hFont; 271 hFont=CreateFont(20,0,0,0,0,0,0,0,GB2312_CHARSET,0,0,0,0,TEXT("微软雅黑")); //创建字体 272 SelectObject(g_mdc,hFont); //选入字体到g_mdc中 273 SetBkMode(g_mdc, TRANSPARENT); //设置文字背景透明 274 SetTextColor(g_mdc,RGB(255,255,0)); //设置文字颜色 275 276 //在左上角进行文字输出 277 swprintf_s(str,L"鼠标X坐标为%d ",g_iX); 278 TextOut(g_mdc,0,0,str,wcslen(str)); 279 swprintf_s(str,L"鼠标Y坐标为%d ",g_iY); 280 TextOut(g_mdc,0,20,str,wcslen(str)); 281 282 //贴上背景图 283 BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY); 284 285 g_tPre = GetTickCount(); 286 287 g_iBGOffset += 5; //让背景滚动量+5 288 if(g_iBGOffset==WINDOW_WIDTH)//如果背景滚动量达到了背景宽度值,就置零 289 g_iBGOffset = 0; 290 } 291 292 //-----------------------------------【Game_CleanUp( )函数】-------------------------------- 293 BOOL Game_CleanUp( HWND hwnd ) 294 { 295 //释放资源对象 296 DeleteObject(g_hBackGround); 297 DeleteDC(g_bufdc); 298 DeleteDC(g_mdc); 299 ReleaseDC(hwnd,g_hdc); 300 return TRUE; 301 }
第8章 物理建模与粒子系统初步
关于物理建模这块,并没有涉及到新的函数的使用,主要是利用物理和数学知识来模拟匀速,加速运动,重力系统和摩擦力系统,这里要好好读读作者的源码才行。
使用结构体来定义粒子,如下面这个结构体snow便是用来定义“雪花”粒子的:
struct snow { int x; //雪花的x坐标 int y; //雪花的Y坐标 BOOL exist; //雪花是否存在 }
定义完粒子的结构体后,便可以实例化一个粒子数组,可以用如下两种方法来进行:
在结构体声明时的尾部加上我们需要实例化的对象:
struct snow { int x; //雪花的x坐标 int y; //雪花的Y坐标 BOOL exist; //雪花是否存在 } SnowFlowers [100];
或者在完成结构体定义后,再单独进行定义:
snow SnowFlowers [100]
雪花飞舞示例程序:
1 #include <windows.h> 2 #include <tchar.h>//使用swprintf_s函数所需的头文件 3 #include <time.h> //使用获取系统时间time()函数需要包含的头文件 4 5 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 6 7 //-----------------------------------【宏定义部分】-------------------------------------------- 8 #define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 9 #define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度 10 #define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 雪花飞舞demo" //为窗口标题定义的宏 11 #define PARTICLE_NUMBER 80 //表示粒子数量的宏,以方便修改粒子数量 12 13 //-----------------------------------【全局结构体定义部分】------------------------------------- 14 struct SNOW 15 { 16 int x; //雪花的 X坐标 17 int y; //雪花的Y坐标 18 BOOL exist; //雪花是否存在 19 }; 20 21 22 //-----------------------------------【全局变量声明部分】------------------------------------- 23 HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄 24 HBITMAP g_hSnow=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源 25 DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间 26 RECT g_rect; //定义一个RECT结构体,用于储存内部窗口区域的坐标 27 SNOW SnowFlowers[PARTICLE_NUMBER]; //雪花粒子数组 28 int g_SnowNum=0; //定义g_SnowNum用于计数 29 30 //-----------------------------------【全局函数声明部分】------------------------------------- 31 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数 32 BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化 33 VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写 34 BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 35 36 //-----------------------------------【WinMain( )函数】-------------------------------------- 37 // 描述:Windows应用程序的入口函数,我们的程序从这里开始 38 //------------------------------------------------------------------------------------------------ 39 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) 40 { 41 //【1】窗口创建四步曲之一:开始设计一个完整的窗口类 42 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类 43 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 44 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 45 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 46 wndClass.cbClsExtra = 0; //窗口类的附加内存,取0就可以了 47 wndClass.cbWndExtra = 0; //窗口的附加内存,依然取0就行了 48 wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 49 wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标 50 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 51 wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄 52 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 53 wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 54 55 //【2】窗口创建四步曲之二:注册窗口类 56 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 57 return -1; 58 59 //【3】窗口创建四步曲之三:正式创建窗口 60 HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow 61 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, 62 WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); 63 64 //【4】窗口创建四步曲之四:窗口的移动、显示与更新 65 MoveWindow(hwnd,250,80,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处 66 ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口 67 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 68 69 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE 70 if (!Game_Init (hwnd)) 71 { 72 MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 73 return FALSE; 74 } 75 76 //【5】消息循环过程 77 MSG msg = { 0 }; //定义并初始化msg 78 while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环 79 { 80 if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 81 { 82 TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 83 DispatchMessage( &msg ); //分发一个消息给窗口程序。 84 } 85 else 86 { 87 g_tNow = GetTickCount(); //获取当前系统时间 88 if(g_tNow-g_tPre >= 60) //当此次循环运行与上次绘图时间相差0.06秒时再进行重绘操作 89 Game_Paint(hwnd); 90 } 91 92 } 93 94 //【6】窗口类的注销 95 UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类 96 return 0; 97 } 98 99 //-----------------------------------【WndProc( )函数】-------------------------------------- 100 // 描述:窗口过程函数WndProc,对窗口消息进行处理 101 //------------------------------------------------------------------------------------------------ 102 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 103 { 104 105 switch( message ) //switch语句开始 106 { 107 case WM_KEYDOWN: //按键消息 108 if(wParam==VK_ESCAPE) //按下【Esc】键 109 PostQuitMessage(0); 110 break; 111 112 case WM_DESTROY: //若是窗口销毁消息 113 Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理 114 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 115 break; //跳出该switch语句 116 117 default: //若上述case条件都不符合,则执行该default语句 118 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程 119 } 120 121 return 0; //正常退出 122 } 123 124 //-----------------------------------【Game_Init( )函数】-------------------------------------- 125 // 描述:初始化函数,进行一些简单的初始化 126 //------------------------------------------------------------------------------------------------ 127 BOOL Game_Init( HWND hwnd ) 128 { 129 srand((unsigned)time(NULL)); //用系统时间初始化随机种子 130 131 HBITMAP bmp; 132 133 g_hdc = GetDC(hwnd); 134 g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC 135 g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC 136 bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象 137 138 SelectObject(g_mdc,bmp);//将空位图对象放到g_mdc中 139 140 //载入位图资源 141 g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); 142 g_hSnow = (HBITMAP)LoadImage(NULL,L"snow.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE); 143 144 GetClientRect(hwnd,&g_rect); //取得内部窗口区域的大小 145 146 Game_Paint(hwnd); 147 return TRUE; 148 } 149 150 //-----------------------------------【Game_Paint( )函数】-------------------------------------- 151 // 描述:绘制函数,在此函数中进行绘制操作 152 //-------------------------------------------------------------------------------------------------- 153 VOID Game_Paint( HWND hwnd ) 154 { 155 156 //先在mdc中贴上背景图 157 SelectObject(g_bufdc,g_hBackGround); 158 BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY); 159 160 //创建粒子 161 if(g_SnowNum< PARTICLE_NUMBER) //当粒子数小于规定的粒子数时,便产生新的粒子,设定每个粒子的属性值 162 { 163 SnowFlowers[g_SnowNum].x = rand()%g_rect.right; //将粒子的X坐标设为窗口中水平方向上的任意位置 164 SnowFlowers[g_SnowNum].y = 0; //将每个粒子的Y坐标都设为"0",即从窗口上沿往下落 165 SnowFlowers[g_SnowNum].exist = true; //设定粒子存在 166 g_SnowNum++; //每产生一个粒子后进行累加计数 167 } 168 169 170 //首先判断粒子是否存在,若存在,进行透明贴图操作 171 for(int i=0;i<PARTICLE_NUMBER;i++) 172 { 173 if(SnowFlowers[i].exist) //粒子还存在 174 { 175 //贴上粒子图 176 SelectObject(g_bufdc,g_hSnow); 177 TransparentBlt(g_mdc,SnowFlowers[i].x,SnowFlowers[i].y,30,30,g_bufdc,0,0,30,30,RGB(0,0,0)); 178 179 //随机决定横向的移动方向和偏移量 180 if(rand()%2==0) 181 SnowFlowers[i].x+=rand()%6; //x坐标加上0~5之间的一个随机值 182 else 183 SnowFlowers[i].x-=rand()%6; //y坐标加上0~5之间的一个随机值 184 //纵方向上做匀速运动 185 SnowFlowers[i].y+=10; //纵坐标加上10 186 //如果粒子坐标超出了窗口长度,就让它以随机的x坐标出现在窗口顶部 187 if(SnowFlowers[i].y > g_rect.bottom) 188 { 189 SnowFlowers[i].x = rand()%g_rect.right; 190 SnowFlowers[i].y = 0; 191 } 192 } 193 194 } 195 //将mdc中的全部内容贴到hdc中 196 BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY); 197 198 g_tPre = GetTickCount(); //记录此次绘图时间 199 } 200 201 //-----------------------------------【Game_CleanUp( )函数】-------------------------------- 202 // 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作 203 //--------------------------------------------------------------------------------------------------- 204 BOOL Game_CleanUp( HWND hwnd ) 205 { 206 //释放资源对象 207 DeleteObject(g_hBackGround); 208 DeleteObject(g_hSnow); 209 DeleteDC(g_bufdc); 210 DeleteDC(g_mdc); 211 ReleaseDC(hwnd,g_hdc); 212 return TRUE; 213 }
星光绽放示例程序:
模拟一个爆炸特效,爆炸点由随机数产生一个位置,爆炸后会出现很多星光以不同的速度向四方飞散,当粒子飞出窗口或者超出时间后便会消失。首先用结构体来构造出星光粒子:
struct FLYSTAR { int x; //星光所在的x坐标 int y; //y坐标 int vx; //星光x方向的速度 int vy; //y方向速度 int lasted; //星光存在的时间 BOOL exist; //星光是否存在 };
完整程序:
1 #include <windows.h> 2 #include <tchar.h>//使用swprintf_s函数所需的头文件 3 #include <time.h> //使用获取系统时间time函数需要包含的头文件 4 5 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 6 7 #define WINDOW_WIDTH 932 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 8 #define WINDOW_HEIGHT 700 //为窗口高度定义的宏,以方便在此处修改窗口高度 9 #define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 星光绽放demo" //为窗口标题定义的宏 10 #define FLYSTAR_NUMBER 100 //表示粒子数量的宏,以方便修改粒子数量 11 #define FLYSTAR_LASTED_FRAME 60 //表示粒子持续帧数的宏,以方便修改每次星光绽放持续的时间 12 13 struct FLYSTAR 14 { 15 int x; //星光所在的x坐标 16 int y; //星光所在的y坐标 17 int vx; //星光x方向的速度 18 int vy; //星光y方向的速度 19 int lasted; //星光存在的时间 20 BOOL exist; //星光是否存在 21 }; 22 23 HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄 24 HBITMAP g_hStar=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源 25 DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间 26 RECT g_rect; //定义一个RECT结构体,用于储存内部窗口区域的坐标 27 FLYSTAR FlyStars[FLYSTAR_NUMBER]; //粒子数组 28 int g_StarNum=0; //定义g_StarNum用于计数 29 30 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数 31 BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化 32 VOID Game_Paint( HWND hwnd); //在此函数中进行绘图代码的书写 33 BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 34 35 //-----------------------------------【WinMain( )函数】-------------------------------------- 36 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) 37 { 38 //【1】窗口创建四步曲之一:开始设计一个完整的窗口类 39 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类 40 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 41 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 42 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 43 wndClass.cbClsExtra = 0; //窗口类的附加内存,取0就可以了 44 wndClass.cbWndExtra = 0; //窗口的附加内存,依然取0就行了 45 wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 46 wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标 47 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 48 wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄 49 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 50 wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 51 52 //【2】窗口创建四步曲之二:注册窗口类 53 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 54 return -1; 55 56 //【3】窗口创建四步曲之三:正式创建窗口 57 HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow 58 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, 59 WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); 60 61 //【4】窗口创建四步曲之四:窗口的移动、显示与更新 62 MoveWindow(hwnd,150,20,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(150,20)处 63 ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口 64 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 65 66 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE 67 if (!Game_Init (hwnd)) 68 { 69 MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 70 return FALSE; 71 } 72 73 //【5】消息循环过程 74 MSG msg = { 0 }; //定义并初始化msg 75 while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环 76 { 77 if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 78 { 79 TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 80 DispatchMessage( &msg ); //分发一个消息给窗口程序。 81 } 82 else 83 { 84 g_tNow = GetTickCount(); //获取当前系统时间 85 if(g_tNow-g_tPre >= 30) //当此次循环运行与上次绘图时间相差0.03秒时再进行重绘操作 86 Game_Paint(hwnd); 87 } 88 89 } 90 91 //【6】窗口类的注销 92 UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类 93 return 0; 94 } 95 96 //-----------------------------------【WndProc( )函数】-------------------------------------- 97 // 描述:窗口过程函数WndProc,对窗口消息进行处理 98 //------------------------------------------------------------------------------------------------ 99 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 100 { 101 102 switch( message ) //switch语句开始 103 { 104 case WM_KEYDOWN: //按键消息 105 if(wParam==VK_ESCAPE) //按下【Esc】键 106 PostQuitMessage(0); 107 break; 108 109 case WM_DESTROY: //若是窗口销毁消息 110 Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理 111 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 112 break; //跳出该switch语句 113 114 default: //若上述case条件都不符合,则执行该default语句 115 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程 116 } 117 118 return 0; //正常退出 119 } 120 121 //-----------------------------------【Game_Init( )函数】-------------------------------------- 122 // 描述:初始化函数,进行一些简单的初始化 123 //------------------------------------------------------------------------------------------------ 124 BOOL Game_Init( HWND hwnd ) 125 { 126 127 srand((unsigned)time(NULL)); //用系统时间初始化随机种子 128 HBITMAP bmp; 129 130 g_hdc = GetDC(hwnd); 131 g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC 132 g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC 133 bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象 134 135 SelectObject(g_mdc,bmp);//将空位图对象放到g_mdc中 136 137 //载入位图背景 138 g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE); 139 g_hStar = (HBITMAP)LoadImage(NULL,L"star.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE); 140 141 GetClientRect(hwnd,&g_rect); //取得内部窗口区域的大小 142 143 Game_Paint(hwnd); 144 return TRUE; 145 } 146 147 //-----------------------------------【Game_Paint( )函数】-------------------------------------- 148 // 描述:绘制函数,在此函数中进行绘制操作 149 //-------------------------------------------------------------------------------------------------- 150 VOID Game_Paint( HWND hwnd ) 151 { 152 153 //先在mdc中贴上背景图 154 SelectObject(g_bufdc,g_hBackGround); 155 BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY); 156 157 //创建粒子 158 if(g_StarNum == 0) //随机设置爆炸点 159 { 160 int x=rand()%g_rect.right; 161 int y=rand()%g_rect.bottom; 162 163 for(int i=0;i<FLYSTAR_NUMBER;i++) //产生星光粒子 164 { 165 FlyStars[i].x = x; 166 FlyStars[i].y = y; 167 FlyStars[i].lasted = 0; //设定该粒子存在的时间为零 168 //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。 169 if(i%4==0) 170 { 171 FlyStars[i].vx = -(1+rand()%15); 172 FlyStars[i].vy = -(1+rand()%15); 173 } 174 if(i%4==1) 175 { 176 FlyStars[i].vx = 1+rand()%15; 177 FlyStars[i].vy = 1+rand()%15; 178 } 179 if(i%4==2) 180 { 181 FlyStars[i].vx = -(1+rand()%15); 182 FlyStars[i].vy = 1+rand()%15; 183 } 184 if(i%4==3) 185 { 186 FlyStars[i].vx = 1+rand()%15; 187 FlyStars[i].vy = -(1+rand()%15); 188 } 189 FlyStars[i].exist = true; //设定粒子存在 190 } 191 g_StarNum = FLYSTAR_NUMBER; //全部粒子由for循环设置完成后,我们将粒子数量设为FLYSTAR_NUMBER,代表目前有FLYSTAR_NUMBER颗星光 192 } 193 194 195 //显示粒子并更新下一帧的粒子坐标 196 for(int i=0;i<FLYSTAR_NUMBER;i++) 197 { 198 if(FlyStars[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(FlyStars[i].x,FlyStars[i].y)进行贴图操作 199 { 200 SelectObject(g_bufdc,g_hStar); 201 TransparentBlt(g_mdc,FlyStars[i].x,FlyStars[i].y,30,30,g_bufdc,0,0,30,30,RGB(0,0,0)); 202 203 //计算下一次贴图的坐标 204 FlyStars[i].x+=FlyStars[i].vx; 205 FlyStars[i].y+=FlyStars[i].vy; 206 207 //在每进行一次贴图后,将粒子的存在时间累加1. 208 FlyStars[i].lasted++; 209 //进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减 210 if(FlyStars[i].x<=-10 || FlyStars[i].x>g_rect.right || FlyStars[i].y<=-10 || FlyStars[i].y>g_rect.bottom || FlyStars[i].lasted>FLYSTAR_LASTED_FRAME) 211 { 212 FlyStars[i].exist = false; //删除星光粒子 213 g_StarNum--; //递减星光总数 214 } 215 } 216 } 217 218 //将mdc中的全部内容贴到hdc中 219 BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY); 220 221 g_tPre = GetTickCount(); //记录此次绘图时间 222 } 223 224 //-----------------------------------【Game_CleanUp( )函数】-------------------------------- 225 // 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作 226 //--------------------------------------------------------------------------------------------------- 227 BOOL Game_CleanUp( HWND hwnd ) 228 { 229 //释放资源对象 230 DeleteObject(g_hBackGround); 231 DeleteObject(g_hStar); 232 DeleteDC(g_bufdc); 233 DeleteDC(g_mdc); 234 ReleaseDC(hwnd,g_hdc); 235 return TRUE; 236 }
参考博文:
【Visual C++】游戏开发笔记十二 游戏输入消息处理(一) 键盘消息处理
【Visual C++】游戏开发笔记十三 游戏输入消息处理(二) 鼠标消息处理
【Visual C++】游戏开发笔记十八 游戏基础物理建模(一) 匀速与加速运动
【Visual C++】游戏开发笔记二十 游戏基础物理建模(二) 重力系统的模拟
【Visual C++】游戏开发笔记二十一 游戏基础物理建模(三) 摩擦力系统模拟