7.4    吃掉金币

马里奥看到金币会怎么办?当然是吃掉它。当马里奥与金币碰撞时,只需要将金币从数组中删除即可。删除后,下一帧重绘画面时,这个金币就不会被绘制,看起来就像消失了。

在当前程序中,每帧都会检查马里奥是否与其他物体发生碰撞,这里的“其他物体”仅指地图中参与碰撞的物体。由于金币是游离在地图之外的物体,因此,除了对地图进行碰撞检测外,还需要对存放金币的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所示。

78吃金币的画面

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

79吃掉金币后的画面