代码拉取完成,页面将自动刷新
"""
这个脚本用于检查tmx文件的正确性,以及导出tmx文件
检查tmx文件的正确性:会检查出生点是否存在,传送门是否存在,对象是否放在了障碍物的坐标上,对象是否找不到路径
"""
import argparse
import math
import xml.etree.ElementTree as ET
from enum import Enum
from pathlib import Path
from typing import List
import tiledmap_export
# =============== 寻路相关 =================
class PfNode:
def __init__(self, x, y, f, parent):
self.x = x
self.y = y
self.f = f
self.parent = parent
class PathFinder:
_i = None
@staticmethod
def I():
if PathFinder._i is None:
PathFinder._i = PathFinder()
return PathFinder._i
def __init__(self):
self.grid = []
self.directions = []
def init(self, pW, pH):
self.grid = [[0 for _ in range(pW)] for _ in range(pH)]
def set8Or4Dir(self, eightDirections):
self.directions = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [-1, 1], [1, -1],
[1, 1]] if eightDirections else [[-1, 0], [1, 0], [0, -1], [0, 1]]
def setObstacle(self, x, y, pVal):
if y < len(self.grid) and x < len(self.grid[y]):
self.grid[y][x] = 1 if pVal else 0
def isObstacle(self, x, y):
return y < len(self.grid) and x < len(self.grid[y]) and self.grid[y][x] == 1
def findPath(self, start, end):
openList = [PfNode(start[0], start[1], 0, None)]
closedList = []
while len(openList) > 0:
lowestIndex = min(range(len(openList)), key=lambda index: openList[index].f)
currentNode = openList.pop(lowestIndex)
if currentNode.x == end[0] and currentNode.y == end[1]:
path = []
while currentNode is not None:
path.append([currentNode.x, currentNode.y])
currentNode = currentNode.parent
return path[::-1]
closedList.append(currentNode)
for dir in self.directions:
neighbor = PfNode(currentNode.x + dir[0], currentNode.y + dir[1], 0, None)
if neighbor.y < len(self.grid) and neighbor.x < len(self.grid[neighbor.y]) and \
not self.grid[neighbor.y][neighbor.x] and \
not any(node.x == neighbor.x and node.y == neighbor.y for node in closedList):
g = currentNode.f + 1
h = abs(neighbor.x - end[0]) + abs(neighbor.y - end[1])
f = g + h
if any(node.x == neighbor.x and node.y == neighbor.y and node.f <= f for node in openList):
continue
neighbor.f = f
neighbor.parent = currentNode
openList.append(neighbor)
return []
# =============== 编辑器相关 =================
class GType(Enum):
Born = 1
Monster = 2
Box = 3
Door = 4
Boss = 5
class VoObject:
obj_name = ''
id = 0
def __init__(self, p_gtype, p_x, p_y):
self.gtype = p_gtype
self.x = p_x
self.y = p_y
class VoTsx:
def __init__(self, p_name):
self.name = p_name
self.pros_map = {}
def show_log(msg):
print('\tLOG:', msg)
def show_error(msg):
print('\tERROR:', msg)
def show_warning(msg):
print('\tWARNING:', msg)
def run(p_source):
PathFinder.I().init(15, 22)
PathFinder.I().set8Or4Dir(False)
# 读取目录下所有的tmx文件
list_file = p_source.rglob('*.tmx')
total_tmx_cnt = 0 # 总共的tmx文件数
right_tmx_cnt = 0 # 正确的tmx文件数
tsx_map = {}
# 读取目录下所有的tsx文件
list_tsx = p_source.rglob('*.tsx')
for tsx in list_tsx:
vo_tsx = VoTsx(tsx.name)
tsx_map[tsx.name] = vo_tsx
tsx_tree = ET.parse(tsx)
tsx_tileset = tsx_tree.getroot()
# 查找所有tile标签
tiles = tsx_tree.findall('tile')
# print(tiles)
for tile in tiles:
tile_id = int(tile.attrib['id'])
pros_map = {}
tile_properties = tile.findall('./properties/property')
for tile_pro in tile_properties:
if 'type' in tile_pro.attrib:
pro_type = tile_pro.attrib['type']
if pro_type == 'int':
val = int(tile_pro.attrib['value'])
elif pro_type == 'bool':
val = tile_pro.attrib['value'] == 'true'
# elif pro_type == 'color':
# val = tile_pro.attrib['value']
# elif pro_type == 'file':
# val = tile_pro.attrib['value']
elif pro_type == 'float':
val = float(tile_pro.attrib['value'])
else:
val = tile_pro.attrib['value']
else:
val = tile_pro.attrib['value']
pros_map[tile_pro.attrib['name']] = val
vo_tsx.pros_map[tile_id] = pros_map
# print(vo_tsx.pros_map)
# for k, v in tsx_map.items():
# print(k, v.pros_map)
for f in list_file:
error_flag = False
total_tmx_cnt += 1
tmx_name = f.name
tree = ET.parse(f)
root = tree.getroot()
tilew = int(root.attrib['tilewidth'])
tileh = int(root.attrib['tileheight'])
parent_map = {c: p for p in tree.iter() for c in p}
firstgids = []
# 查找所有tileset标签
tilesets = tree.findall('tileset')
for tileset in tilesets:
# 读取tileset标签的firstgid属性
firstgid_val = int(tileset.attrib['firstgid'])
tsx_name = tileset.attrib['source']
firstgids.append({'firstgid': firstgid_val, 'tsx': tsx_name})
# print(firstgid_val)
# print(firstgids)
def get_gtype_by(p_gid):
for firstgid in firstgids:
if p_gid >= firstgid['firstgid']:
vo_tsx = tsx_map[firstgid['tsx']]
# print(vo_tsx.name)
tile_id = p_gid - firstgid['firstgid']
# print(tile_id)
if tile_id in vo_tsx.pros_map:
pros_map = vo_tsx.pros_map[tile_id]
if 'gType' in pros_map:
return pros_map['gType']
return None
# 查找name属性值为“obstacle”的layer标签
obstacle_layer = tree.find("./layer[@name='obstacle']")
w_cnt = int(obstacle_layer.attrib['width'])
h_cnt = int(obstacle_layer.attrib['height'])
# print(w_cnt, h_cnt)
obstacle_data = obstacle_layer.find('data')
# print(obstacle_data.text)
data_text = obstacle_data.text
# 去除data文本的空白字符
data_text = ''.join(data_text.split())
data_list = data_text.split(',')
data_list = [int(x) for x in data_list]
# 把data_list转换成 tiled_w * tiled_h 的二维数组
data_list = [data_list[i:i + w_cnt] for i in range(0, len(data_list), w_cnt)]
# print(data_list)
# print(len(data_list), len(data_list[0]))
result_list = [[row[i] for row in data_list] for i in range(len(data_list[0]))]
# print(result_list)
# print(len(result_list), len(result_list[0]))
PathFinder.I().init(w_cnt, h_cnt)
PathFinder.I().set8Or4Dir(False)
for x in range(w_cnt):
for y in range(h_cnt):
if result_list[x][y] > 0:
PathFinder.I().setObstacle(x, y, 1)
# print(PathFinder.I().isObstacle(1, 10))
# print(len(PathFinder.I().grid))
# 查找name属性值为“object”的objectgroup标签
object_layer = tree.find("./objectgroup[@name='object']")
# 找到objectgroup标签下的所有object标签
objects = object_layer.findall('object')
# print(objects)
obj_vos: List[VoObject] = []
vo_born: VoObject = None
vo_door: VoObject = None
for obj in range(len(objects)):
offset_x = int(objects[obj].attrib['x'])
offset_y = int(objects[obj].attrib['y'])
gid = int(objects[obj].attrib['gid'])
gtype = get_gtype_by(gid)
# print(offset_x, offset_y)
obj_tilex = math.floor(offset_x / tilew)
obj_tiley = math.floor(offset_y / tileh) - 1
# print(obj_tilex, obj_tiley)
vo_obj = VoObject(gtype, obj_tilex, obj_tiley)
vo_obj.id = int(objects[obj].attrib['id'])
vo_obj.obj_name = objects[obj].attrib['name']
if PathFinder.I().isObstacle(obj_tilex, obj_tiley):
show_warning(
f'{tmx_name} 对象放在了障碍物的坐标上了 坐标=({vo_obj.x},{vo_obj.y}),name={vo_obj.obj_name},id={vo_obj.id}')
error_flag = True
if gtype == GType.Born.value:
vo_born = vo_obj
elif gtype == GType.Door.value:
vo_door = vo_obj
obj_vos.append(vo_obj)
# PathFinder.I().findPath()
if vo_born is None:
show_error(f'{tmx_name} 没有出生点 ')
error_flag = True
continue
if vo_door is None:
show_error(f'{tmx_name} 没有传送门 ')
error_flag = True
for obj in obj_vos:
if obj.gtype == GType.Born.value:
continue
# 寻找传送门到对象的路径
path = PathFinder.I().findPath([vo_born.x, vo_born.y], [obj.x, obj.y])
# print(path)
if len(path) == 0:
show_warning(f'{tmx_name} 有对象找不到路径,坐标=({obj.x},{obj.y}),name={obj.obj_name},id={obj.id}')
error_flag = True
# break
if error_flag:
continue
right_tmx_cnt += 1
# break
error_cnt = total_tmx_cnt - right_tmx_cnt
print(f'...检查完毕...总共的tmx文件数={total_tmx_cnt},错误的tmx文件数={error_cnt}')
if error_cnt > 0:
print('...tmx存在错误!!! 请修改错误后再导出tmx文件...')
return False
else:
print('...tmx检查完毕,没有错误,可以导出tmx文件...')
return True
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='帮助信息')
parser.add_argument('--source', type=str, default='', help='tmx源目录')
parser.add_argument('--out', type=str, default='', help='tmx输出目录')
args = parser.parse_args()
if args.source != '':
source = Path(args.source)
else:
source = Path('./tiledmap/map_source')
if args.out != '':
out = Path(args.out)
else:
out = Path('./tiledmap/map_out')
print('开始检查tmx文件...')
if run(source):
print('\n开始导出tmx文件...')
tiledmap_export.run(source, out)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。