之前,我们使用数字在地图中表示不同的物体,如1代表天空,2代表地面,3代表砖块,4代表水管。程序根据不同的数字绘制不同颜色的矩形。
现在,我们要用瓦片图替代简陋的矩形。那么,地图中的数字应该如何编号呢?一种方法是使用瓦片图的连续编号,第一个图是1,第二个图是2,以此类推。另一种方法是按照行号+列号来编号,比如第一行第一个是101,第一行第12个是112,第二行第一个是201,以此类推。为什么使用101而不是0101,是因为图片数量较少,行号用一位数就足够了。
考虑到目前地图文件是手工编辑的,使用行列号来编号更方便查找图片的位置。例如,地面编号是101,砖块是102,竖向水管由301、302、401和402组成,如图4‑5所示。蓝天实际上并不使用图片,只是窗口的背景色而已。

图4‑5瓦片组成画面
上文我们已经成功拆分了瓦片图,由于每帧都要重绘画面,会频繁使用瓦片图,因此有必要将图片缓存起来。下面是一个缓存类的实现,将拆分的瓦片图按行列编号保存到字典里,代码如下所示:
04\02\TilesImageCache.py
|
import pygame
class TilesImageCache: def __init__(self): self.imageMap = {} self.bgImageList = [206, 112, 113, 114, 115, 212, 213, 214, 317, 417, 208, 314, 315, 415, 106, 504, 316]
# 加载图集 tileMap = pygame.image.load("tile_set.png").convert_alpha()
# 设置瓦片大小 tileWidth = 16 tileHeight = 16
# 获取的宽度和高度 mapWidth, mapHeight = tileMap.get_size()
# 计算每行每列的瓦片数量 numCols = mapWidth // tileWidth numRows = mapHeight // tileHeight
# 拆分并保存所有瓦片 for row in range(numRows): for col in range(numCols): # 拆分瓦片(x,y,width,height) tileSurface = tileMap.subsurface(col * tileWidth, row * tileHeight, tileWidth, tileHeight)
# 编号,row=0,col=0时是第一行第一列,key是101 key = (row + 1) * 100 + (col + 1) self.imageMap[key] = tileSurface
|
代码比较简单,不再赘述。地图文件的编号需要相应修改,如地面改成101,砖块改成102等等。
在程序运行时,第一次遍历地图会确定哪些物体参与碰撞检测,原先仅有1234四个数字用于判断逻辑,现在有更多图片,需要一种新的方法。由于大部分物体都参与碰撞,只有少数如白云、树木、小山等不参与碰撞,那么可以将这些不参与碰撞的编号记录在缓存类中的bgImageList变量,碰撞检测时排除这些编号即可。
程序第二次遍历地图是进行画面绘制,根据地图中的数字在图片缓存中找到对应的图片,然后绘制到屏幕上。修改后的代码如下所示:
04\03\Game.py
|
import pygame from constants import * from utils import * from Globals import * from Mario import Mario from TilesImageCache import TilesImageCache
class Game: def __init__(self): # 初始化 Pygame pygame.init()
# 设置窗口大小 self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
# 设置窗口标题 pygame.display.set_caption('super mario bros')
# 避免输入法影响按键 pygame.key.stop_text_input()
# 创建时钟对象 self.clock = pygame.time.Clock()
# 初始化图片缓存 Globals.tilesImageCache = TilesImageCache()
# 马里奥 self.mario = Mario(10, 100, self.screen)
# 地图数据 self.mapArray = [] self.mapViewFrom = 0 self.mapViewTo = SCREEN_WIDTH self.mapViewFromMax = 0
# 存储参与碰撞的物体信息 self.objectArray = []
# 初始化地图 self.initMap()
# 初始化地图 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 == 1 or cell in Globals.tilesImageCache.bgImageList: continue # 背景图片,不碰撞 else: # 其他参与碰撞 posX = cellIndex * MAP_BLOCK_SIZE posY = rowIndex * MAP_BLOCK_SIZE self.objectArray.append(pygame.Rect(posX, posY, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE))
# 滚动地图 def scrollMap(self, step): self.mapViewFrom += step if self.mapViewFrom < 0: self.mapViewFrom = 0 if self.mapViewFrom > self.mapViewFromMax: self.mapViewFrom = self.mapViewFromMax self.mapViewTo = self.mapViewFrom + SCREEN_WIDTH
def drawMap(self): # 画地图 for rowIndex in range(len(self.mapArray)): row = self.mapArray[rowIndex] for cellIndex in range(len(row)): # 转换为屏幕坐标,在显示范围内才绘制,注意参数的顺序 screenPos = mapArrayToScreen(cellIndex, rowIndex) if screenPos[0] + MAP_BLOCK_SIZE > 0 and screenPos[0] < SCREEN_WIDTH: cell = row[cellIndex] # 编号在图片缓存中,则显示图片 if cell in Globals.tilesImageCache.imageMap: self.screen.blit(Globals.tilesImageCache.imageMap[cell], screenPos)
def run(self):
# 主循环 isRunning = True while isRunning: for event in pygame.event.get(): if event.type == pygame.QUIT: isRunning = False
# 全屏擦除(注意背景色改成了天蓝色) self.screen.fill(COLOR_SKY_BLUE)
# 绘制地图 self.drawMap()
# 获取按键状态 keys = pygame.key.get_pressed()
# 更新马里奥 self.mario.update(keys)
# 刷新显示 pygame.display.flip()
# 每秒60帧 self.clock.tick(60) # 退出 Pygame pygame.quit()
if __name__ == '__main__': game = Game() Globals.game = game # 保存起来,便于使用 game.run()
|
代码中主要修改了四个地方:
l 创建TilesImageCache对象,并放到Globals中,便于使用。
l 修改initMap()方法,在bgImageList变量中的编号不参与碰撞检测。
l 修改drawMap()方法,直接使用Globals.tilesImageCache.imageMap[cell]从缓存获取图片。
l 修改窗口背景色为天蓝色
另外,修改常量类中MAP_BLOCK_SIZE为16,窗口高度改为224(14个瓦片高,每个瓦片16像素)。运行程序,画面如图2‑36所示,是不是更有感觉了?

图2‑36使用瓦片图的画面
下面就是辛苦的地图制作时间了,参考马里奥的世界地图修改我们的地图文件,代码见04\04目录,最终的效果如图2‑37和图2‑38所示。

图2‑37完整的世界地图

图2‑38世界地图-关底