实验4 碰撞检测与运动模拟(Box2D桌球)

说明:

课程教材《计算机游戏程序设计》(基础篇)(第3版) 提供示例代码,而课程实验在示例代码的基础上提出更高的实验要求。除此之外,本人也会额外加入些个人创意,希望同学们在参考之余也能加入自己的想法。

实现效果:

实验报告:

一、实验目的与要求

1. 了解物理模拟、精灵的绘制与移动、触摸事件的应用等。

2. 熟悉基于Box2D的物理引擎。

3. 掌握Box2D触屏检测和碰撞检测机制。

二、实验内容与方法

1.完成游戏编译(70分)

成功编译并运行教材P150“游戏物理模拟实例-基于Box2D的游戏实例”。

 

2.完成方案一 (10分)

修改游戏代码,实现修改内容一,即修改击球方式。

 

3.完成方案二 (10分)

修改游戏代码,实现修改内容二,即增加一个进洞口。

 

4.完成方案三 (10分)

修改游戏代码,实现修改内容三,即修改球杆跟随力度变化的状态。

 

三、实验步骤与过程

记录关键步骤/设计过程/设计结果的截图

1.完成游戏编译(70分)

老套路编译例程并运行程序。

图 1

2.修改击球方式 (10分)

修改击球方式为:在击球区域拖动鼠标左键,可以实现球杆的瞄准以及能量的蓄积,松开鼠标左键,实现击球。

  • 仔细研究原代码,发现“在击球区域拖动鼠标左键,可以实现球杆的瞄准”这一功能原本就已经实现了,无需多做修改。
  • 而对于“在击球区域拖动鼠标左键,可以实现能量的蓄积”这一功能,只需把原来在“能量滑块区域”滑动更改为在“击球区域”中滑动即可。

因此,在拖动鼠标调用的函数onTouchMoved()中,无需再把“球杆瞄准”和“能量积蓄”两个功能分别写在“击球区”和“能量滑动区”中实现,而是统一放到“击球区”中实现即可。

此时,“当前击球能量”变量_curPower的值可更改为由“白球”的坐标和“目标点”的坐标的距离长度(为更好控制数值,可让它们再除以2)。

即curPower=(touch->getLocation()-changePos(mainBilliards->getPosition())).getLength()/2

  • “松开鼠标左键,实现击球”这一功能在原代码中也已经实现了,只不过它限定了在“能量滑动区域”的范围里。因此,只需要把这一个限定条件“_powerRect.containsPoint(touch->getLocation())” 给删掉就可以了。

修改后代码为:

图 2

图 3

图 4

3.增加个进洞口 (10分)

Ps一张“球洞”图片,命名为“hole”。

图 5

  • 直接利用BilliardSprite类来创建“球洞”对象,但是与创建“台球”对象不同的是,由于“球洞”不是“运动物体”而是“静态物体”,所以在create时要注意参数isStatic设置为true。

图 6

由于台球游戏一般有四个洞,所以我也给这个游戏设了四个“球洞”。所以,四个球洞除了位置的不同外,还应该有旋转角度的不同,由上图可见四个球洞精灵对象分别要旋转0、90、180、270度。但由于BilliardSprite初始化函数中没有设置精灵对象的旋转角度的代码。所以可以另外在BilliardSprite类中写一个方法函数initHole(float rotation)来设置精灵对象的旋转角度。

但要注意的是,BilliardSprite中的update函数中会不断地更新精灵的位置以及根据body的角度变化来设置精灵的旋转。但是在默认情况下,物体都是不动的,所以会不断地给精灵的旋转角度设置为0 ,这样initHole函数设置的“洞口”旋转角度就前功尽废了。如下图update代码所示:

图 7

所以,为了“球洞”精灵不受update函数的影响。我们可以在initHole函数执行this->unscheduleAllSelectors(),解除update定时器,让“球洞”维持“静止”。

而“球洞”精灵的位置,要由“球洞”物体的位置来转换而成。因此,“球洞”初始化函数initHole代码有:

图 8

  • 当“白球”进洞时,重新开始游戏。该功能实现只需要如实验三一样,在游戏主场景PhysicsBox2dScene.cpp中执行如下代码,进行与新场景的切换即可:

CCDirector::sharedDirector()->replaceScene(PhysicsBox2dScene::createScene());

于是,问题的关键就在于如何判断“白球进洞”并执行上述代码了。可给碰撞监听类GameContactListener添加一个bool类型的私有变量mainBallInHole,表示白球是否进洞,并添加该变量的get方法。

当物体间发生碰撞后,GameContactListener.cpp的BeginContact函数会被调用,该函数通过b2Contact参数获取到两个碰撞对象。因此,只需通过获取这两个碰撞对象的name并进行比对,如果是“main”和“hole”便能确定是“白球进洞”,然后再把mainBallInHole变量设为true。

而PhysicsBox2dScene.cpp的update函数中通过get方法获取到mainBallInHole变量再进行if判断,条件符合后便执行场景切换函数即可。

 

代码为:

GameContactListener.cpp:

图 9

PhysicsBox2dScene.cpp的update函数:

图 10

PhysicsBox2dScene.cpp:

图 11

  • 当除了白球的“普通球”进洞时,该球要消失。结合②部分可知,只需在GameContactListener.cpp的BeginContact函数中添加一个name为“ball”与“hole”碰撞的情况即可。其中,最好的实现方法莫过于把要移除的小球给记录在链表ballsRemovalList中,在时间长step执行后及场景被渲染前,执行GameContactListener的函数deleteBody来遍历ballsRemovalList,对其中记录的小球对象进行删除。

 

然而,由于BilliardSprite对象被调用的地方比较多,导致直接删除该对象(delete BilliardSprite*)程序报错,本人功力不够深厚没能解决这一问题。只能退而求其次,在BilliardSprite类中写一个新的函数方法deleteBody(),通过调用其来删除BilliardSprite对象中的_body以及移除_sprite 。这样做虽然不是最佳方案但也能实现同样的功能。

代码为:

GameContactListener.cpp:

图 12

PhysicsBox2dScene.cpp (要在step后执行删除操作):

图 13

GameContactListener.cpp:

图 14

BilliardSprite.cpp:

图 15

 

4.修改球杆跟随力度变化的状态 (10分)

重点观察PhysicsBox2dScene.cpp中的函数updateLine()的“计算球杆位置”部分的代码,有如下所示:

图 16

从这个代码中我们很难看得出什么,我们对其进行展开和转换。以x轴坐标为例,即:

(((end_p - start_p).getLength() + 45) * start_p.x - 45 * end_p.x) / (end_p - start_p).getLength()

我们可以把其转化为:

start_p.x + 45 * (start_p.x - end_p.x) / (end_p - start_p).getLength()

由转换后的代码我们就比较容易看出start_p.x指白球的x坐标,而 (start_p.x - end_p.x) / (end_p - start_p).getLength() 指的是夹角的余弦值。因此,易知这个45指的应该是球杆与白球的距离(y轴坐标分析同理)。

可知,默认情况下球杆离白球的距离为45。因此,我们只需根据力度的数值改变这45的距离即可。引入_curPower,把代码中的45都改为45+_curPower/2  (除以2是为了适当控制距离,令球杆不要离白球太远),即:

图 17

 

5其余

下面介绍自己新添的内容:

  • 由一个洞口添加到四个洞口(前文已说明)
  • 窗口分辨率调整为1600 x 900
  • 小球由5个增添到10个,并对位置进行了调整
  • 添加新的背景精灵bg,即为绿色的台球布。代码为:

图 18

 

6程序运行

初始图:

图 19

 

注意球杆和白球距离,以及力量条间的关系:

图 20

图 21

 

猜你喜欢

转载自blog.csdn.net/purers/article/details/82385571
今日推荐