tiled_map_reader.gd 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652
  1. # The MIT License (MIT)
  2. #
  3. # Copyright (c) 2018 George Marques
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in all
  13. # copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. @tool
  23. extends RefCounted
  24. class_name TiledMapReader
  25. # Constants for tile flipping
  26. # http://doc.mapeditor.org/reference/tmx-map-format/#tile-flipping
  27. const FLIPPED_HORIZONTALLY_FLAG = 0x80000000
  28. const FLIPPED_VERTICALLY_FLAG = 0x40000000
  29. const FLIPPED_DIAGONALLY_FLAG = 0x20000000
  30. # Prefix for error messages, make easier to identify the source
  31. const error_prefix = "Tiled Importer: "
  32. # Properties to save the value in the metadata
  33. const whitelist_properties = [
  34. "backgroundcolor",
  35. "compression",
  36. "draworder",
  37. "gid",
  38. "height",
  39. "imageheight",
  40. "imagewidth",
  41. "infinite",
  42. "margin",
  43. "name",
  44. "orientation",
  45. "probability",
  46. "spacing",
  47. "tilecount",
  48. "tiledversion",
  49. "tileheight",
  50. "tilewidth",
  51. "type",
  52. "version",
  53. "visible",
  54. "width",
  55. "custom_material",
  56. ]
  57. # All templates loaded, can be looked up by path name
  58. var _loaded_templates = {}
  59. # Maps each tileset file used by the map to it's first gid; Used for template parsing
  60. var _tileset_path_to_first_gid = {}
  61. # Custom objects
  62. var spawn_pool : Array = []
  63. var warp_pool : Array = []
  64. var port_pool : Array = []
  65. # JSON Instance
  66. var JSONInstance = JSON.new()
  67. # Tile DB
  68. var tileDic : Dictionary = {}
  69. # Tile Specific Nodes
  70. var specificDic : Dictionary = {}
  71. # Navigation mesh variables
  72. var cell_size = Vector2.ZERO
  73. var map_width = 0
  74. var map_height = 0
  75. var map_flags = WorldMap.Flags.NONE
  76. var nav_region : NavigationRegion2D = NavigationRegion2D.new()
  77. var map_boundaries : Rect2 = Rect2()# Collision polygons
  78. var source_data : NavigationMeshSourceGeometryData2D = NavigationMeshSourceGeometryData2D.new()
  79. func reset_global_memebers():
  80. _loaded_templates = {}
  81. _tileset_path_to_first_gid = {}
  82. # Main functions
  83. # Reads a source file and gives back a scene
  84. func build_client(source_path, options) -> Node2D:
  85. reset_global_memebers()
  86. var map = read_file(source_path)
  87. if typeof(map) == TYPE_INT:
  88. return map
  89. if typeof(map) != TYPE_DICTIONARY:
  90. return null
  91. var err = validate_map(map)
  92. if err != OK:
  93. return err
  94. var map_mode = TileSet.TILE_SHAPE_SQUARE
  95. var map_pos_offset = Vector2()
  96. var map_background = Color()
  97. var cell_offset = Vector2()
  98. cell_size = Vector2(int(map.tilewidth), int(map.tileheight))
  99. map_width = 0
  100. map_height = 0
  101. if "width" in map:
  102. map_width = map.width
  103. if "height" in map:
  104. map_height = map.height
  105. if "orientation" in map:
  106. match map.orientation:
  107. "isometric":
  108. map_mode = TileSet.TILE_SHAPE_ISOMETRIC
  109. "staggered":
  110. map_pos_offset.y -= cell_size.y / 2
  111. match map.staggeraxis:
  112. "x":
  113. cell_size.x /= 2.0
  114. if map.staggerindex == "even":
  115. cell_offset.x += 1
  116. map_pos_offset.x -= cell_size.x
  117. "y":
  118. cell_size.y /= 2.0
  119. if map.staggerindex == "even":
  120. cell_offset.y += 1
  121. map_pos_offset.y -= cell_size.y
  122. "hexagonal":
  123. # Godot maps are always odd and don't have an "even" setting. To
  124. # imitate even staggering we simply start one row/column late and
  125. # adjust the position of the whole map.
  126. match map.staggeraxis:
  127. "x":
  128. cell_size.x = int((cell_size.x + map.hexsidelength) / 2)
  129. if map.staggerindex == "even":
  130. cell_offset.x += 1
  131. map_pos_offset.x -= cell_size.x
  132. "y":
  133. cell_size.y = int((cell_size.y + map.hexsidelength) / 2)
  134. if map.staggerindex == "even":
  135. cell_offset.y += 1
  136. map_pos_offset.y -= cell_size.y
  137. var root = Node2D.new()
  138. root.set_name(source_path.get_file().get_basename())
  139. if options.save_tiled_properties:
  140. set_tiled_properties_as_meta(root, map)
  141. if options.custom_properties:
  142. set_custom_properties(root, map)
  143. var tileset = build_tileset_for_scene(map.tilesets, source_path, options, root)
  144. if typeof(tileset) != TYPE_OBJECT:
  145. # Error happened
  146. return tileset
  147. var mapData = {
  148. "options": options,
  149. "map_mode": map_mode,
  150. "map_pos_offset": map_pos_offset,
  151. "map_background": map_background,
  152. "cell_size": cell_size,
  153. "cell_offset": cell_offset,
  154. "tileset": tileset,
  155. "source_path": source_path,
  156. "infinite": bool(map.infinite) if "infinite" in map else false
  157. }
  158. var zOrder = 0
  159. tileset.tile_size = cell_size
  160. # Set zOrders
  161. for tmxLayer in map.layers:
  162. if tmxLayer.name == "Fringe":
  163. break
  164. else:
  165. zOrder -= 1
  166. # Add each layers
  167. for tmxLayer in map.layers:
  168. var layer : TileMapLayer = make_layer(tmxLayer, root, mapData, zOrder)
  169. if layer:
  170. map_boundaries = map_boundaries.merge(layer.get_used_rect())
  171. root.add_child(layer)
  172. layer.set_owner(root)
  173. zOrder += 1
  174. # Set metadata
  175. map_boundaries.position.x *= cell_size.x
  176. map_boundaries.end.x *= cell_size.x
  177. map_boundaries.position.y *= cell_size.y
  178. map_boundaries.end.y *= cell_size.y
  179. root.set_meta("MapBoundaries", map_boundaries)
  180. # Background color
  181. if options.add_background and "backgroundcolor" in map:
  182. var bg_color = str(map.backgroundcolor)
  183. if (!bg_color.is_valid_html_color()):
  184. print_error("Invalid background color format: " + bg_color)
  185. return root
  186. map_background = Color(bg_color)
  187. var viewport_size = Vector2(ProjectSettings.get("display/window/size/width"), ProjectSettings.get("display/window/size/height"))
  188. var parbg = ParallaxBackground.new()
  189. var parlayer = ParallaxLayer.new()
  190. var colorizer = ColorRect.new()
  191. parbg.scroll_ignore_camera_zoom = true
  192. parlayer.motion_mirroring = viewport_size
  193. colorizer.color = map_background
  194. colorizer.rect_size = viewport_size
  195. colorizer.rect_min_size = viewport_size
  196. parbg.name = "Background"
  197. root.add_child(parbg)
  198. parbg.owner = root
  199. parlayer.name = "BackgroundLayer"
  200. parbg.add_child(parlayer)
  201. parlayer.owner = root
  202. colorizer.name = "BackgroundColor"
  203. parlayer.add_child(colorizer)
  204. colorizer.owner = root
  205. return root
  206. func build_navigation() -> Node2D:
  207. nav_region.set_name("NavRegion")
  208. nav_region.navigation_polygon = NavigationPolygon.new()
  209. nav_region.navigation_polygon.set_source_geometry_mode(NavigationPolygon.SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN)
  210. nav_region.navigation_polygon.add_outline(PackedVector2Array([Vector2(map_boundaries.position.x, map_boundaries.position.y), Vector2(map_boundaries.position.x, map_boundaries.end.y), Vector2(map_boundaries.end.x, map_boundaries.end.y), Vector2(map_boundaries.end.x, map_boundaries.position.y)]))
  211. nav_region.navigation_polygon.set_agent_radius(10 if cell_size.y == 32 else 4)
  212. return nav_region
  213. func fill_polygon_pool(tileset : TileSet, cell_pos : Vector2, gid : int):
  214. var layer_id : int = tileDic[gid][0]
  215. var atlas_pos : Vector2i = tileDic[gid][1]
  216. var ts_atlas : TileSetAtlasSource = tileset.get_source(layer_id)
  217. var tile_data : TileData = ts_atlas.get_tile_data(atlas_pos, 0)
  218. var tile_polygon_count : int = tile_data.get_collision_polygons_count(0)
  219. for tile_polygon in tile_polygon_count:
  220. var polygon : PackedVector2Array = tile_data.get_collision_polygon_points(0, tile_polygon)
  221. if polygon.size() > 0:
  222. var last_vertex = Vector2(-1, -1)
  223. var first_vertex = polygon[0]
  224. var filtered_polygon : PackedVector2Array = []
  225. for vertex in polygon.size():
  226. if last_vertex != polygon[vertex]:
  227. last_vertex = polygon[vertex]
  228. if vertex != polygon.size() - 1 || vertex == polygon.size() - 1 && polygon[vertex] != first_vertex:
  229. filtered_polygon.append(polygon[vertex] + cell_pos + cell_size / 2.0)
  230. source_data.add_obstruction_outline(filtered_polygon)
  231. # Reads a collision pool and create a navigation mesh
  232. func build_server(source_path) -> Node:
  233. var root = MapServerData.new()
  234. root.set_name(source_path.get_file().get_basename())
  235. root.flags = map_flags
  236. # Can't save an array of custom objects, every element will be null when loaded
  237. # root.spawns = spawn_pool
  238. for spawn in spawn_pool:
  239. var spawn_array : Array = []
  240. spawn_array.append(spawn.count)
  241. spawn_array.append(spawn.name)
  242. spawn_array.append(spawn.type)
  243. spawn_array.append(spawn.spawn_position)
  244. spawn_array.append(spawn.spawn_offset)
  245. spawn_array.append(spawn.respawn_delay)
  246. spawn_array.append(spawn.player_script)
  247. spawn_array.append(spawn.own_script)
  248. spawn_array.append(spawn.nick)
  249. root.spawns.append(spawn_array)
  250. # Can't save an array of custom objects, every element will be null when loaded
  251. for warp in warp_pool:
  252. var warp_polygon : PackedVector2Array = []
  253. for warp_vertex in warp.polygon:
  254. warp_polygon.append(warp_vertex + warp.position)
  255. var warp_array : Array = []
  256. warp_array.append(warp.destinationMap)
  257. warp_array.append(warp.destinationPos)
  258. warp_array.append(warp_polygon)
  259. warp_array.append(warp.autoWarp)
  260. root.warps.append(warp_array)
  261. for port in port_pool:
  262. var port_polygon : PackedVector2Array = []
  263. for port_vertex in port.polygon:
  264. port_polygon.append(port_vertex + port.position)
  265. var port_array : Array = []
  266. port_array.append(port.destinationMap)
  267. port_array.append(port.destinationPos)
  268. port_array.append(port_polygon)
  269. port_array.append(port.autoWarp)
  270. port_array.append(port.sailingPos)
  271. root.ports.append(port_array)
  272. return root
  273. # Specific nodes to add per tiles (i.e.: Particle effects, light sources, etc...)
  274. func add_specific_nodes(parent : Node2D, cell_in_map : Vector2, gid : int):
  275. if gid in specificDic and specificDic[gid].size() > 0:
  276. var specificGid = specificDic[gid]
  277. match specificGid[0]:
  278. "LightSource":
  279. var lighting : CanvasLayer = parent.get_node_or_null("LightingLayer")
  280. if lighting:
  281. var lightSource : LightSource = LightSource.new()
  282. if lightSource:
  283. lightSource.position = cell_in_map
  284. lightSource.position.x += specificGid[1].x
  285. lightSource.position.y -= specificGid[1].y / 2.0
  286. lightSource.speed = specificGid[2]
  287. lightSource.radius = specificGid[3]
  288. lightSource.color = specificGid[4]
  289. lighting.add_child(lightSource)
  290. lightSource.set_owner(parent)
  291. "FX":
  292. var fx : Node2D = FileSystem.LoadEffect(specificGid[2])
  293. if fx:
  294. fx.z_index = 10
  295. fx.position = cell_in_map
  296. fx.position.x += specificGid[1].x
  297. fx.position.y += specificGid[1].y
  298. var effects : Node2D = parent.get_node_or_null("Effects")
  299. if not effects:
  300. effects = Node2D.new()
  301. effects.name = "Effects"
  302. parent.add_child(effects)
  303. effects.set_owner(parent)
  304. effects.add_child(fx)
  305. fx.set_owner(parent)
  306. # Creates a layer node from the data
  307. # Returns a TileMapLayer on success or null if an error happen
  308. func make_layer(tmxLayer, parent, data, zindex) -> TileMapLayer:
  309. var err = validate_layer(tmxLayer)
  310. if err != OK:
  311. return null
  312. # Main map data
  313. var map_pos_offset = data.map_pos_offset
  314. var cell_size = data.cell_size
  315. var cell_offset = data.cell_offset
  316. var options = data.options
  317. var tileset = data.tileset
  318. var source_path = data.source_path
  319. var infinite = data.infinite
  320. var opacity = float(tmxLayer.opacity) if "opacity" in tmxLayer else 1.0
  321. var visible = bool(tmxLayer.visible) if "visible" in tmxLayer else true
  322. var layer : TileMapLayer = TileMapLayer.new()
  323. layer.set_name(tmxLayer.name)
  324. layer.set_tile_set(tileset)
  325. layer.set_navigation_enabled(false)
  326. layer.add_to_group("navigation_polygon_source_geometry_group", true)
  327. if tmxLayer.type == "tilelayer":
  328. layer.set_modulate(Color(1.0, 1.0, 1.0, opacity))
  329. layer.set_enabled(visible)
  330. layer.set_z_index(zindex)
  331. if "Fringe" in tmxLayer.name:
  332. layer.set_y_sort_enabled(true)
  333. layer.set_y_sort_origin(cell_size.y / 2)
  334. var offset = Vector2()
  335. if "offsetx" in tmxLayer:
  336. offset.x = int(tmxLayer.offsetx)
  337. if "offsety" in tmxLayer:
  338. offset.y = int(tmxLayer.offsety)
  339. var chunks = []
  340. if infinite:
  341. chunks = tmxLayer.chunks
  342. else:
  343. chunks = [tmxLayer]
  344. for chunk in chunks:
  345. err = validate_chunk(chunk)
  346. if err != OK:
  347. return null
  348. var chunk_data = chunk.data
  349. if "encoding" in tmxLayer and tmxLayer.encoding == "base64":
  350. if "compression" in tmxLayer:
  351. var layer_size : Vector2 = Vector2(int(tmxLayer.width), int(tmxLayer.height))
  352. chunk_data = decompress_layer_data(chunk.data, tmxLayer.compression, layer_size)
  353. if typeof(chunk_data) == TYPE_INT:
  354. return null
  355. else:
  356. chunk_data = read_base64_layer_data(chunk.data)
  357. var count = 0
  358. for tile_id in chunk_data:
  359. var int_id = str(tile_id).to_int() & 0xFFFFFFFF
  360. if int_id == 0:
  361. count += 1
  362. continue
  363. var gid = int_id & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)
  364. var cell_x = cell_offset.x + chunk.x + (count % int(chunk.width))
  365. var cell_y = cell_offset.y + chunk.y + int(count / chunk.width)
  366. var cell = Vector2i(cell_x, cell_y)
  367. var cell_pos_x = cell_x * cell_size.x
  368. var cell_pos_y = cell_y * cell_size.y
  369. var cell_in_map = Vector2(cell_pos_x, cell_pos_y)
  370. layer.set_cell(cell, tileDic[gid][0], tileDic[gid][1])
  371. add_specific_nodes(parent, cell_in_map, gid)
  372. fill_polygon_pool(tileset, cell_in_map, gid)
  373. count += 1
  374. if options.save_tiled_properties:
  375. set_tiled_properties_as_meta(parent, tmxLayer)
  376. if options.custom_properties:
  377. set_custom_properties(parent, tmxLayer)
  378. elif tmxLayer.type == "imagelayer":
  379. var image = null
  380. if tmxLayer.image != "":
  381. image = load_image(tmxLayer.image, source_path, options)
  382. if typeof(image) != TYPE_OBJECT:
  383. # Error happened
  384. return null
  385. var pos = Vector2()
  386. var offset = Vector2()
  387. if "x" in tmxLayer:
  388. pos.x = float(tmxLayer.x)
  389. if "y" in tmxLayer:
  390. pos.y = float(tmxLayer.y)
  391. if "offsetx" in tmxLayer:
  392. offset.x = float(tmxLayer.offsetx)
  393. if "offsety" in tmxLayer:
  394. offset.y = float(tmxLayer.offsety)
  395. var sprite = Sprite2D.new()
  396. sprite.set_name(str(tmxLayer.name))
  397. sprite.centered = false
  398. sprite.texture = image
  399. sprite.visible = visible
  400. sprite.modulate = Color(1.0, 1.0, 1.0, opacity)
  401. if options.save_tiled_properties:
  402. set_tiled_properties_as_meta(sprite, tmxLayer)
  403. if options.custom_properties:
  404. set_custom_properties(sprite, tmxLayer)
  405. sprite.set("editor/display_folded", true)
  406. parent.add_child(sprite)
  407. sprite.position = pos + offset
  408. sprite.set_owner(parent)
  409. elif tmxLayer.type == "objectgroup":
  410. var object_layer = Node2D.new()
  411. if options.save_tiled_properties:
  412. set_tiled_properties_as_meta(object_layer, tmxLayer)
  413. if options.custom_properties:
  414. set_custom_properties(object_layer, tmxLayer)
  415. object_layer.set("editor/display_folded", true)
  416. parent.add_child(object_layer)
  417. object_layer.set_owner(parent)
  418. if "name" in tmxLayer and not str(tmxLayer.name).is_empty():
  419. object_layer.set_name(str(tmxLayer.name))
  420. if not "draworder" in tmxLayer or tmxLayer.draworder == "topdown":
  421. tmxLayer.objects.sort_custom(object_sorter)
  422. for object in tmxLayer.objects:
  423. if "template" in object:
  424. var template_file = object["template"]
  425. var template_data_immutable = get_template(remove_filename_from_path(data["source_path"]) + template_file)
  426. if typeof(template_data_immutable) != TYPE_DICTIONARY:
  427. # Error happened
  428. print("Error getting template for object with id " + str(data["id"]))
  429. continue
  430. # Overwrite template data with current object data
  431. apply_template(object, template_data_immutable)
  432. set_default_obj_params(object)
  433. if "point" in object and object.point:
  434. var point = Node2D.new()
  435. if not "x" in object or not "y" in object:
  436. print_error("Missing coordinates for point in object tmxLayer.")
  437. continue
  438. point.position = Vector2(float(object.x), float(object.y))
  439. point.visible = bool(object.visible) if "visible" in object else true
  440. object_layer.add_child(point)
  441. point.set_owner(parent)
  442. if "name" in object and not str(object.name).is_empty():
  443. point.set_name(str(object.name))
  444. elif "id" in object and not str(object.id).is_empty():
  445. point.set_name(str(object.id))
  446. if options.save_tiled_properties:
  447. set_tiled_properties_as_meta(point, object)
  448. if options.custom_properties:
  449. set_custom_properties(point, object)
  450. elif not "gid" in object:
  451. # Not a tile object
  452. if "type" in object and object.type == "navigation":
  453. # Can't make navigation objects right now
  454. print_error("Navigation polygons aren't supported in an object tmxLayer.")
  455. continue # Non-fatal error
  456. var shape = shape_from_object(object)
  457. if typeof(shape) != TYPE_OBJECT:
  458. # Error happened
  459. return null
  460. if "type" in object and object.type == "occluder":
  461. var occluder = LightOccluder2D.new()
  462. var pos = Vector2()
  463. var rot = 0
  464. if "x" in object:
  465. pos.x = float(object.x)
  466. if "y" in object:
  467. pos.y = float(object.y)
  468. if "rotation" in object:
  469. rot = float(object.rotation)
  470. occluder.visible = bool(object.visible) if "visible" in object else true
  471. occluder.position = pos
  472. occluder.rotation_degrees = rot
  473. occluder.occluder = shape
  474. if "name" in object and not str(object.name).is_empty():
  475. occluder.set_name(str(object.name))
  476. elif "id" in object and not str(object.id).is_empty():
  477. occluder.set_name(str(object.id))
  478. if options.save_tiled_properties:
  479. set_tiled_properties_as_meta(occluder, object)
  480. if options.custom_properties:
  481. set_custom_properties(occluder, object)
  482. object_layer.add_child(occluder)
  483. occluder.set_owner(parent)
  484. else:
  485. var offset = Vector2()
  486. var customObject
  487. var collisionObject
  488. var pos = Vector2()
  489. var rot = 0
  490. if "x" in object:
  491. pos.x = float(object.x)
  492. if "y" in object:
  493. pos.y = float(object.y)
  494. if "rotation" in object:
  495. rot = float(object.rotation)
  496. # Spawn objects are not generating nodes but are storing information in the spawn pool
  497. if object.type == "Spawn":
  498. if not shape is RectangleShape2D:
  499. print_error("Spawn object is not set as a rectangle shape, no other shape or polygons should be used for this object")
  500. else:
  501. var spawn_object = SpawnObject.new()
  502. if "properties" in object:
  503. if "count" in object.properties:
  504. spawn_object.count = object.properties.count
  505. if "name" in object.properties:
  506. spawn_object.name = object.properties.name
  507. if "type" in object.properties:
  508. spawn_object.type = object.properties.type
  509. if "player_script" in object.properties:
  510. spawn_object.player_script = object.properties.player_script
  511. if "own_script" in object.properties:
  512. spawn_object.own_script = object.properties.own_script
  513. if "respawn_delay" in object.properties:
  514. spawn_object.respawn_delay = object.properties.respawn_delay
  515. if "nick" in object.properties:
  516. spawn_object.nick = object.properties.nick
  517. spawn_object.spawn_position = pos + shape.extents
  518. spawn_object.spawn_offset = shape.extents
  519. spawn_pool.push_back(spawn_object)
  520. continue
  521. # Regular shape
  522. if not ("polygon" in object or "polyline" in object):
  523. customObject = CollisionShape2D.new()
  524. customObject.shape = shape
  525. if shape is RectangleShape2D:
  526. offset = shape.extents
  527. elif shape is CircleShape2D:
  528. offset = Vector2(shape.radius, shape.radius)
  529. elif shape is CapsuleShape2D:
  530. offset = Vector2(shape.radius, shape.height)
  531. if shape.radius > shape.height:
  532. var temp = shape.radius
  533. shape.radius = shape.height
  534. shape.height = temp
  535. customObject.rotation_degrees = 90
  536. shape.height *= 2
  537. customObject.position = offset
  538. # Hand-drawn polygons
  539. else:
  540. if object.type == "Warp":
  541. customObject = WarpObject.new()
  542. collisionObject = CollisionPolygon2D.new()
  543. elif object.type == "Port":
  544. customObject = PortObject.new()
  545. collisionObject = CollisionPolygon2D.new()
  546. else:
  547. customObject = Polygon2D.new()
  548. var points = null
  549. if shape is ConcavePolygonShape2D:
  550. points = []
  551. var segments = shape.segments
  552. for i in range(0, segments.size()):
  553. if i % 2 != 0:
  554. continue
  555. points.push_back(segments[i])
  556. # customObject.build_mode = Polygon2D.BUILD_SEGMENTS
  557. else:
  558. points = shape.points
  559. # customObject.build_mode = Polygon2D.BUILD_SOLIDS
  560. customObject.position = pos
  561. if collisionObject:
  562. collisionObject.polygon = points
  563. var area : float = 0.0
  564. var areaMin : Vector2 = Vector2.ZERO
  565. var areaMax : Vector2 = Vector2.ZERO
  566. for i in range(points.size() - 1):
  567. areaMin.x = min(areaMin.x, points[i].x)
  568. areaMin.y = min(areaMin.y, points[i].y)
  569. areaMax.x = max(areaMax.x, points[i].x)
  570. areaMax.y = max(areaMax.y, points[i].y)
  571. area += points[i].x * points[i + 1].y - points[i + 1].x * points[i].y
  572. area += points[points.size() - 1].x * points[0].y - points[0].x * points[points.size() - 1].y
  573. area = abs(area) / 2
  574. customObject.areaSize = area
  575. var pointsInPolygon: Array = []
  576. var numPoints : int = area / (32*32) * 4
  577. pointsInPolygon.append_array(points)
  578. while pointsInPolygon.size() < numPoints:
  579. var randomPoint : Vector2 = Vector2(randf_range(areaMin.x, areaMax.x), randf_range(areaMin.y, areaMax.y))
  580. if Geometry2D.is_point_in_polygon(randomPoint, points):
  581. pointsInPolygon.append(randomPoint)
  582. customObject.randomPoints = pointsInPolygon
  583. customObject.polygon = points
  584. # customObject.one_way_collision = object.type == "one-way"
  585. if "name" in object and not str(object.name).is_empty():
  586. customObject.set_name(str(object.name))
  587. elif "id" in object and not str(object.id).is_empty():
  588. customObject.set_name(str(object.id))
  589. if collisionObject:
  590. collisionObject.set_name(customObject.get_name())
  591. if customObject && object_layer:
  592. customObject.set("editor/display_folded", true)
  593. object_layer.add_child(customObject)
  594. customObject.set_owner(parent)
  595. if collisionObject:
  596. collisionObject.set("editor/display_folded", true)
  597. customObject.add_child(collisionObject)
  598. collisionObject.set_owner(parent)
  599. if options.save_tiled_properties:
  600. set_tiled_properties_as_meta(customObject, object)
  601. if options.custom_properties:
  602. set_custom_properties(customObject, object)
  603. # Warp
  604. if "type" in object and "properties" in object:
  605. var dest_cellsize = cell_size
  606. if "dest_cellsize" in object.properties:
  607. dest_cellsize = object.properties.dest_cellsize
  608. if "dest_map" in object.properties and not str(object.properties.dest_map).is_empty():
  609. customObject.destinationMap = object.properties.dest_map
  610. if "dest_pos_x" in object.properties and "dest_pos_y" in object.properties:
  611. customObject.destinationPos = Vector2(object.properties.dest_pos_x, object.properties.dest_pos_y) * dest_cellsize
  612. if "auto_warp" in object.properties:
  613. customObject.autoWarp = object.properties.auto_warp
  614. if "sail_pos_x" in object.properties and "sail_pos_y" in object.properties:
  615. customObject.sailingPos = Vector2(object.properties.sail_pos_x, object.properties.sail_pos_y) * dest_cellsize
  616. if customObject is PortObject:
  617. port_pool.append(customObject)
  618. elif customObject is WarpObject:
  619. warp_pool.append(customObject)
  620. customObject.visible = bool(object.visible) if "visible" in object else true
  621. customObject.position = pos
  622. else: # "gid" in object
  623. var tile_raw_id = str(object.gid).to_int() & 0xFFFFFFFF
  624. var tile_id = tile_raw_id & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)
  625. var is_tile_object = false # tileset.tile_get_region(tile_id).get_area() == 0
  626. var collisions = 0 # tileset.tile_get_shape_count(tile_id)
  627. var has_collisions = collisions > 0 && object.has("type") && object.type != "sprite"
  628. var sprite = Sprite2D.new()
  629. var pos = Vector2()
  630. var rot = 0
  631. var scale = Vector2(1, 1)
  632. var ts_atlas : TileSetAtlasSource = tileset.get_source(tileset.get_source_count() - 1)
  633. sprite.texture = ts_atlas.get_texture()
  634. var texture_size : Vector2 = sprite.texture.get_size() if sprite.texture != null else Vector2()
  635. if not is_tile_object:
  636. sprite.region_enabled = true
  637. sprite.region_rect = Rect2(Vector2.ZERO, ts_atlas.get_texture_region_size())
  638. texture_size = ts_atlas.get_texture_region_size()
  639. sprite.flip_h = bool(tile_raw_id & FLIPPED_HORIZONTALLY_FLAG)
  640. sprite.flip_v = bool(tile_raw_id & FLIPPED_VERTICALLY_FLAG)
  641. if "x" in object:
  642. pos.x = float(object.x)
  643. if "y" in object:
  644. pos.y = float(object.y)
  645. if "rotation" in object:
  646. rot = float(object.rotation)
  647. if texture_size != Vector2():
  648. if "width" in object and float(object.width) != texture_size.x:
  649. scale.x = float(object.width) / texture_size.x
  650. if "height" in object and float(object.height) != texture_size.y:
  651. scale.y = float(object.height) / texture_size.y
  652. var obj_root = sprite
  653. if has_collisions:
  654. match object.type:
  655. "area": obj_root = Area2D.new()
  656. "kinematic": obj_root = KinematicCollision2D.new()
  657. "rigid": obj_root = RigidBody2D.new()
  658. _: obj_root = StaticBody2D.new()
  659. object_layer.add_child(obj_root)
  660. obj_root.owner = parent
  661. obj_root.add_child(sprite)
  662. sprite.owner = parent
  663. var shapes = tileset.tile_get_shapes(tile_id)
  664. for s in shapes:
  665. var collision_node = CollisionShape2D.new()
  666. collision_node.shape = s.shape
  667. collision_node.transform = s.shape_transform
  668. if sprite.flip_h:
  669. collision_node.position.x *= -1
  670. collision_node.position.x -= cell_size.x
  671. collision_node.scale.x *= -1
  672. if sprite.flip_v:
  673. collision_node.scale.y *= -1
  674. collision_node.position.y *= -1
  675. collision_node.position.y -= cell_size.y
  676. obj_root.add_child(collision_node)
  677. collision_node.owner = parent
  678. if "name" in object and not str(object.name).is_empty():
  679. obj_root.set_name(str(object.name))
  680. elif "id" in object and not str(object.id).is_empty():
  681. obj_root.set_name(str(object.id))
  682. obj_root.position = pos
  683. obj_root.rotation_degrees = rot
  684. obj_root.visible = bool(object.visible) if "visible" in object else true
  685. obj_root.scale = scale
  686. # Translate from Tiled bottom-left position to Godot top-left
  687. sprite.centered = false
  688. sprite.region_filter_clip_enabled = options.uv_clip
  689. sprite.offset = Vector2(0, -texture_size.y)
  690. sprite.z_index = zindex
  691. if not has_collisions:
  692. object_layer.add_child(sprite)
  693. sprite.set_owner(parent)
  694. if options.save_tiled_properties:
  695. set_tiled_properties_as_meta(obj_root, object)
  696. if options.custom_properties:
  697. if options.tile_metadata:
  698. var tile_meta = tileset.get_meta("tile_meta")
  699. if typeof(tile_meta) == TYPE_DICTIONARY and tile_id in tile_meta:
  700. for prop in tile_meta[tile_id]:
  701. obj_root.set_meta(prop, tile_meta[tile_id][prop])
  702. set_custom_properties(obj_root, object)
  703. return null
  704. elif tmxLayer.type == "group":
  705. var group = Node2D.new()
  706. var pos = Vector2()
  707. if "x" in tmxLayer:
  708. pos.x = float(tmxLayer.x)
  709. if "y" in tmxLayer:
  710. pos.y = float(tmxLayer.y)
  711. group.modulate = Color(1.0, 1.0, 1.0, opacity)
  712. group.visible = visible
  713. group.position = pos
  714. if options.save_tiled_properties:
  715. set_tiled_properties_as_meta(group, tmxLayer)
  716. if options.custom_properties:
  717. set_custom_properties(group, tmxLayer)
  718. if "name" in tmxLayer and not str(tmxLayer.name).is_empty():
  719. group.set_name(str(tmxLayer.name))
  720. group.set("editor/display_folded", true)
  721. parent.add_child(group)
  722. group.set_owner(parent)
  723. else:
  724. print_error("Unknown tmxLayer type ('%s') in '%s'" % [str(tmxLayer.type), str(tmxLayer.name) if "name" in tmxLayer else "[unnamed tmxLayer]"])
  725. return null
  726. return layer
  727. func set_default_obj_params(object):
  728. # Set default values for object
  729. for attr in ["width", "height", "rotation", "x", "y"]:
  730. if not attr in object:
  731. object[attr] = 0
  732. if not "type" in object:
  733. object.type = ""
  734. if not "visible" in object:
  735. object.visible = true
  736. # Makes a tileset from a array of tilesets data
  737. # Since Godot supports only one TileSet per TileMap, all tilesets from Tiled are combined
  738. func build_tileset_for_scene(tilesets, source_path, options, root):
  739. var err = ERR_INVALID_DATA
  740. var tile_meta = {}
  741. var tsGroup = TileSet.new()
  742. tsGroup.add_physics_layer()
  743. for tileset in tilesets:
  744. var tsAtlas = TileSetAtlasSource.new()
  745. tsAtlas.use_texture_padding = false
  746. var layerID = tsGroup.add_source(tsAtlas)
  747. var ts = tileset
  748. var ts_source_path = source_path
  749. if "source" in ts:
  750. if not "firstgid" in tileset or not str(tileset.firstgid).is_valid_int():
  751. print_error("Missing or invalid firstgid tileset property.")
  752. return ERR_INVALID_DATA
  753. ts_source_path = source_path.get_base_dir().path_join(ts.source)
  754. # Used later for templates
  755. _tileset_path_to_first_gid[ts_source_path] = tileset.firstgid
  756. if ts.source.get_extension().to_lower() == "tsx":
  757. var tsx_reader = TiledXMLToDictionary.new()
  758. ts = tsx_reader.read_tsx(ts_source_path)
  759. if typeof(ts) != TYPE_DICTIONARY:
  760. # Error happened
  761. return ts
  762. else: # JSON Tileset
  763. if FileAccess.file_exists(ts_source_path) == false:
  764. print_error("Error opening tileset '%s'." % [ts.source])
  765. return false
  766. var f = FileAccess.open(ts_source_path, FileAccess.READ)
  767. var json_res = JSONInstance.parse(f.get_as_text())
  768. if json_res.error != OK:
  769. print_error("Error parsing tileset '%s' JSON: %s" % [ts.source, json_res.error_string])
  770. return ERR_INVALID_DATA
  771. ts = json_res.result
  772. if typeof(ts) != TYPE_DICTIONARY:
  773. print_error("Tileset '%s' is not a dictionary." % [ts.source])
  774. return ERR_INVALID_DATA
  775. ts.firstgid = tileset.firstgid
  776. err = validate_tileset(ts)
  777. if err != OK:
  778. return err
  779. var has_global_image = "image" in ts
  780. var spacing = int(ts.spacing) if "spacing" in ts and str(ts.spacing).is_valid_int() else 0
  781. var margin = int(ts.margin) if "margin" in ts and str(ts.margin).is_valid_int() else 0
  782. var firstgid = int(ts.firstgid)
  783. var columns = int(ts.columns) if "columns" in ts and str(ts.columns).is_valid_int() else -1
  784. var image = null
  785. var imagesize = Vector2()
  786. if has_global_image:
  787. image = load_image(ts.image, ts_source_path, options)
  788. if typeof(image) != TYPE_OBJECT:
  789. # Error happened
  790. return image
  791. imagesize = Vector2(int(ts.imagewidth), int(ts.imageheight))
  792. var tilesize = Vector2(int(ts.tilewidth), int(ts.tileheight))
  793. var tilecount = int(ts.tilecount)
  794. var gid = firstgid
  795. var x = margin
  796. var y = margin
  797. var i = 0
  798. var column = 0
  799. var tileRegions = []
  800. while i < tilecount:
  801. var tilepos = Vector2(x, y)
  802. var tileRegion = Rect2(tilepos, tilesize)
  803. tileRegions.push_back(tileRegion)
  804. column += 1
  805. i += 1
  806. x += int(tilesize.x) + spacing
  807. if (columns > 0 and column >= columns) or x >= int(imagesize.x) - margin or (x + int(tilesize.x)) > int(imagesize.x):
  808. x = margin
  809. y += int(tilesize.y) + spacing
  810. column = 0
  811. i = 0
  812. while i < tilecount:
  813. var tileRegion = tileRegions[i]
  814. var rel_id = str(gid - firstgid)
  815. if gid in tileDic:
  816. i += 1
  817. gid += 1
  818. continue
  819. elif has_global_image:
  820. tsAtlas.set_texture(image)
  821. # if options.apply_offset:
  822. # tsAtlas.set_margins(Vector2(0, 32-tilesize.y))
  823. elif not rel_id in ts.tiles:
  824. i += 1
  825. gid += 1
  826. continue
  827. else:
  828. var image_path = ts.tiles[rel_id].image
  829. image = load_image(image_path, ts_source_path, options)
  830. if typeof(image) != TYPE_OBJECT:
  831. # Error happened
  832. return image
  833. tsAtlas.set_texture(image)
  834. # if options.apply_offset:
  835. # tsAtlas.set_margins(Vector2(0, 32-image.get_height()))
  836. var atlasPos : Vector2i = tileRegion.position / tileRegion.size
  837. tileDic[gid] = [layerID, atlasPos]
  838. tsAtlas.set_texture_region_size(tileRegion.size)
  839. tsAtlas.create_tile(atlasPos)
  840. var tileData : TileData = tsAtlas.get_tile_data(atlasPos, 0)
  841. var textureOrigin : Vector2i = Vector2i.ZERO
  842. if tileRegion.size.x > cell_size.x || tileRegion.size.y > cell_size.y:
  843. textureOrigin.x = -(tileRegion.size.x - cell_size.x) / 2
  844. textureOrigin.y = (tileRegion.size.y - cell_size.y) / 2
  845. tileData.set_texture_origin(textureOrigin)
  846. if rel_id in ts.tiles && "animation" in ts.tiles[rel_id]:
  847. var frame_count: int = 0
  848. tsAtlas.set_tile_animation_columns(atlasPos, 0)
  849. tsAtlas.set_tile_animation_separation(atlasPos, Vector2.ZERO)
  850. for frame in ts.tiles[rel_id].animation:
  851. if tsAtlas.has_room_for_tile(atlasPos, Vector2.ONE, 0, Vector2.ZERO, frame_count + 1, atlasPos):
  852. tsAtlas.set_tile_animation_frames_count(atlasPos, frame_count + 1)
  853. var duration : float = frame["duration"].to_float() / 1000.0
  854. tsAtlas.set_tile_animation_frame_duration(atlasPos, frame_count, duration)
  855. tileDic[gid + frame_count] = [layerID, atlasPos]
  856. frame_count += 1
  857. if rel_id in ts.tiles && "objectgroup" in ts.tiles[rel_id] and "objects" in ts.tiles[rel_id].objectgroup:
  858. for object in ts.tiles[rel_id].objectgroup.objects:
  859. var shape = shape_from_object(object)
  860. if typeof(shape) != TYPE_OBJECT:
  861. # Error happened
  862. return shape
  863. var polygonShape : PackedVector2Array = []
  864. if shape is ConvexPolygonShape2D:
  865. polygonShape = shape.get_points()
  866. elif shape is ConcavePolygonShape2D:
  867. polygonShape = shape.get_segments()
  868. elif shape is RectangleShape2D:
  869. var shapeSize = shape.get_size()
  870. polygonShape = [ \
  871. Vector2(0, 0), \
  872. Vector2(0, shapeSize.y), \
  873. Vector2(0, shapeSize.y), \
  874. Vector2(shapeSize.x, shapeSize.y), \
  875. Vector2(shapeSize.x, shapeSize.y), \
  876. Vector2(shapeSize.x, 0), \
  877. Vector2(shapeSize.x, 0), \
  878. Vector2(0, 0) \
  879. ]
  880. var offset = Vector2(float(object.x), float(object.y)) - cell_size / 2
  881. if tileRegion.size.y > cell_size.y:
  882. offset.y -= (tileRegion.size.y - cell_size.y)
  883. for iVertice in range(0, polygonShape.size()):
  884. polygonShape[iVertice] += offset
  885. if polygonShape.is_empty() == false:
  886. var tilePolygonCount = tileData.get_collision_polygons_count(0)
  887. tileData.set_collision_polygons_count(0, tilePolygonCount + 1)
  888. tileData.set_collision_polygon_points(0, tilePolygonCount, polygonShape)
  889. # Handle some specific features
  890. if "tileproperties" in ts and rel_id in ts.tileproperties:
  891. if "custom" in ts.tileproperties[rel_id]:
  892. match ts.tileproperties[rel_id].custom:
  893. "LightSource":
  894. var light_radius : float = 64.0
  895. var light_color : Color = Color.WHITE
  896. var light_speed : float = 20.0
  897. var light_offset : Vector2 = tileRegion.size / 2
  898. if "light_radius" in ts.tileproperties[rel_id] and ts.tileproperties[rel_id].light_radius:
  899. light_radius = ts.tileproperties[rel_id].light_radius
  900. if "light_color" in ts.tileproperties[rel_id] and ts.tileproperties[rel_id].light_color:
  901. light_color = Color(ts.tileproperties[rel_id].light_color)
  902. if "light_speed" in ts.tileproperties[rel_id]:
  903. light_speed = ts.tileproperties[rel_id].light_speed
  904. if "light_offset" in ts.tileproperties[rel_id]:
  905. light_offset.y -= ts.tileproperties[rel_id].light_offset
  906. specificDic[gid] = ["LightSource", light_offset, light_speed, light_radius, light_color]
  907. "FX":
  908. var fx_path : String = "particles/" + ts.tileproperties[rel_id].FX if "FX" in ts.tileproperties[rel_id] else ""
  909. var inner_offset : Vector2 = Vector2.ZERO
  910. if "offset_x" in ts.tileproperties[rel_id]:
  911. inner_offset.x = ts.tileproperties[rel_id].offset_x
  912. if "offset_y" in ts.tileproperties[rel_id]:
  913. inner_offset.y = ts.tileproperties[rel_id].offset_y
  914. specificDic[gid] = ["FX", inner_offset, fx_path]
  915. if options.custom_properties and options.tile_metadata and "tileproperties" in ts \
  916. and "tilepropertytypes" in ts and rel_id in ts.tileproperties and rel_id in ts.tilepropertytypes:
  917. tile_meta[gid] = get_custom_properties(ts.tileproperties[rel_id], ts.tilepropertytypes[rel_id])
  918. if options.save_tiled_properties and rel_id in ts.tiles:
  919. for property in whitelist_properties:
  920. if property in ts.tiles[rel_id]:
  921. if not gid in tile_meta: tile_meta[gid] = {}
  922. tile_meta[gid][property] = ts.tiles[rel_id][property]
  923. gid += 1
  924. i += 1
  925. if str(ts.name) != "":
  926. tsAtlas.resource_name = str(ts.name)
  927. if options.save_tiled_properties:
  928. set_tiled_properties_as_meta(tsAtlas, ts)
  929. if options.custom_properties:
  930. if "properties" in ts and "propertytypes" in ts:
  931. set_custom_properties(tsAtlas, ts)
  932. if options.custom_properties and options.tile_metadata:
  933. tsGroup.set_meta("tile_meta", tile_meta)
  934. return tsGroup
  935. # Makes a standalone TileSet. Useful for importing TileSets from Tiled
  936. # Returns an error code if fails
  937. func build_tileset(source_path, options):
  938. var set = read_tileset_file(source_path)
  939. if typeof(set) == TYPE_INT:
  940. return set
  941. if typeof(set) != TYPE_DICTIONARY:
  942. return ERR_INVALID_DATA
  943. # Just to validate and build correctly using the existing builder
  944. set["firstgid"] = 0
  945. return build_tileset_for_scene([set], source_path, options, null)
  946. # Loads an image from a given path
  947. # Returns a Texture
  948. func load_image(rel_path, source_path, options):
  949. var embed = options.embed_internal_images if "embed_internal_images" in options else false
  950. var ext = rel_path.get_extension().to_lower()
  951. if ext != "png" and ext != "jpg":
  952. print_error("Unsupported image format: %s. Use PNG or JPG instead." % [ext])
  953. return ERR_FILE_UNRECOGNIZED
  954. var total_path = rel_path
  955. if rel_path.is_relative_path():
  956. total_path = ProjectSettings.globalize_path(source_path.get_base_dir()).path_join(rel_path)
  957. total_path = ProjectSettings.localize_path(total_path)
  958. if not FileAccess.file_exists(total_path):
  959. print_error("Image not found: %s" % [total_path])
  960. return ERR_FILE_NOT_FOUND
  961. if not total_path.begins_with("res://"):
  962. # External images need to be embedded
  963. embed = true
  964. var image = null
  965. if embed:
  966. image = ImageTexture.new()
  967. image.load(total_path)
  968. else:
  969. image = ResourceLoader.load(total_path, "ImageTexture")
  970. return image
  971. # Reads a file and returns its contents as a dictionary
  972. # Returns an error code if fails
  973. func read_file(path):
  974. if path.get_extension().to_lower() == "tmx":
  975. var tmx_to_dict = TiledXMLToDictionary.new()
  976. var data = tmx_to_dict.read_tmx(path)
  977. if typeof(data) != TYPE_DICTIONARY:
  978. # Error happened
  979. print_error("Error parsing map file '%s'." % [path])
  980. # Return error or result
  981. return data
  982. # Not TMX, must be JSON
  983. if FileAccess.file_exists(path) == false:
  984. return false
  985. var file = FileAccess.open(path, FileAccess.READ)
  986. var error = JSONInstance.parse(file.get_as_text())
  987. if error != OK:
  988. print_error("Error parsing JSON: " + error)
  989. return error
  990. return JSONInstance.get_data()
  991. # Reads a tileset file and return its contents as a dictionary
  992. # Returns an error code if fails
  993. func read_tileset_file(path):
  994. if path.get_extension().to_lower() == "tsx":
  995. var tmx_to_dict = TiledXMLToDictionary.new()
  996. var data = tmx_to_dict.read_tsx(path)
  997. if typeof(data) != TYPE_DICTIONARY:
  998. # Error happened
  999. print_error("Error parsing map file '%s'." % [path])
  1000. # Return error or result
  1001. return data
  1002. # Not TSX, must be JSON
  1003. if FileAccess.file_exists(path) == false:
  1004. return false
  1005. var file = FileAccess.open(path, FileAccess.READ)
  1006. var content = JSONInstance.parse(file.get_as_text())
  1007. if content.error != OK:
  1008. print_error("Error parsing JSON: " + content.error_string)
  1009. return content.error
  1010. return content.result
  1011. # Creates a shape from an object data
  1012. # Returns a valid shape depending on the object type (collision/occluder/navigation/warp/spawn)
  1013. func shape_from_object(object):
  1014. var shape = ERR_INVALID_DATA
  1015. set_default_obj_params(object)
  1016. if "polygon" in object or "polyline" in object:
  1017. var vertices = PackedVector2Array()
  1018. if "polygon" in object:
  1019. for point in object.polygon:
  1020. vertices.push_back(Vector2(float(point.x), float(point.y)))
  1021. else:
  1022. for point in object.polyline:
  1023. vertices.push_back(Vector2(float(point.x), float(point.y)))
  1024. if object.type == "navigation":
  1025. shape = NavigationPolygon.new()
  1026. shape.vertices = vertices
  1027. shape.add_outline(vertices)
  1028. shape.make_polygons_from_outlines()
  1029. elif object.type == "occluder":
  1030. shape = OccluderPolygon2D.new()
  1031. shape.polygon = vertices
  1032. shape.closed = "polygon" in object
  1033. else:
  1034. if is_convex(vertices):
  1035. var sorter = PolygonSorter.new()
  1036. vertices = sorter.sort_polygon(vertices)
  1037. shape = ConvexPolygonShape2D.new()
  1038. shape.points = vertices
  1039. else:
  1040. shape = ConcavePolygonShape2D.new()
  1041. var segments = [vertices[0]]
  1042. for x in range(1, vertices.size()):
  1043. segments.push_back(vertices[x])
  1044. segments.push_back(vertices[x])
  1045. segments.push_back(vertices[0])
  1046. shape.segments = PackedVector2Array(segments)
  1047. elif "ellipse" in object:
  1048. if object.type == "navigation" or object.type == "occluder":
  1049. print_error("Ellipse shapes are not supported as navigation or occluder. Use polygon/polyline instead.")
  1050. return ERR_INVALID_DATA
  1051. if not "width" in object or not "height" in object:
  1052. print_error("Missing width or height in ellipse shape.")
  1053. return ERR_INVALID_DATA
  1054. var w = abs(float(object.width))
  1055. var h = abs(float(object.height))
  1056. if w == h:
  1057. shape = CircleShape2D.new()
  1058. shape.radius = w / 2.0
  1059. else:
  1060. # Using a capsule since it's the closest from an ellipse
  1061. shape = CapsuleShape2D.new()
  1062. shape.radius = w / 2.0
  1063. shape.height = h / 2.0
  1064. else: # Rectangle
  1065. if not "width" in object or not "height" in object:
  1066. print_error("Missing width or height in rectangle shape.")
  1067. return ERR_INVALID_DATA
  1068. var size = Vector2(float(object.width), float(object.height))
  1069. if object.type == "navigation" or object.type == "occluder":
  1070. # Those types only accept polygons, so make one from the rectangle
  1071. var vertices = PackedVector2Array([
  1072. Vector2(0, 0),
  1073. Vector2(size.x, 0),
  1074. size,
  1075. Vector2(0, size.y)
  1076. ])
  1077. if object.type == "navigation":
  1078. shape = NavigationPolygon.new()
  1079. shape.vertices = vertices
  1080. shape.add_outline(vertices)
  1081. shape.make_polygons_from_outlines()
  1082. else:
  1083. shape = OccluderPolygon2D.new()
  1084. shape.polygon = vertices
  1085. else:
  1086. shape = RectangleShape2D.new()
  1087. shape.extents = size / 2.0
  1088. return shape
  1089. # Determines if the set of vertices is convex or not
  1090. # Returns a boolean
  1091. func is_convex(vertices):
  1092. var size = vertices.size()
  1093. if size <= 3:
  1094. # Less than 3 verices can't be concave
  1095. return true
  1096. var cp = 0
  1097. for i in range(0, size + 2):
  1098. var p1 = vertices[(i + 0) % size]
  1099. var p2 = vertices[(i + 1) % size]
  1100. var p3 = vertices[(i + 2) % size]
  1101. var prev_cp = cp
  1102. cp = (p2.x - p1.x) * (p3.y - p2.y) - (p2.y - p1.y) * (p3.x - p2.x)
  1103. if i > 0 and sign(cp) != sign(prev_cp):
  1104. return false
  1105. return true
  1106. # Decompress the data of the layer
  1107. # Compression argument is a string, either "gzip" or "zlib"
  1108. func decompress_layer_data(layer_data, compression, map_size):
  1109. if compression != "gzip" and compression != "zlib":
  1110. print_error("Unrecognized compression format: %s" % [compression])
  1111. return ERR_INVALID_DATA
  1112. var compression_type = FileAccess.COMPRESSION_DEFLATE if compression == "zlib" else FileAccess.COMPRESSION_GZIP
  1113. var expected_size = int(map_size.x) * int(map_size.y) * 4
  1114. var raw_data = Marshalls.base64_to_raw(layer_data).decompress(expected_size, compression_type)
  1115. return decode_layer(raw_data)
  1116. # Reads the layer as a base64 data
  1117. # Returns an array of ints as the decoded layer would be
  1118. func read_base64_layer_data(layer_data):
  1119. var decoded = Marshalls.base64_to_raw(layer_data)
  1120. return decode_layer(decoded)
  1121. # Reads a PoolByteArray and returns the layer array
  1122. # Used for base64 encoded and compressed layers
  1123. func decode_layer(layer_data):
  1124. var result = []
  1125. for i in range(0, layer_data.size(), 4):
  1126. var num = (layer_data[i]) | \
  1127. (layer_data[i + 1] << 8) | \
  1128. (layer_data[i + 2] << 16) | \
  1129. (layer_data[i + 3] << 24)
  1130. result.push_back(num)
  1131. return result
  1132. # Set the custom properties into the metadata of the object
  1133. func set_custom_properties(object, tiled_object):
  1134. if not "properties" in tiled_object or not "propertytypes" in tiled_object:
  1135. return
  1136. var properties = get_custom_properties(tiled_object.properties, tiled_object.propertytypes)
  1137. for property in properties:
  1138. object.set_meta(property, properties[property])
  1139. if property == "lighting":
  1140. var lighting : Node = object.get_node_or_null("LightingLayer")
  1141. if lighting == null:
  1142. lighting = FileSystem.LoadEffect("Lighting")
  1143. lighting.set_name("LightingLayer")
  1144. lighting.lightLevel = properties[property]
  1145. object.add_child(lighting)
  1146. lighting.set_owner(object)
  1147. elif property == "flagnodrop" and bool(properties[property]):
  1148. map_flags |= WorldMap.Flags.NO_DROP
  1149. elif property == "flagnospell" and bool(properties[property]):
  1150. map_flags |= WorldMap.Flags.NO_SPELL
  1151. elif property == "flagnorejoin" and bool(properties[property]):
  1152. map_flags |= WorldMap.Flags.NO_REJOIN
  1153. elif property == "flagonlyspirit" and bool(properties[property]):
  1154. map_flags |= WorldMap.Flags.ONLY_SPIRIT
  1155. # Get the custom properties as a dictionary
  1156. # Useful for tile meta, which is not stored directly
  1157. func get_custom_properties(properties, types):
  1158. var result = {}
  1159. for property in properties:
  1160. var value = null
  1161. if str(types[property]).to_lower() == "bool":
  1162. value = bool(properties[property])
  1163. elif str(types[property]).to_lower() == "int":
  1164. value = int(properties[property])
  1165. elif str(types[property]).to_lower() == "float":
  1166. value = float(properties[property])
  1167. elif str(types[property]).to_lower() == "color":
  1168. value = Color(properties[property])
  1169. else:
  1170. value = str(properties[property])
  1171. result[property] = value
  1172. return result
  1173. # Get the available whitelisted properties from the Tiled object
  1174. # And them as metadata in the Godot object
  1175. func set_tiled_properties_as_meta(object, tiled_object):
  1176. for property in whitelist_properties:
  1177. if property in tiled_object:
  1178. object.set_meta(property, tiled_object[property])
  1179. # Custom function to sort objects in an object layer
  1180. # This is done to support the "topdown" draw order, which sorts by 'y' coordinate
  1181. func object_sorter(first, second):
  1182. if first.y == second.y:
  1183. return first.id < second.id
  1184. return first.y < second.y
  1185. # Validates the map dictionary content for missing or invalid keys
  1186. # Returns an error code
  1187. func validate_map(map):
  1188. if not "type" in map or map.type != "map":
  1189. print_error("Missing or invalid type property.")
  1190. return ERR_INVALID_DATA
  1191. elif not "version" in map or int(map.version) != 1:
  1192. print_error("Missing or invalid map version.")
  1193. return ERR_INVALID_DATA
  1194. elif not "tileheight" in map or not str(map.tileheight).is_valid_int():
  1195. print_error("Missing or invalid tileheight property.")
  1196. return ERR_INVALID_DATA
  1197. elif not "tilewidth" in map or not str(map.tilewidth).is_valid_int():
  1198. print_error("Missing or invalid tilewidth property.")
  1199. return ERR_INVALID_DATA
  1200. elif not "layers" in map or typeof(map.layers) != TYPE_ARRAY:
  1201. print_error("Missing or invalid layers property.")
  1202. return ERR_INVALID_DATA
  1203. elif not "tilesets" in map or typeof(map.tilesets) != TYPE_ARRAY:
  1204. print_error("Missing or invalid tilesets property.")
  1205. return ERR_INVALID_DATA
  1206. if "orientation" in map and (map.orientation == "staggered" or map.orientation == "hexagonal"):
  1207. if not "staggeraxis" in map:
  1208. print_error("Missing stagger axis property.")
  1209. return ERR_INVALID_DATA
  1210. elif not "staggerindex" in map:
  1211. print_error("Missing stagger axis property.")
  1212. return ERR_INVALID_DATA
  1213. return OK
  1214. # Validates the tileset dictionary content for missing or invalid keys
  1215. # Returns an error code
  1216. func validate_tileset(tileset):
  1217. if not "firstgid" in tileset or not str(tileset.firstgid).is_valid_int():
  1218. print_error("Missing or invalid firstgid tileset property.")
  1219. return ERR_INVALID_DATA
  1220. elif not "tilewidth" in tileset or not str(tileset.tilewidth).is_valid_int():
  1221. print_error("Missing or invalid tilewidth tileset property.")
  1222. return ERR_INVALID_DATA
  1223. elif not "tileheight" in tileset or not str(tileset.tileheight).is_valid_int():
  1224. print_error("Missing or invalid tileheight tileset property.")
  1225. return ERR_INVALID_DATA
  1226. elif not "tilecount" in tileset or not str(tileset.tilecount).is_valid_int():
  1227. print_error("Missing or invalid tilecount tileset property.")
  1228. return ERR_INVALID_DATA
  1229. if not "image" in tileset:
  1230. for tile in tileset.tiles:
  1231. if not "image" in tileset.tiles[tile]:
  1232. print_error("Missing or invalid image in tileset property.")
  1233. return ERR_INVALID_DATA
  1234. elif not "imagewidth" in tileset.tiles[tile] or not str(tileset.tiles[tile].imagewidth).is_valid_int():
  1235. print_error("Missing or invalid imagewidth tileset property 1.")
  1236. return ERR_INVALID_DATA
  1237. elif not "imageheight" in tileset.tiles[tile] or not str(tileset.tiles[tile].imageheight).is_valid_int():
  1238. print_error("Missing or invalid imageheight tileset property.")
  1239. return ERR_INVALID_DATA
  1240. else:
  1241. if not "imagewidth" in tileset or not str(tileset.imagewidth).is_valid_int():
  1242. print_error("Missing or invalid imagewidth tileset property 2.")
  1243. return ERR_INVALID_DATA
  1244. elif not "imageheight" in tileset or not str(tileset.imageheight).is_valid_int():
  1245. print_error("Missing or invalid imageheight tileset property.")
  1246. return ERR_INVALID_DATA
  1247. return OK
  1248. # Validates the layer dictionary content for missing or invalid keys
  1249. # Returns an error code
  1250. func validate_layer(layer):
  1251. if not "type" in layer:
  1252. print_error("Missing or invalid type layer property.")
  1253. return ERR_INVALID_DATA
  1254. elif not "name" in layer:
  1255. print_error("Missing or invalid name layer property.")
  1256. return ERR_INVALID_DATA
  1257. match layer.type:
  1258. "tilelayer":
  1259. if not "height" in layer or not str(layer.height).is_valid_int():
  1260. print_error("Missing or invalid layer height property.")
  1261. return ERR_INVALID_DATA
  1262. elif not "width" in layer or not str(layer.width).is_valid_int():
  1263. print_error("Missing or invalid layer width property.")
  1264. return ERR_INVALID_DATA
  1265. elif not "data" in layer:
  1266. if not "chunks" in layer:
  1267. print_error("Missing data or chunks layer properties.")
  1268. return ERR_INVALID_DATA
  1269. elif typeof(layer.chunks) != TYPE_ARRAY:
  1270. print_error("Invalid chunks layer property.")
  1271. return ERR_INVALID_DATA
  1272. elif "encoding" in layer:
  1273. if layer.encoding == "base64" and typeof(layer.data) != TYPE_STRING:
  1274. print_error("Invalid data layer property.")
  1275. return ERR_INVALID_DATA
  1276. if layer.encoding != "base64" and typeof(layer.data) != TYPE_ARRAY:
  1277. print_error("Invalid data layer property.")
  1278. return ERR_INVALID_DATA
  1279. elif typeof(layer.data) != TYPE_ARRAY:
  1280. print_error("Invalid data layer property.")
  1281. return ERR_INVALID_DATA
  1282. if "compression" in layer:
  1283. if layer.compression != "gzip" and layer.compression != "zlib":
  1284. print_error("Invalid compression type.")
  1285. return ERR_INVALID_DATA
  1286. "imagelayer":
  1287. if not "image" in layer or typeof(layer.image) != TYPE_STRING:
  1288. print_error("Missing or invalid image path for layer.")
  1289. return ERR_INVALID_DATA
  1290. "objectgroup":
  1291. if not "objects" in layer or typeof(layer.objects) != TYPE_ARRAY:
  1292. print_error("Missing or invalid objects array for layer.")
  1293. return ERR_INVALID_DATA
  1294. "group":
  1295. if not "layers" in layer or typeof(layer.layers) != TYPE_ARRAY:
  1296. print_error("Missing or invalid layer array for group layer.")
  1297. return ERR_INVALID_DATA
  1298. return OK
  1299. func validate_chunk(chunk):
  1300. if not "data" in chunk:
  1301. print_error("Missing data chunk property.")
  1302. return ERR_INVALID_DATA
  1303. elif not "height" in chunk or not str(chunk.height).is_valid_int():
  1304. print_error("Missing or invalid height chunk property.")
  1305. return ERR_INVALID_DATA
  1306. elif not "width" in chunk or not str(chunk.width).is_valid_int():
  1307. print_error("Missing or invalid width chunk property.")
  1308. return ERR_INVALID_DATA
  1309. elif not "x" in chunk or not str(chunk.x).is_valid_int():
  1310. print_error("Missing or invalid x chunk property.")
  1311. return ERR_INVALID_DATA
  1312. elif not "y" in chunk or not str(chunk.y).is_valid_int():
  1313. print_error("Missing or invalid y chunk property.")
  1314. return ERR_INVALID_DATA
  1315. return OK
  1316. # Custom function to print error, to centralize the prefix addition
  1317. func print_error(err):
  1318. printerr(error_prefix + err)
  1319. func get_template(path):
  1320. # If this template has not yet been loaded
  1321. if not _loaded_templates.has(path):
  1322. # IS XML
  1323. if path.get_extension().to_lower() == "tx":
  1324. var parser = XMLParser.new()
  1325. var err = parser.open(path)
  1326. if err != OK:
  1327. print_error("Error opening TX file '%s'." % [path])
  1328. return err
  1329. var content = parse_template(parser, path)
  1330. if typeof(content) != TYPE_DICTIONARY:
  1331. # Error happened
  1332. print_error("Error parsing template map file '%s'." % [path])
  1333. return false
  1334. _loaded_templates[path] = content
  1335. # IS JSON
  1336. else:
  1337. if FileAccess.file_exists(path):
  1338. return false
  1339. var file = FileAccess.open(path, FileAccess.READ)
  1340. var json_res = JSONInstance.parse(file.get_as_text())
  1341. if json_res.error != OK:
  1342. print_error("Error parsing JSON template map file '%s'." % [path])
  1343. return json_res.error
  1344. var result = json_res.result
  1345. if typeof(result) != TYPE_DICTIONARY:
  1346. print_error("Error parsing JSON template map file '%s'." % [path])
  1347. return ERR_INVALID_DATA
  1348. var object = result.object
  1349. if object.has("gid"):
  1350. if result.has("tileset"):
  1351. var ts_path = remove_filename_from_path(path) + result.tileset.source
  1352. var tileset_gid_increment = get_first_gid_from_tileset_path(ts_path) - 1
  1353. object.gid += tileset_gid_increment
  1354. _loaded_templates[path] = object
  1355. var dict = _loaded_templates[path]
  1356. var dictCopy = {}
  1357. for k in dict:
  1358. dictCopy[k] = dict[k]
  1359. return dictCopy
  1360. func parse_template(parser, path):
  1361. var err = OK
  1362. # Template root node shouldn't have attributes
  1363. var data = {}
  1364. var tileset_gid_increment = 0
  1365. data.id = 0
  1366. err = parser.read()
  1367. while err == OK:
  1368. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  1369. if parser.get_node_name() == "template":
  1370. break
  1371. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  1372. if parser.get_node_name() == "tileset":
  1373. var ts_path = remove_filename_from_path(path) + parser.get_named_attribute_value_safe("source")
  1374. tileset_gid_increment = get_first_gid_from_tileset_path(ts_path) - 1
  1375. data.tileset = ts_path
  1376. if parser.get_node_name() == "object":
  1377. var object = TiledXMLToDictionary.parse_object(parser)
  1378. for k in object:
  1379. data[k] = object[k]
  1380. err = parser.read()
  1381. if data.has("gid"):
  1382. data["gid"] += tileset_gid_increment
  1383. return data
  1384. func get_first_gid_from_tileset_path(path):
  1385. for t in _tileset_path_to_first_gid:
  1386. if is_same_file(path, t):
  1387. return _tileset_path_to_first_gid[t]
  1388. return 0
  1389. static func get_filename_from_path(path):
  1390. var substrings = path.split("/", false)
  1391. var file_name = substrings[substrings.size() - 1]
  1392. return file_name
  1393. static func remove_filename_from_path(path):
  1394. var file_name = get_filename_from_path(path)
  1395. var stringSize = path.length() - file_name.length()
  1396. var file_path = path.substr(0,stringSize)
  1397. return file_path
  1398. static func is_same_file(path1, path2):
  1399. if FileAccess.file_exists(path1) || FileAccess.file_exists(path2):
  1400. return false
  1401. var file1 = FileAccess.open(path1, FileAccess.READ)
  1402. var file2 = FileAccess.open(path2, FileAccess.READ)
  1403. var file1_str = file1.get_as_text()
  1404. var file2_str = file2.get_as_text()
  1405. if file1_str == file2_str:
  1406. return true
  1407. return false
  1408. static func apply_template(object, template_immutable):
  1409. for k in template_immutable:
  1410. # Do not overwrite any object data
  1411. if typeof(template_immutable[k]) == TYPE_DICTIONARY:
  1412. if not object.has(k):
  1413. object[k] = {}
  1414. apply_template(object[k], template_immutable[k])
  1415. elif not object.has(k):
  1416. object[k] = template_immutable[k]