说明:
<学习>系列所有的源代码均由《计算机游戏程序设计》提供。本人会在这些代码中融入自己的想法,对其进行迭代优化,旨在个人学习。
实现效果:
学习目标:
1. 了解物理模拟、精灵的绘制与移动、触摸事件的应用等。
2. 熟悉基于Box2D的物理引擎。
3. 掌握Box2D触屏检测和碰撞检测机制。
修改内容:
1.优化击球方式。
2.增加一个进洞口。
3.修改球杆跟随力度变化的状态。
步骤与过程
1.完成游戏编译
老套路编译示例代码并运行程序。
图 1
2.优化击球方式
修改击球方式为:在击球区域拖动鼠标左键,可以实现球杆的瞄准以及能量的蓄积,松开鼠标左键,实现击球。
- 仔细研究原代码,发现“在击球区域拖动鼠标左键,可以实现球杆的瞄准”这一功能原本就已经实现了,无需多做修改。
- 而对于“在击球区域拖动鼠标左键,可以实现能量的蓄积”这一功能,只需把原来在“能量滑块区域”滑动更改为在“击球区域”中滑动即可。
因此,在拖动鼠标调用的函数onTouchMoved()中,无需再把“球杆瞄准”和“能量积蓄”两个功能分别写在“击球区”和“能量滑动区”中实现,而是统一放到“击球区”中实现即可。
此时,“当前击球能量”变量_curPower的值可更改为由“白球”的坐标和“目标点”的坐标的距离长度(为更好控制数值,可让它们再除以2)。
即curPower=(touch->getLocation()-changePos(mainBilliards->getPosition())).getLength()/2
- “松开鼠标左键,实现击球”这一功能在原代码中也已经实现了,只不过它限定了在“能量滑动区域”的范围里。因此,只需要把这一个限定条件“_powerRect.containsPoint(touch->getLocation())” 给删掉就可以了。
修改后代码为:
图 2
图 3
图 4
3.增加进洞口
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.修改球杆跟随力度变化的状态
重点观察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