马里奥看到金币会怎么办?当然是吃掉它。当马里奥与金币碰撞时,只需要将金币从数组中删除即可。删除后,下一帧重绘画面时,这个金币就不会被绘制,看起来就像消失了。
在当前程序中,每帧都会检查马里奥是否与其他物体发生碰撞,这里的“其他物体”仅指地图中参与碰撞的物体。由于金币是游离在地图之外的物体,因此,除了对地图进行碰撞检测外,还需要对存放金币的itemArray进行碰撞检测。
由于金币不是Rect对象,不能直接使用RectA.collidelist(List<Rect>)方法来对itemArray进行碰撞检测,因此需要稍作修改,代码如下所示:
07\03\utils\utils.py
|
# 马里奥与itemArray的碰撞检测 def checkItemHit(marioRect, itemList): for idx, item in enumerate(itemList): if marioRect.colliderect(item.rect): return idx return -1 |
由于itemArray中的每个物体都有rect属性(这是我们自己约定的,放入itemArray中的每个物体都要有rect属性),因此可以遍历数组的每个物体进行碰撞检测,返回第一个碰撞物体的下标。这个方法比较通用,因此被放到了工具类中。
现在,我们知道碰撞物体的下标了,然后做些什么呢?检测到碰撞后如何处理,这个叫做碰撞响应。例如,当马里奥碰到水管,水管保持不变,但马里奥无法继续前进。当马里奥碰到金币,金币会消失,马里奥会得到分数,类似的情况还有很多。碰撞响应是双向的,即参与碰撞的两个物体都需要处理碰撞事件,即使什么都不做。
我们给Mario类增加了onHit()方法,实际上没有执行任何操作,仅仅是将碰撞物体的类名打印出来,代码如下所示:
07\03\mario\Mario.py
|
# 碰撞 def onHit(self, hitItem): print("mario onHit..." + type(hitItem).__name__) |
金币FlashingCoinAnimation类增加onHit方法,将自己从数组中删除,代码如下所示:
07\03\item\FlashingCoinAnimation.py
|
# 碰撞 def onHit(self, itemIndex): print("coin onHit...") del Globals.game.itemArray[itemIndex] |
我们已经准备好了所有辅助方法,接下来需要将它们串起来。由于金币并不会阻碍马里奥的移动,所以可以在马里奥移动之后,再判断是否发生碰撞。走路和跳跃状态可以在处理完方向键后再进行碰撞检测。由于这部分代码比较通用,因此我们将其封装为doItemHit()方法,放到父类中以便于使用,代码如下所示:
07\03\mario\state\MarioState.py
|
# 动态物体的碰撞检测 def doItemHit(self): itemIndex = checkItemHit(screenToMap(self.mario.rect), Globals.game.itemArray) # 是否碰撞 if itemIndex != -1: print("马里奥碰撞到物体,下标" + str(itemIndex)) hitItem = Globals.game.itemArray[itemIndex] # 碰撞的物体 hitItem.onHit(itemIndex) # 物体的碰撞响应 self.mario.onHit(hitItem) # 马里奥的碰撞响应 |
在走路和跳跃状态中调用doItemHit()方法即可。而在站立状态时,马里奥不会移动,因此无需进行碰撞检测。
简而言之,代码逻辑如下:马里奥移动后,与itemArray中的物体进行碰撞检测,一旦发生碰撞,调用双方的onHit方法处理碰撞事件。这样设计,以后即使增加其他种类的物体,主逻辑也无需改变。
运行程序,画面如图7‑8所示。

图7‑8吃金币的画面
吃掉金币后的画面,如图7‑9所示。

图7‑9吃掉金币后的画面