conv_lvs.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. #!/usr/local/bin/python3
  2. # -*- coding: utf-8 -*-
  3. # File: conv_lvs.py
  4. # Desc: 将用到的关卡文件压缩成程序使用的protobuf格式
  5. # Date: 2017-04-18
  6. import os
  7. import sys
  8. import json
  9. import levelData_pb2
  10. import time
  11. def read_json(file_path):
  12. with open(file_path, 'r') as file:
  13. return json.load(file)
  14. def write_json(data, file_path):
  15. with open(file_path, 'w+') as file:
  16. json.dump(data, file, indent=4)
  17. # 将 ../conf/levelInstInfo.csv 里面的每一个实例的模板名加上“tf_”前缀
  18. def add_prefix_to_inst_name():
  19. with open('../conf/levelInstInfo.csv', 'r') as f:
  20. hdr_done = False
  21. with open('../conf/levelInstInfo_new.csv', 'w+') as f_new:
  22. for line in f.readlines():
  23. if line.startswith('#'):
  24. f_new.write(line)
  25. continue
  26. if not hdr_done:
  27. f_new.write(line)
  28. hdr_done = True
  29. continue
  30. line = line.split(',')
  31. if line[1].startswith('tf_'):
  32. f_new.write(line)
  33. continue
  34. line[1] = 'tf_' + line[1]
  35. f_new.write(','.join(line))
  36. # 解析 ../conf/levelInstInfo.csv 文件,得到所有被用到的模板列表
  37. def parse_level_inst_info(groups):
  38. temp_list = set()
  39. for grp in groups:
  40. fn = '../conf/levelInstInfo.csv'
  41. if len(grp) != 0:
  42. fn = '../conf/levelInstInfo-' + grp + '.csv'
  43. with open(fn, 'r') as f:
  44. # 跳过第一行
  45. f.readline()
  46. for line in f.readlines():
  47. if line.startswith('#'):
  48. continue
  49. temp_list.add(line.split(',')[1])
  50. return temp_list
  51. # 将某个tiled的json文件转换成protobuf格式
  52. def conver_json_to_proto(tDir, tFileName, outDir):
  53. global lvFileInfo, lvInfo
  54. jsonFile = os.path.join(tDir, tFileName + '.json')
  55. with open(jsonFile, 'r') as f:#, encoding='utf-8'
  56. jsonData = json.load(f)
  57. levelData = levelData_pb2.LevelData()
  58. levelData.tileWidth = jsonData['width']
  59. levelData.tileHeight = jsonData['height']
  60. cntUndefined = 0
  61. tilesByPos = {}
  62. maxZ = 0
  63. maxX = 0
  64. maxY = 0
  65. # 首先得到stacked的坐标数据
  66. stacked_map = {}
  67. for item in jsonData['layers']:
  68. name = item['name']
  69. if name == 'Marks':
  70. data = item['data']
  71. for i in range(0, len(data)):
  72. if data[i] == 0:
  73. continue
  74. x = (int)(i % levelData.tileWidth)
  75. y = (int)(i / (int)(levelData.tileWidth))
  76. stacked_map[(x, y)] = 0
  77. # 处理各层的tile数据,并去掉中间的空层
  78. cntTiles = 0
  79. z = 0
  80. for item in jsonData['layers']:
  81. name = item['name']
  82. if not name.startswith('Tile_'):
  83. # stacked
  84. continue
  85. data = item['data']
  86. is_empty = True
  87. for i in range(0, len(data)):
  88. if data[i] == 0:
  89. continue
  90. is_empty = False
  91. tile = levelData.tiles.add()
  92. tile.x = (int)(i % levelData.tileWidth)
  93. tile.y = (int)(i / (int)(levelData.tileWidth))
  94. tile.z = z
  95. if (tile.x, tile.y) in stacked_map:
  96. stacked_map[(x, y)] += 1
  97. tiledata = tile.tileData
  98. tiledata.zv = 0
  99. tiledata.weight = 0
  100. tiledata.id = data[i]
  101. if tiledata.id == -10:
  102. cntUndefined += 1
  103. tiledata.type = 1
  104. tiledata.subtype = 0
  105. # 一些统计信息
  106. tilesByPos[(tile.x, tile.y, tile.z)] = tile
  107. if tile.z > maxZ:
  108. maxZ = tile.z
  109. if tile.x > maxX:
  110. maxX = tile.x
  111. if tile.y > maxY:
  112. maxY = tile.y
  113. cntTiles += 1
  114. if not is_empty:
  115. z += 1
  116. print('Tile 总数: %d' % (cntTiles,))
  117. print('Tile 总数: %d' % (cntTiles,))
  118. print('Tile 总数: %d' % (cntTiles,))
  119. if cntTiles%3 != 0:
  120. print('Error: Tile总数不是3的倍数 3: %d' % (cntTiles,))
  121. exit(1)
  122. # 检查每层的tile是否有重叠
  123. pos_conflict = {}
  124. for z in range(0, maxZ+1):
  125. for x in range(0, maxX+1):
  126. for y in range(0, maxY+1):
  127. if (x,y,z) in tilesByPos:
  128. adjs = [(0,1),(0,-1),(1,0),(-1,0),(1,1),(1,-1),(-1,1),(-1,-1)]
  129. for adj in adjs:
  130. p = (x+adj[0],y+adj[1],z)
  131. if p in tilesByPos:
  132. if (x,y) not in pos_conflict:
  133. pos_conflict[(x,y,z)] = []
  134. pos_conflict[(x,y,z)].append(p)
  135. for p,ps in pos_conflict.items():
  136. print("位置冲突:", p, ps)
  137. if len(pos_conflict) > 0:
  138. exit(1)
  139. # 刷新io缓冲区
  140. sys.stdout.flush()
  141. time.sleep(3)
  142. # 根据上面的布局信息,计算每个tile的几个信息:
  143. # 1. 每个tile的视觉层级(不同于上面z的信息,那是一个布局信息,相同的z可能出在不同的视觉层级)
  144. # 从maxZ开始,逐层向下计算;对于每一个位置,如果该位置有tile,则计算该tile的视觉层级
  145. # 对于每一个tile,如果其上方(从该tile的z+1层,一直到maxZ层)有tile,则其视觉层级为上方tile的视觉层级+1
  146. for z in range(0, maxZ+1):
  147. z = maxZ - z
  148. for x in range(0, maxX+1):
  149. for y in range(0, maxY+1):
  150. if (x,y,z) in tilesByPos:
  151. tile = tilesByPos[(x, y, z)]
  152. adjs = [(x,y),(x,y+1),(x,y-1),(x+1,y),(x+1,y+1),(x+1,y-1),(x-1,y),(x-1,y-1),(x-1,y+1)]
  153. # 确定视觉层级
  154. zvMax = -1
  155. for zup in range(z+1, maxZ+1):
  156. for adj in adjs:
  157. if (adj[0],adj[1],zup) in tilesByPos:
  158. zv = tilesByPos[adj[0],adj[1],zup].tileData.zv
  159. if zv > zvMax:
  160. zvMax = zv
  161. tile.tileData.zv = zvMax + 1
  162. # 计算权重
  163. # 从最底下开始,逐层向上计算
  164. # 每个tile的初始权重为4,然后加上其下方压住的tile的权重:如果压了全部,则该加上被压住的tile权重;
  165. # 如果压住了一半,则加上被压住的tile权重的一半;如果是压住1/4,则加上被压住的tile权重的1/4
  166. for z in range(0, maxZ+1):
  167. for x in range(0, maxX+1):
  168. for y in range(0, maxY+1):
  169. if (x,y,z) in tilesByPos:
  170. tile = tilesByPos[(x,y,z)]
  171. adjs = [(x,y,1.0),(x,y+1,0.5),(x,y-1,0.5),(x+1,y,0.5),(x+1,y+1,0.25),(x+1,y-1,0.25),(x-1,y,0.5),(x-1,y-1,0.25),(x-1,y+1,0.25)]
  172. weight = 4
  173. for adj in adjs:
  174. # 从临近层往下,在某个位置找到了tile,则该tile被压住了,就不再找了
  175. for zb in range(0, z):
  176. zb = z-1-zb
  177. if (adj[0], adj[1], zb) in tilesByPos:
  178. tileB = tilesByPos[(adj[0], adj[1], zb)]
  179. weight += tileB.tileData.weight * adj[2]
  180. break
  181. tile.tileData.weight = int(weight)
  182. pass
  183. for item in stacked_map:
  184. stack_data = levelData.stacks.add()
  185. stack_data.x = int(item[0])
  186. stack_data.y = int(item[1])
  187. stack_data.direction = 0 # 先默认都是0
  188. # 数据序列化
  189. level_protobuf_data = levelData.SerializeToString()
  190. protoFile = os.path.join(outDir, tFileName + '.bin')
  191. with open(protoFile, 'w+b') as f:
  192. f.write(level_protobuf_data)
  193. def gen_index_and_data(binDir, group = None):
  194. lvelsIndex = levelData_pb2.LevelsIndex()
  195. if group is not None:
  196. protoDataTrunk = os.path.join(binDir, 'levels-' + str(group) + '.bin')
  197. protoDataIndex = os.path.join(binDir, 'levelsIndex-' + str(group) + '.bin')
  198. else:
  199. protoDataTrunk = os.path.join(binDir, 'levels' + '.bin')
  200. protoDataIndex = os.path.join(binDir, 'levelsIndex' + '.bin')
  201. offset = 0
  202. for filename in os.listdir(binDir):
  203. my_message = levelData_pb2.LevelData()
  204. binFile = os.path.splitext(filename)[0] + '.bin'
  205. binFile = os.path.join(binDir, binFile)
  206. if os.path.exists(binFile) :
  207. # print(binFile)
  208. with open(binFile, "rb") as f:
  209. binary_data = f.read()
  210. my_message.ParseFromString(binary_data)
  211. os.remove(binFile)
  212. with open(protoDataTrunk, 'ab') as f:
  213. f.write(binary_data)
  214. lvelsIndex.LevelsIndex[os.path.splitext(filename)[0]].len = len(binary_data)
  215. lvelsIndex.LevelsIndex[os.path.splitext(filename)[0]].offset = offset
  216. offset += len(binary_data)
  217. level_protobuf_index = lvelsIndex.SerializePartialToString()
  218. with open(protoDataIndex, 'w+b') as f:
  219. f.write(level_protobuf_index)
  220. # 将所有的模板文件转换成protobuf格式
  221. def convert_all_templates():
  222. # 清空loadable目录
  223. for root, dirs, files in os.walk('../loadable'):
  224. for name in files:
  225. os.remove(os.path.join(root, name))
  226. temp_list = parse_level_inst_info(['','3'])
  227. dirs = ['../tf_templates', '../miniGame', '../templates']
  228. for temp_name in temp_list:
  229. print(f"Converting template {temp_name}")
  230. # 判断正确的目录
  231. for d in dirs:
  232. if os.path.exists(os.path.join(d, temp_name + '.json')):
  233. conver_json_to_proto(d, temp_name, '../loadable')
  234. break
  235. gen_index_and_data('../loadable')
  236. # 修改levelInstInfo.csv文件: 对于每一行,如果其try1一直到try10的随机数都是100,则一次将其加1
  237. def correct_random_seed():
  238. for f in ['../conf/levelInstInfo.csv', '../conf/levelInstInfo-2.csv']:
  239. lines = None
  240. with open(f, 'r') as file:
  241. lines = file.readlines()
  242. with open(f, 'w') as file:
  243. for line in lines:
  244. if line.startswith('#'):
  245. file.write(line)
  246. continue
  247. fs = line.split(',')
  248. if len(fs) < 12:
  249. file.write(line)
  250. continue
  251. for i in range(2, 12):
  252. if fs[i].split('|')[1] == '100':
  253. fs[i] = fs[i].replace('100', '%d' % (100+i))
  254. file.write(','.join(fs))
  255. # 将关卡实例每个关卡单独保存一个,避免改动的时候相互影响
  256. def split_level_inst_info():
  257. # 首先读取levelInfo.csv文件,对于每一个关卡,解析其所使用的实例名字信息
  258. # 然后读取levelInstInfo.csv文件,得到每一个实例的详细信息
  259. # 新建一个levelInstInfo-new.csv文件,对于每一个关卡,将其所使用的实例的信息改名成inst-关卡名,并存入上一步的详细信息
  260. # 最后将levelInstInfo-new.csv文件保存
  261. lvs_inst = {}
  262. with open('../conf/levelInfo-2.csv', 'r') as file:
  263. for line in file.readlines():
  264. if line.startswith('#'):
  265. continue
  266. fs = line.split(',')
  267. lvs_inst[fs[0]] = (fs[1], fs)
  268. inst_info = {}
  269. with open('../conf/levelInstInfo-2.csv', 'r') as file:
  270. for line in file.readlines():
  271. if line.startswith('#'):
  272. continue
  273. fs = line.split(',')
  274. inst_info[fs[0]] = ",".join(fs[1:])
  275. # 更新info
  276. with open('../conf/levelInfo-2-new.csv', 'w') as file:
  277. for lv, (inst,li) in lvs_inst.items():
  278. li[1] = 'inst-%s' % (lv,)
  279. file.write(",".join(li))
  280. # 写入实例信息
  281. with open('../conf/levelInstInfo-2-new.csv', 'w') as file:
  282. for lv, (inst,li) in lvs_inst.items():
  283. if inst in inst_info:
  284. file.write("inst-%s,%s" % (lv, inst_info[inst]))
  285. else:
  286. print(f"Error: {inst} not found in levelInstInfo.csv")
  287. pass
  288. # 更新关卡实例的随机值
  289. def updateRandomSeed(lvInfoFN, instInfoFN, seedsInfo):
  290. # seedsInfo每一行的格式:[Tile] resovler id: 31,37710|539110|14697|465925|662106|151463|667120|234313|60714|840794|
  291. # 解析seedsInfo,得到关卡和实例的时机值
  292. seeds = {}
  293. for line in seedsInfo.split('\n'):
  294. fs = line.split(' ')
  295. if len(fs) < 4:
  296. continue
  297. fs = fs[3].split(',')
  298. seeds[fs[0]] = fs[1].split('|')
  299. # 先通过lvInfoFN文件,得到所有的关卡对应的实例名称
  300. seeds4Inst = {}
  301. with open(lvInfoFN, 'r') as file:
  302. for line in file.readlines():
  303. if line.startswith('#'):
  304. continue
  305. fs = line.split(',')
  306. if fs[0] in seeds:
  307. seeds4Inst[fs[1]] = seeds[fs[0]]
  308. # 对于instInfoFN文件,对于每一行,如果lvid在seeds里面,则用上面的随机值来更新一下;否则原封不动写入新文件
  309. with open(instInfoFN, 'r') as infile:
  310. lines = infile.readlines()
  311. with open(instInfoFN + '-new', 'w') as outfile:
  312. for line in lines:
  313. fs = line.split(',')
  314. if len(fs) < 2:
  315. outfile.write(line)
  316. continue
  317. if fs[0] in seeds4Inst:
  318. for i in range(2, 12):
  319. v = seeds4Inst[fs[0]][i-2].split('-')
  320. ps = fs[i].split('|')
  321. ps[0] = v[0]
  322. ps[1] = v[1]
  323. fs[i] = '|'.join(ps)
  324. outfile.write(','.join(fs))
  325. # 将instInfoFN-new文件重命名为instInfoFN
  326. os.rename(instInfoFN + '-new', instInfoFN)
  327. pass
  328. # 将所有的关卡文件中tile的尺寸调整一下
  329. def adjust_tile_size(templates_dir):
  330. # 遍历目录中的所有json文件,将属性 tileheight 和 tilewidth 调整为 40;
  331. # 将tilesets中所有元素的 imageheight 和 imagewidth 调整为 800, tilecount 调整为 100, tileheight 和 tilewidth 调整为 80
  332. for root, dirs, files in os.walk(templates_dir):
  333. for name in files:
  334. if not name.endswith('.json'):
  335. continue
  336. jsonFile = os.path.join(root, name)
  337. with open(jsonFile, 'r') as f:
  338. jsonData = json.load(f)
  339. jsonData['tilewidth'] = 40
  340. jsonData['tileheight'] = 40
  341. for tileset in jsonData['tilesets']:
  342. tileset['imageheight'] = 800
  343. tileset['imagewidth'] = 800
  344. tileset['tilecount'] = 100
  345. tileset['tileheight'] = 80
  346. tileset['tilewidth'] = 80
  347. with open(jsonFile, 'w') as f:
  348. json.dump(jsonData, f, indent=4)
  349. if __name__ == '__main__':
  350. # 获取当前工作目录
  351. current_dir = os.getcwd()
  352. script_dir = os.path.dirname(os.path.abspath(__file__))
  353. os.chdir(script_dir)
  354. # 得到可以发布的protobuf文件
  355. # convert_all_templates()
  356. # correct_random_seed()
  357. # split_level_inst_info()
  358. # 更新随机信息
  359. seedsInfo = """
  360. """
  361. #updateRandomSeed('../conf/levelInfo-3.csv', '../conf/levelInstInfo-3.csv', seedsInfo)
  362. adjust_tile_size("../tf_templates")
  363. # 恢复当前工作目录
  364. os.chdir(current_dir)