Ursina开发Minecraft克隆版 覆水放置方块触发AssertionError报错
报错根因
你触发的AssertionError: !is_empty() at line 1045 of panda/src/pgraph/nodePath.cxx是典型的遍历列表时同步修改列表结构+访问已销毁实体属性导致的错误:
- 你正向遍历
voxels列表的过程中直接删除列表条目、销毁方块实体,会导致列表索引错位,后续循环会读取到已经被移除出列表、已经调用destroy()销毁的实体 - 被销毁的Ursina实体本质是Panda3D的空NodePath,此时访问它的
position属性就会触发底层的断言报错 - 额外隐患:全列表遍历比对坐标的效率极低,且浮点数坐标未做取整处理时,很容易出现视觉上坐标重合但程序判断不相等的匹配错误
修复步骤
- 禁止在遍历原
voxels列表的循环内直接执行实体销毁、列表删除操作,先把所有待删除的目标收集到临时列表,遍历完成后再统一做销毁和数据清理 - 新增实体存活校验:访问任何方块实体属性前,先判断
voxel.nodePath.is_empty()是否为False,跳过已销毁实体做兜底 - 新增坐标哈希索引:用字典存储坐标到方块实体的映射,把重合检测的时间复杂度从O(n)降到O(1),从根源上避免遍历删数据的索引错位问题
- 坐标比对前对xyz值做整数取整,规避浮点数精度误差导致的匹配失败
核心修复代码
# 全局方块存储:保留原有列表做遍历用,新增坐标字典做快速查询 voxels = [] voxel_index = {} # 键为(x,y,z)整数元组,值为对应的方块实体 # 新增方块时同步更新两个存储结构 def spawn_voxel(pos, is_water=False): block = Voxel( position=pos, texture=water_texture if is_water else block_texture, collider=None if is_water else 'box' # 水方块关碰撞,其余开碰撞 ) block.is_water = is_water voxels.append(block) pos_key = (round(pos.x), round(pos.y), round(pos.z)) voxel_index[pos_key] = block return block # 替换原有的重合水方块删除逻辑 def clear_overlap_water(): wait_to_delete = [] # 第一步:仅遍历收集待删除目标,不做任何修改操作 for block in voxels: # 跳过已销毁的无效实体 if block.nodePath.is_empty(): continue current_pos = (round(block.x), round(block.y), round(block.z)) # 当前位置是普通方块时,检查同位置是否存在水方块 if not block.is_water and current_pos in voxel_index: target_block = voxel_index[current_pos] if target_block.is_water and not target_block.nodePath.is_empty(): wait_to_delete.append( (current_pos, target_block) ) # 第二步:统一销毁实体、清理存储数据 for pos_key, target in wait_to_delete: destroy(target) del voxel_index[pos_key] voxels.remove(target) # 删除普通方块时也同步更新两个存储结构,避免脏数据 def destroy_block(block): if block.nodePath.is_empty(): return pos_key = (round(block.x), round(block.y), round(block.z)) destroy(block) if pos_key in voxel_index: del voxel_index[pos_key] if block in voxels: voxels.remove(block)
提示:所有涉及方块坐标增删查的操作,都要同步更新
voxels列表和voxel_index字典,避免两个存储结构数据不一致。
内容的提问来源于stack exchange,提问作者dvyDvine
相关产品推荐
相关产品推荐

