目前,所有金币的坐标都是固定的,代码如下所示:
07\03\Game.py
|
# 多个金币 from item.FlashingCoinAnimation import FlashingCoinAnimation self.itemArray = [FlashingCoinAnimation(100, 100), FlashingCoinAnimation(260, 110), FlashingCoinAnimation(310, 176), FlashingCoinAnimation(330, 176), FlashingCoinAnimation(350, 176), FlashingCoinAnimation(370, 176), FlashingCoinAnimation(390, 176), FlashingCoinAnimation(410, 176), FlashingCoinAnimation(400, 60)] |
如果有一百个金币,代码将变得庞大,因此,我们应该找到一种管理金币位置的方式。我们已经拥有一个地图文件,其中使用数字表示各种物体,例如水管、砖块、树木等等,如图7‑10所示。

图7‑10地图文件
我们可以将金币的位置也放入地图文件中吗?是可以的。之前,背景图片的数字是用瓦片图集的行号加列号进行命名的,最小的数字101代表第一行第一列,因此小于100的数字可以用来表示动态物体。我们将用数字1表示金币,后面的数字表示其他物体,现在地图文件中的1表示的是天空,我们将它改成0。
在加载地图时,程序会遍历地图并记录哪些物体参与碰撞检测,现在,我们只需要额外记录金币的位置即可,代码如下所示:
07\04\Game.py
|
# 动态物体的位置数组 self.itemPosArray = [] self.itemArray = [] ……省略……
# 初始化地图 def initMap(self):
# 地图数据 self.mapArray = loadMapData("1-1.txt")
# 滚动地图From的最大值 self.mapViewFromMax = len(self.mapArray[0]) * MAP_BLOCK_SIZE - SCREEN_WIDTH
# 存储参与碰撞的物体信息(地图坐标) for rowIndex in range(len(self.mapArray)): row = self.mapArray[rowIndex] for cellIndex in range(len(row)): cell = row[cellIndex] if cell == 0 or cell in Globals.tilesImageCache.bgImageList: continue # 背景图片,不碰撞 else: # 位置信息 posX = cellIndex * MAP_BLOCK_SIZE posY = rowIndex * MAP_BLOCK_SIZE if cell < 100: # 动态物体,记录坐标 self.itemPosArray.append((posX, posY, cell)) else: # 参与碰撞的物体 self.objectArray.append(pygame.Rect(posX, posY, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE))
|
在代码中,如果判断地图数字小于100,则将其位置信息存储在itemPosArray中,这是一个保存物体位置信息的数组。为什么要记录金币的位置,而不是直接创建金币对象呢?在游戏开发中有一个原则,即“用不到就不要创建、看不到就不要绘制”,用专业术语来说就是“避免不必要的预加载和过度渲染”。因为游戏可能包含大量的游戏对象和场景元素,如果都在游戏开始时创建,会导致资源浪费和性能下降。因此,通常会在需要时才创建游戏对象。
那么,何时创建金币对象呢?在马里奥接近金币位置的时候,也就是当屏幕滚动到金币位置时。在主程序中增加createItem()方法,代码如下所示:
07\04\Game.py
|
# 创建动态物体 def createItem(self): for itemIndex in range(len(self.itemPosArray) - 1, -1, -1): # 倒过来循环,因为后面可能删除 itemPos = self.itemPosArray[itemIndex] # 屏幕右边界到达物体位置(提前10像素创建) if self.mapViewTo >= itemPos[0] - 10: if itemPos[2] == 1: # 金币 from item.FlashingCoinAnimation import FlashingCoinAnimation self.itemArray.append(FlashingCoinAnimation(itemPos[0], itemPos[1])) del self.itemPosArray[itemIndex] print("itemPos size:" + str(len(self.itemPosArray))) print("item size:" + str(len(self.itemArray))) |
在代码中会逆向循环位置数组,这是一种常见的编程技巧,可以避免删除数组元素时影响索引的问题。在位置判断时,减去了10像素,这样会稍早一点创建金币,让金币看起来早就存在,而不是突然出现在屏幕边缘。最后,创建金币后要记得从位置数组中删除,否则下一帧满足条件会重复创建金币。
在主循环绘制地图之后,调用createItem()方法即可,运行程序,画面如图7‑11所示。

图7‑11满屏的金币
由于地图文件中表示天空的数字1还没有改成0,所以我们看到了整个屏幕都是金币。观察左侧的打印信息,可以发现itemPosArray中有2099个金币位置,而itemArray中只有182个金币对象。实际画面中只显示了172个金币,那另外的10个金币在哪里呢?让马里奥移动几步后,就可以看到,如图7‑12所示。原来右边这一列隐藏着10个金币。这就是位置判断减去10像素的效果,虽然看不见,但金币已经被创建了。

图7‑12满屏金币滚屏
让马里奥尽情享用金币大餐后,来到了关底旗杆处,让我们看看金币的情况,如图7‑13所示。

图7‑13关底的金币情况
可以看到,itemPosArray中仍然有189个金币位置,而itemArray里却增加到了1287个金币。发现问题了没有?itemPosArray中金币数量逐渐减少是正常的,而itemArray中金币数仍然如此之多是不正常的,因为实际画面中并没有这么多金币。那金币哪去了呢?它们在屏幕左边看不到的地方,滚屏滚出了视野。
再回想一下游戏开发的原则,“看不到就不要绘制”,这些不可见的金币已经变得多余,需要将它们销毁以释放内存。因此,除了创建金币,我们还需要负责销毁金币,将createItem()方法改名为manageItem()方法,并增加销毁金币的逻辑,代码如下所示:
07\05\Game.py
|
# 动态物体的创建与销毁 def manageItem(self): # 即将进入屏幕,创建 for itemIndex in range(len(self.itemPosArray) - 1, -1, -1): # 倒过来循环,因为后面可能删除 itemPos = self.itemPosArray[itemIndex] # 屏幕右边界到达物体位置(提前10像素创建) if self.mapViewTo >= itemPos[0] - 10: if itemPos[2] == 1: # 金币 from item.FlashingCoinAnimation import FlashingCoinAnimation self.itemArray.append(FlashingCoinAnimation(itemPos[0], itemPos[1])) del self.itemPosArray[itemIndex]
# 物体往左移出屏幕范围后,删除 for itemIndex in range(len(self.itemArray) - 1, -1, -1): # 倒过来循环,因为后面可能删除 item = self.itemArray[itemIndex] # 物体右边缘<屏幕左边界 if item.rect.right < self.mapViewFrom: del self.itemArray[itemIndex]
print("itemPos size:" + str(len(self.itemPosArray))) print("item size:" + str(len(self.itemArray))) |
修改后运行程序,可以看到金币数量正常了,画面如图7‑14所示。

图7‑14金币数量正常了