zoukankan      html  css  js  c++  java
  • 《逐梦旅程 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++】游戏开发笔记二十三 游戏基础物理建模(五) 粒子系统模拟(二)

  • 相关阅读:
    第一次项目总结
    动画animation
    动画基本
    JQ属性和CSS
    JQ选择器
    关于JS的循环和函数,由入门到放弃
    Js知识点
    课程总结
    移动端开发--项目总总结
    项目总结
  • 原文地址:https://www.cnblogs.com/f91og/p/7162074.html
Copyright © 2011-2022 走看看