您的位置:首页 > 编程语言 > C语言/C++

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

2012-12-11 00:17 573 查看
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
http://blog.csdn.net/zhmxy555/article/details/7555785
作者:毛星云 邮箱: happylifemxy@qq.com 期待着与志同道合的朋友们相互交流

本节内容主要介绍摩擦力系统的模拟,顺带着介绍了暴雪的神级作品《暗黑破坏神3》。

浅墨已经在开始动笔写DirectX 11相关的内容了,等这几节Win32的文章发完之后,即刻开始DirectX 11的讲解。

一.基础知识讲解

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

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

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

[cpp]
view plaincopyprint?

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进行多人联网合作或对战

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



更多内容去请大家前去http://d3.uuu9.com/欣赏,浅墨在这里就不赘言了。

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

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

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

[cpp]
view plaincopyprint?

#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;
}


下面是运行时的截图:











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

这里感谢gh402兄的建议,我们将之前小鸟的正面的平面效果图换成了带点3D效果的侧面图,使运行结果更加逼真了。

本节即到这里了,下面依旧是放出两个版本的源代码供大家下载学习)(当然,必须是零资源分下载^^):

本篇文章VS2010版的源码请点击这里下载:【VS2010版】【Visual
C++】Code_Note_21

本篇文章VC6.0版的源码请点击这里下载:
【VC6.0版】【Visual C++】Code_Note_21

感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们,也恳请大家继续关注我的专栏。

目前在讲的GDI只是前奏。DirectX 11会在GDI梳理完后进行深入讲解,敬请期待~~

【Visual C++】游戏开发 系列文章才刚刚展开一点而已,因为游戏世界实在是太博大精深了~

但我们不能着急,得慢慢打好基础。做学问最忌好高骛远,不是吗?

浅墨希望看到大家的留言,希望与大家共同交流,希望得到睿智的评论(即使是批评)。

你们的支持是我写下去的动力~

精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习,共同进步。

大家看过后觉得值得一看的话,可以顶一下这篇文章,你们的支持是我继续写下去的动力~

如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨相关的问题。

最后,谢谢你们一直的支持~~~

——————————浅墨于2012年5月10日
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐