《逐梦旅程 WINDOWS游戏编程之从零开始》笔记3——输入消息处理,物理建模与粒子系统初步

第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 }
View Code

要点:

根据键盘输入的消息来改变贴图坐标,从而在下次绘制时改变贴图的位置,产生一种移动的效果。

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,&lt);
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,&lt);
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 }
View Code

第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 }
View Code

星光绽放示例程序:

模拟一个爆炸特效,爆炸点由随机数产生一个位置,爆炸后会出现很多星光以不同的速度向四方飞散,当粒子飞出窗口或者超出时间后便会消失。首先用结构体来构造出星光粒子:

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 }
View Code 

参考博文:

【Visual C++】游戏开发笔记十二 游戏输入消息处理(一) 键盘消息处理

【Visual C++】游戏开发笔记十三 游戏输入消息处理(二) 鼠标消息处理

【Visual C++】游戏开发笔记十八 游戏基础物理建模(一) 匀速与加速运动

【Visual C++】游戏开发笔记二十 游戏基础物理建模(二) 重力系统的模拟

【Visual C++】游戏开发笔记二十一 游戏基础物理建模(三) 摩擦力系统模拟

【Visual C++】游戏开发笔记二十二 游戏基础物理建模(四) 粒子系统模拟(一)

【Visual C++】游戏开发笔记二十三 游戏基础物理建模(五) 粒子系统模拟(二)

原文地址:https://www.cnblogs.com/f91og/p/7162074.html