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

来源:岁月联盟 编辑:exp 时间:2012-07-02

 


一.基础知识讲解

摩擦力是两个表面接触的物体相互运动时互相施加的一种物理力。广义地物体在液体和气体中运动时也受到摩擦力。摩擦力可谓无处不在,为了模拟出与现实生活相符的游戏场景,游戏或者游戏引擎中,用相关代码实现摩擦力的真实效果是十分必要的。

任何一款完善的物理引擎,都有模拟摩擦力系统的相关代码。如大名鼎鼎的havok物理引擎(暗黑破坏神3所采用的物理引擎),也如最近在移动平台上很热门的Box2d物理引擎。

上一节范例中并没有考虑小鸟下坠与弹跳时的摩擦力影响效果,这节笔记里面我们将其考虑其中,加入了使小鸟运动速度减慢的负向加速度,但忽略小鸟与空气之间的阻力。下面看看如何实现这样一个比较符合真实状况的小鸟下落与弹跳效果:


[cpp]
int     x=0,y=100,vx=10,vy=0;//初始横坐标x=0,初始纵坐标y=100,初始水平方向速度vx=10,初始竖直方向速度vy=0)    
                                
int     gy=2,fx=-1,fy=-4; //重力加速度gy=2,x方向摩擦力为-1,f方向摩擦力为-4  
 
 
x += vx;            //计算X轴方向贴图坐标,每调用一次MyPiant(),x坐标就加上一个vx的当前值  
 
    vy = vy + gy;        //计算Y轴方向速度分量,vy随着每一次MyPiant()函数的调用就加上一个gy(重力加速度)  
    y += vy;             //计算Y轴方向贴图坐标,每调用一次MyPiant(),y坐标就加上一个刚改变过后的vy,相当于加速运动  
 
    if(y >= rect.bottom-70)          //判断是否触地,如果触碰到窗口边界,进行调整   
    { 
        y = rect.bottom - 70; 
 
    //X轴方向的摩擦力处理  
        vx += fx;       //  vx=fx+vx;这里fx为负值,所以每调用一次MyPiant(),vx恒定减小一个fx          
        if(vx < 0)      //当vx值递减到小于0时,就将其设为0,即小球在X方向不再移动。  
            vx = 0; 
 
        //Y轴方向摩擦力处理  
        vy += fy;  //vy=fy+vy;这里fy同样为负值,所以每调用一次MyPiant(),vy恒定减小一个fy  
        if(vy < 0)  //若速度减到小于等于0,置为零,即小球在Y方向不再移动。  
            vy = 0; 
 
        vy = -vy; 
int  x=0,y=100,vx=10,vy=0;//初始横坐标x=0,初始纵坐标y=100,初始水平方向速度vx=10,初始竖直方向速度vy=0) 
                              
int  gy=2,fx=-1,fy=-4; //重力加速度gy=2,x方向摩擦力为-1,f方向摩擦力为-4


x += vx;   //计算X轴方向贴图坐标,每调用一次MyPiant(),x坐标就加上一个vx的当前值

 vy = vy + gy;   //计算Y轴方向速度分量,vy随着每一次MyPiant()函数的调用就加上一个gy(重力加速度)
 y += vy;    //计算Y轴方向贴图坐标,每调用一次MyPiant(),y坐标就加上一个刚改变过后的vy,相当于加速运动

 if(y >= rect.bottom-70)   //判断是否触地,如果触碰到窗口边界,进行调整
 {
  y = rect.bottom - 70;

 //X轴方向的摩擦力处理
  vx += fx;  // vx=fx+vx;这里fx为负值,所以每调用一次MyPiant(),vx恒定减小一个fx  
  if(vx < 0)      //当vx值递减到小于0时,就将其设为0,即小球在X方向不再移动。
   vx = 0;

  //Y轴方向摩擦力处理
  vy += fy;  //vy=fy+vy;这里fy同样为负值,所以每调用一次MyPiant(),vy恒定减小一个fy
  if(vy < 0)  //若速度减到小于等于0,置为零,即小球在Y方向不再移动。
   vy = 0;

  vy = -vy;

这段代码里面小鸟由高出下落触及地面进行反弹,且存在落地时的摩擦力,使得小鸟在落地弹跳后速度减慢,最后呈现静止的状态,停在窗口边缘。

二.神级游戏作品《暗黑破坏神Ⅲ》的介绍


既然已经提到了这款革命性的作品,我们就来介绍一下,即将在2012年5月15日全球上市的神级作品——《暗黑破坏神Ⅲ》

(DIABLO Ⅲ)(浅墨习惯称作大菠萝3,呵呵)。

当然《暗黑破坏神Ⅲ》是由我们无所不能的C++来开发的~

浅墨在这里不打算花大篇幅介绍,就简明扼要地提一下吧。

先贴一张暗黑破坏神Ⅲ的封面美图:

/

暗黑Ⅲ用C++编程语言开发(C++由于其执行的高效性,一直是中大型游戏开发的统治性编程语言),采用暴雪自家定制的

3D图形引擎,物理引擎采用Havok,于PC和Mac两个平台发售,并暂时不会登陆XBOX 360及PlayStation等家用机平台。

《暗黑破坏神3》对于DirectX 11进行了特殊的优化和加速,官方推荐使用Windows Vista或者Windows 7来进行游戏以便获

得更好的体验效果。

正所谓“暴雪出品,必属精品”,昨天浅墨偶尔看了下暗黑3五种职业的技能介绍视频,看完后热血沸腾,暴雪真的是没有辜


负广大暗黑粉丝这么多年的等待,用心做出了这么一款任何溢美之词都无法形容其精美的天赐之作。


暗黑Ⅲ有诸多新的亮点,让暗黑系列的动作角色扮演游戏体验达到一个新的高度,浅墨在这里列一些出来:  


★五种强大的职业可供选择,包括野蛮人、巫医、法师、武僧以及恶魔猎手   


★全新3D图像引擎将带来无与伦比的视觉效果,Havok物理引擎技术  


★ Sanctuary世界变幻莫测的室内外场景   


★极具互动性的游戏环境,危险的陷阱与障碍,可被破坏的元素   


★随机产生的事件与场景,带来无止境的动态游戏体验   


★种类繁多的具有独特攻击方式和习性的恶魔与怪物   


★全新任务系统加上角色定制选项,带来终极动作RPG体验   


★可通过BN进行多人联网合作或对战


 这张图是其中四种职业的合照(原本五职业),目测少了“恶魔猎手”这一帅气的职业:

 

/

3.详细注释的源代码欣赏


好了回到我们的主题摩擦力吧,为了演示的方便直观,我们选择将笔记二十源码的一些内容进行更改,在其基础上模拟实现出摩擦力系统的效果。

依旧是国际惯例,贴出详细注释的源代码:

[cpp]
#include "stdafx.h"   
#include <stdio.h>  
 
 
//全局变量  
HINSTANCE hInst; 
HBITMAP bg,angrybird; 
HDC     hdc,mdc,bufdc; 
HWND    hWnd; 
DWORD   tPre,tNow,tCheck; 
RECT    rect; 
int     x=0,y=100,vx=10,vy=0;//初始横坐标x=0,初始纵坐标y=100,初始水平方向速度vx=10,初始竖直方向速度vy=0)    
                                
int     gy=2,fx=-1,fy=-4; //重力加速度gy=2,x方向摩擦力为-1,f方向摩擦力为-4  
 
//全局函数?  
ATOM                MyRegisterClass(HINSTANCE hInstance); 
BOOL                InitInstance(HINSTANCE, int); 
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM); 
void                MyPaint(HDC hdc); 
 
//****WinMain函数,程序入口点函数**************************************   *  
int APIENTRY WinMain(HINSTANCE hInstance, 
                     HINSTANCE hPrevInstance, 
                     LPSTR     lpCmdLine, 
                     int       nCmdShow) 

    MSG msg; 
 
    MyRegisterClass(hInstance); 
 
    //初始化  
    if (!InitInstance (hInstance, nCmdShow))  
    { 
        return FALSE; 
    } 
 
    //消息循环  
    GetMessage(&msg,NULL,NULL,NULL);  //初始化msg     
    while( msg.message!=WM_QUIT ) 
    { 
        if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) ) 
        { 
            TranslateMessage( &msg ); 
            DispatchMessage( &msg ); 
        } 
        else 
        { 
            tNow = GetTickCount(); 
            if(tNow-tPre >= 50) 
                MyPaint(hdc); 
        } 
    } 
 
    return msg.wParam; 

 
//****设计一个窗口类,类似填空题,使用窗口结构体*********************    
ATOM MyRegisterClass(HINSTANCE hInstance) 

    WNDCLASSEX wcex; 
 
    wcex.cbSize = sizeof(WNDCLASSEX);  
    wcex.style          = CS_HREDRAW | CS_VREDRAW; 
    wcex.lpfnWndProc    = (WNDPROC)WndProc; 
    wcex.cbClsExtra     = 0; 
    wcex.cbWndExtra     = 0; 
    wcex.hInstance      = hInstance; 
    wcex.hIcon          = NULL; 
    wcex.hCursor        = NULL; 
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW); 
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1); 
    wcex.lpszMenuName   = NULL; 
    wcex.lpszClassName  = "maple"; 
    wcex.hIconSm        = NULL; 
 
    return RegisterClassEx(&wcex); 

 
//****初始化函数*************************************      
// 1.加载位图资源    
// 2.取得内部窗口区域信息      
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 

    HBITMAP bmp; 
    hInst = hInstance; 
 
    hWnd = CreateWindow("maple", "浅墨的窗口" , WS_OVERLAPPEDWINDOW, 
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); 
 
    if (!hWnd) 
    { 
        return FALSE; 
    } 
 
    MoveWindow(hWnd,10,10,750,420,true); 
    ShowWindow(hWnd, nCmdShow); 
    UpdateWindow(hWnd); 
 
    hdc = GetDC(hWnd); 
    mdc = CreateCompatibleDC(hdc); 
    bufdc = CreateCompatibleDC(hdc); 
    bmp = CreateCompatibleBitmap(hdc,750,400); 
 
    SelectObject(mdc,bmp); 
 
    bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,750,400,LR_LOADFROMFILE); 
    angrybird = (HBITMAP)LoadImage(NULL,"angrybird.bmp",IMAGE_BITMAP,140,70,LR_LOADFROMFILE); 
 
     
    GetClientRect(hWnd,&rect); 
 
    MyPaint(hdc); 
 
    return TRUE; 

 
//****自定义绘图函数*********************************  
// 1.窗口贴图  
// 2.根据小球的运动状态计算速度与贴图坐标  
void MyPaint(HDC hdc) 

    SelectObject(bufdc,bg); 
    BitBlt(mdc,0,0,750,400,bufdc,0,0,SRCCOPY); 
 
    SelectObject(bufdc,angrybird); 
    BitBlt(mdc,x,y,70,70,bufdc,70,0,SRCAND); 
    BitBlt(mdc,x,y,70,70,bufdc,0,0,SRCPAINT); 
 
    BitBlt(hdc,0,0,750,400,mdc,0,0,SRCCOPY); 
 
    x += vx;            //计算X轴方向贴图坐标,每调用一次MyPiant(),x坐标就加上一个vx的当前值  
 
    vy = vy + gy;        //计算Y轴方向速度分量,vy随着每一次MyPiant()函数的调用就加上一个gy(重力加速度)  
    y += vy;             //计算Y轴方向贴图坐标,每调用一次MyPiant(),y坐标就加上一个刚改变过后的vy,相当于加速运动  
 
    if(y >= rect.bottom-70)          //判断是否触地,如果触碰到窗口边界,进行调整   
    { 
        y = rect.bottom - 70; 
 
    //X轴方向的摩擦力处理  
        vx += fx;       //  vx=fx+vx;这里fx为负值,所以每调用一次MyPiant(),vx恒定减小一个fx          
        if(vx < 0)      //当vx值递减到小于0时,就将其设为0,即小球在X方向不再移动。  
            vx = 0; 
 
        //Y轴方向摩擦力处理  
        vy += fy;  //vy=fy+vy;这里fy同样为负值,所以每调用一次MyPiant(),vy恒定减小一个fy  
        if(vy < 0)  //若速度减到小于等于0,置为零,即小球在X方向不再移动。  
            vy = 0; 
 
        vy = -vy; 
    } 
 
    tPre = GetTickCount();     //记录此次时间  

 
//****消息处理函数***********************************    
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 

    switch (message) 
    { 
        case WM_KEYDOWN:                    //按键消息  ?  
            if(wParam==VK_ESCAPE)       //按下【Esc】键    
                PostQuitMessage(0); 
            break; 
        case WM_DESTROY:                    //窗口结束消息 ?  
            DeleteDC(mdc); 
            DeleteDC(bufdc); 
            DeleteObject(bg); 
            DeleteObject(angrybird); 
            ReleaseDC(hWnd,hdc); 
            PostQuitMessage(0); 
            break; 
        default:                            //其他消息  ?  
            return DefWindowProc(hWnd, message, wParam, lParam); 
   } 
   return 0; 

#include "stdafx.h"
#include <stdio.h>


//全局变量
HINSTANCE hInst;
HBITMAP bg,angrybird;
HDC  hdc,mdc,bufdc;
HWND hWnd;
DWORD tPre,tNow,tCheck;
RECT rect;
int  x=0,y=100,vx=10,vy=0;//初始横坐标x=0,初始纵坐标y=100,初始水平方向速度vx=10,初始竖直方向速度vy=0) 
                              
int  gy=2,fx=-1,fy=-4; //重力加速度gy=2,x方向摩擦力为-1,f方向摩擦力为-4

//全局函数?
ATOM    MyRegisterClass(HINSTANCE hInstance);
BOOL    InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void    MyPaint(HDC hdc);

//****WinMain函数,程序入口点函数**************************************   *
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 MSG msg;

 MyRegisterClass(hInstance);

 //初始化
 if (!InitInstance (hInstance, nCmdShow))
 {
  return FALSE;
 }

 //消息循环
 GetMessage(&msg,NULL,NULL,NULL);  //初始化msg  
    while( msg.message!=WM_QUIT )
    {
        if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
  else
  {
   tNow = GetTickCount();
   if(tNow-tPre >= 50)
    MyPaint(hdc);
  }
    }

 return msg.wParam;
}

//****设计一个窗口类,类似填空题,使用窗口结构体********************* 
ATOM MyRegisterClass(HINSTANCE hInstance)
{
 WNDCLASSEX wcex;

 wcex.cbSize = sizeof(WNDCLASSEX);
 wcex.style   = CS_HREDRAW | CS_VREDRAW;
 wcex.lpfnWndProc = (WNDPROC)WndProc;
 wcex.cbClsExtra  = 0;
 wcex.cbWndExtra  = 0;
 wcex.hInstance  = hInstance;
 wcex.hIcon   = NULL;
 wcex.hCursor  = NULL;
 wcex.hCursor  = LoadCursor(NULL, IDC_ARROW);
 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
 wcex.lpszMenuName = NULL;
 wcex.lpszClassName = "maple";
 wcex.hIconSm  = NULL;

 return RegisterClassEx(&wcex);
}

//****初始化函数*************************************   
// 1.加载位图资源 
// 2.取得内部窗口区域信息   
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
 HBITMAP bmp;
 hInst = hInstance;

 hWnd = CreateWindow("maple", "浅墨的窗口" , WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

 if (!hWnd)
 {
  return FALSE;
 }

 MoveWindow(hWnd,10,10,750,420,true);
 ShowWindow(hWnd, nCmdShow);
 UpdateWindow(hWnd);

 hdc = GetDC(hWnd);
 mdc = CreateCompatibleDC(hdc);
 bufdc = CreateCompatibleDC(hdc);
 bmp = CreateCompatibleBitmap(hdc,750,400);

 SelectObject(mdc,bmp);

 bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,750,400,LR_LOADFROMFILE);
 angrybird = (HBITMAP)LoadImage(NULL,"angrybird.bmp",IMAGE_BITMAP,140,70,LR_LOADFROMFILE);

 
 GetClientRect(hWnd,&rect);

 MyPaint(hdc);

 return TRUE;
}

//****自定义绘图函数*********************************
// 1.窗口贴图
// 2.根据小球的运动状态计算速度与贴图坐标
void MyPaint(HDC hdc)
{
 SelectObject(bufdc,bg);
 BitBlt(mdc,0,0,750,400,bufdc,0,0,SRCCOPY);

 SelectObject(bufdc,angrybird);
 BitBlt(mdc,x,y,70,70,bufdc,70,0,SRCAND);
 BitBlt(mdc,x,y,70,70,bufdc,0,0,SRCPAINT);

 BitBlt(hdc,0,0,750,400,mdc,0,0,SRCCOPY);

 x += vx;   //计算X轴方向贴图坐标,每调用一次MyPiant(),x坐标就加上一个vx的当前值

 vy = vy + gy;   //计算Y轴方向速度分量,vy随着每一次MyPiant()函数的调用就加上一个gy(重力加速度)
 y += vy;    //计算Y轴方向贴图坐标,每调用一次MyPiant(),y坐标就加上一个刚改变过后的vy,相当于加速运动

 if(y >= rect.bottom-70)   //判断是否触地,如果触碰到窗口边界,进行调整
 {
  y = rect.bottom - 70;

 //X轴方向的摩擦力处理
  vx += fx;  // vx=fx+vx;这里fx为负值,所以每调用一次MyPiant(),vx恒定减小一个fx  
  if(vx < 0)      //当vx值递减到小于0时,就将其设为0,即小球在X方向不再移动。
   vx = 0;

  //Y轴方向摩擦力处理
  vy += fy;  //vy=fy+vy;这里fy同样为负值,所以每调用一次MyPiant(),vy恒定减小一个fy
  if(vy < 0)  //若速度减到小于等于0,置为零,即小球在X方向不再移动。
   vy = 0;

  vy = -vy;
 }

 tPre = GetTickCount();     //记录此次时间
}

//****消息处理函数*********************************** 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 switch (message)
 {
  case WM_KEYDOWN:     //按键消息  ?
   if(wParam==VK_ESCAPE)  //按下【Esc】键 
    PostQuitMessage(0);
   break;
  case WM_DESTROY:     //窗口结束消息 ?
   DeleteDC(mdc);
   DeleteDC(bufdc);
   DeleteObject(bg);
   DeleteObject(angrybird);
   ReleaseDC(hWnd,hdc);
   PostQuitMessage(0);
   break;
  default:       //其他消息  ?
   return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}


下面是运行时的截图:

 

 

/

 

/

 

/

 

/

 

/最后小鸟能量耗尽,停留在窗口边缘的地面上。

 作者:zhmxy555