Pygame从瓦片集blit子表面渲染瓦片地图出现异常
Pygame瓦片地图渲染错位问题
实现逻辑
- 瓦片集加载:读取
.png格式瓦片图源,切割后将所有独立瓦片纹理存入tileset.tiles列表 - 地图存储:使用三层
.csv文件存储瓦片排布信息,每个网格位置对应一个瓦片纹理id - 渲染优化:加载房间时将三层瓦片一次性烘焙到
map表面,游戏主循环每帧直接blit该表面到屏幕,避免逐瓦片渲染的性能开销
异常现象
- 瓦片无法正确绘制到目标表面:
Room类load方法第二次blit同一子表面时触发异常,初始排查未定位到直接诱因 - 渲染结果受csv排布影响:任意调整csv内的瓦片编号(包括跨层调整)都会导致最终渲染结果错乱,出现随机错贴瓦片的情况;测试用例中id=8的瓦片为天空蓝色方块,其余id对应不同地形、装饰纹理
- 局部修改无对应反馈:修改中层第一个瓦片(id=0为花朵纹理)时渲染结果无变化;修改底层前几个瓦片同样出现渲染错位
- 已排除瓦片集类故障:绕过
load方法直接从Tileset实例提取子表面,可在屏幕任意位置正常绘制瓦片
排查留存素材
- 三层csv排布文件:底层、中层、顶层瓦片配置
- 异常渲染结果截图
- 中层瓦片调整后的配置文件、对应渲染结果截图
- 底层瓦片调整后的配置文件、对应渲染结果截图
复现代码
import pygame as pg class Tileset: def __init__(self,img:pg.Surface): self.tiles = [] self.img = img self.loadtiles() def loadtiles(self): for i in range(16): for n in range(16): currentimg = self.img.subsurface(32*n,32*i,32,32) self.tiles.append(currentimg.copy()) class Tile(pg.sprite.Sprite): def __init__(self,image:pg.Surface,position:tuple): self.img = image self.pos = position def draw (self,surface:pg.Surface): surface.blit(self.img,self.pos) class Room: def __init__(self,id,size:tuple): self.id = id self.layers = [[],[],[]] self.size = (size[0]*32,size[1]*32) # 加载新房间时调用:读取房间csv,存储瓦片信息,烘焙新的地图表面 def load(self,map): map = pg.Surface(self.size) for layer in self.layers: with open ('levels/final/room'+str(self.id)+'_'+str(self.layers.index(layer))+'.csv') as file: data = file.readlines() for rrrow in data: # 原始行数据 rrow = rrrow.strip('\n').split(',') # 半处理行数据 row = [] # 处理完成的行数据 for rtile in rrow: # 行内未处理瓦片 if rtile != -1: tileimg = tileset.tiles[int(rtile)] tile = Tile(tileimg,(rrow.index(rtile)*32, data.index(rrrow)*32)) # 生成瓦片实例 tile.draw(map) # 绘制瓦片到当前地图表面 row.append(tile) # 将瓦片存入行列表 layer.append(row) # 将行存入层列表,后续用于碰撞检测 return map # pygame主循环初始化(非完整代码,不影响问题复现) res = (32*30,32*20) scr = pg.display.set_mode(res) tileset = Tileset(pg.image.load('graphics\stock.png')) room0 = Room(0,(30,20)) map = pg.Surface((0,0)) map = room0.load(map) running = True while running: scr.fill((0,0,0)) scr.blit(map,(0,0)) pg.display.flip()
故障原因
代码中四处逻辑错误直接导致渲染异常:
- 层索引获取错误:遍历
self.layers时用self.layers.index(layer)拼接csv文件名,初始化时三个层均为空列表,index()方法永远返回第一个匹配空列表的索引0,导致中层、顶层加载时全部读取底层csv文件,三层内容完全重复,修改中层文件自然不会有渲染变化 - 瓦片坐标计算错误:用
rrow.index(rtile)计算瓦片x坐标、data.index(rrrow)计算瓦片y坐标,list.index()只会返回列表中第一个匹配值的索引:同一行存在多个相同id的瓦片时,所有同id瓦片的x坐标都会被错误设置为第一个同id瓦片的位置;csv中存在内容完全相同的行时,所有重复行的y坐标都会被错误设置为第一个匹配行的位置,直接导致瓦片错位 - 空瓦片判断逻辑失效:从csv读取的
rtile是字符串类型,和整数-1比较永远为真,会尝试将空字符串/字符串格式的"-1"转为整数索引瓦片列表,容易触发索引越界错误 - Windows路径转义风险:
graphics\stock.png中的\s会被Python识别为转义字符,可能导致瓦片集图片加载失败
修复方案
- 遍历层、行、列时统一使用
enumerate()直接获取索引值,禁止用list.index()方法查找位置,避免重复值导致的索引错误 - 修正空瓦片判断逻辑,先把读取到的字符串转为整数,再判断是否为空瓦片(约定空瓦片id为-1)
- 修正文件路径写法,使用raw字符串或者正斜杠规避转义字符问题
修复后的load方法核心逻辑参考:
def load(self,map): map = pg.Surface(self.size) # 按层索引遍历,避免用list.index找层位置 for layer_idx in range(len(self.layers)): with open(f'levels/final/room{self.id}_{layer_idx}.csv') as file: data = file.readlines() # 直接拿行号作为y坐标索引 for y_idx, rrrow in enumerate(data): rrow = rrrow.strip('\n').split(',') row = [] # 直接拿列号作为x坐标索引 for x_idx, rtile in enumerate(rrow): tile_id = int(rtile) # 正确判断空瓦片 if tile_id == -1: continue tileimg = tileset.tiles[tile_id] tile = Tile(tileimg, (x_idx*32, y_idx*32)) tile.draw(map) row.append(tile) self.layers[layer_idx].append(row) return map
同时把瓦片集加载路径改成pg.image.load(r'graphics\stock.png')或者pg.image.load('graphics/stock.png')即可。
内容的提问来源于stack exchange,提问作者royalmk
相关产品推荐
相关产品推荐

