dxruby_tiled 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ module DXRuby
2
+ module Tiled
3
+ class TileLayer < Layer
4
+ attr_reader :width, :height, :startx, :starty
5
+
6
+ def initialize(data, map)
7
+ super
8
+ if data[:chunks]
9
+ @x1 = data[:chunks].map {|chunk| chunk[:x] }.min
10
+ @y1 = data[:chunks].map {|chunk| chunk[:y] }.min
11
+ @x2 = data[:chunks].map {|chunk| chunk[:x] + chunk[:width ] }.max
12
+ @y2 = data[:chunks].map {|chunk| chunk[:y] + chunk[:height] }.max
13
+ @data = Array.new((@x2 - @x1) * (@y2 - @y1), 0)
14
+ data[:chunks].each do |chunk|
15
+ get_data(chunk[:data], data[:encoding], data[:compression]).each_with_index do |d, i|
16
+ x, y = chunk[:x] + i % chunk[:width] - @x1, chunk[:y] + i / chunk[:width] - @y1
17
+ @data[y * (@x2 - @x1) + x] = d
18
+ end
19
+ end
20
+ else
21
+ @x1 = data[:startx] || 0
22
+ @y1 = data[:starty] || 0
23
+ @x2 = @x1 + (data[:width ] || map.width )
24
+ @y2 = @y1 + (data[:height] || map.height)
25
+ @data = get_data(data[:data], data[:encoding], data[:compression])
26
+ end
27
+ @startx = @x1
28
+ @starty = @y1
29
+ @width = @x2 - @x1
30
+ @height = @y2 - @y1
31
+ @tile_width = map.tile_width
32
+ @tile_height = map.tile_height
33
+ @renderorder_x = map.renderorder_x
34
+ @renderorder_y = map.renderorder_y
35
+ @render_target = DXRuby::RenderTarget.new(DXRuby::Window.width, DXRuby::Window.height)
36
+ @tilesets = map.tilesets
37
+
38
+ self.extend LoopTileLayer if map.loop
39
+ end
40
+
41
+ def [](x, y)
42
+ x < @x1 || x >= @x2 || y < @y1 || y >= @y2 ?
43
+ 0 : @data[(y - @y1) * @width + x - @x1]
44
+ end
45
+
46
+ def []=(x, y, value)
47
+ return if x < @x1 || x >= @x2 || y < @y1 || y >= @y2
48
+ @data[(y - @y1) * @width + x - @x1] = value
49
+ end
50
+
51
+ def include?(x, y)
52
+ x >= @x1 && x < @x2 && y >= @y1 && y < @y2
53
+ end
54
+ alias_method :member?, :include?
55
+
56
+ def at(pos_x, pos_y)
57
+ x, y = xy_at(pos_x, pos_y)
58
+ self[x, y]
59
+ end
60
+
61
+ def change_at(pos_x, pos_y, value)
62
+ x, y = xy_at(pos_x, pos_y)
63
+ self[x, y] = value
64
+ end
65
+
66
+ def tile_at(pos_x, pos_y)
67
+ @tilesets[at(pos_x, pos_y)]
68
+ end
69
+
70
+ def render(pos_x, pos_y, target = DXRuby::Window, z = 0, offset_x = 0, offset_y = 0, opacity = 1.0)
71
+ unless @render_target.width == target.width && @render_target.height == target.height
72
+ @render_target.resize(target.width, target.height)
73
+ end
74
+ end
75
+ alias_method :draw, :render
76
+
77
+ private
78
+
79
+ def get_data(data, encoding, compression)
80
+ case encoding
81
+ when "base64"
82
+ tmp = Base64.decode64(data)
83
+ case compression
84
+ when "gzip" # unsupported
85
+ raise NotImplementedError.new("GZip is unsupported.")
86
+ when "zlib"
87
+ data_array = Zlib::Inflate.inflate(tmp).unpack("L*")
88
+ else
89
+ data_array = tmp.unpack("L*")
90
+ end
91
+ else
92
+ data_array = data
93
+ end
94
+ data_array
95
+ end
96
+ end
97
+
98
+ module LoopTileLayer
99
+ def [](x, y)
100
+ @data[((y - @y1) % @height) * @width + ((x - @x1) % @width)]
101
+ end
102
+
103
+ def []=(x, y, value)
104
+ @data[((y - @y1) % @height) * @width + ((x - @x1) % @width)] = value
105
+ end
106
+
107
+ def include?(x, y)
108
+ true
109
+ end
110
+
111
+ def member?(x, y)
112
+ true
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,15 +1,16 @@
1
1
  module DXRuby
2
2
  module Tiled
3
3
  class Tileset
4
- attr_reader :firstgid, :name, :tile_width, :tile_height,
4
+ attr_reader :firstgid, :source, :name, :tile_width, :tile_height,
5
5
  :spacing, :margin, :tile_count, :columns, :tile_offset, :properties,
6
- :tile_images, :animations
6
+ :tiles, :animations
7
7
 
8
8
  def initialize(data, map)
9
- @data = data
10
- @map = map
9
+ @firstgid = data[:firstgid] || 1
10
+ @source = data[:source]
11
+ data_dir = @source ? File.dirname(@source) : map.data_dir
12
+ data = map.load_tileset(@source) if @source
11
13
 
12
- @firstgid = data[:firstgid] || 1
13
14
  @name = data[:name]
14
15
  @tile_width = data[:tilewidth] || map.tile_width
15
16
  @tile_height = data[:tileheight] || map.tile_height
@@ -17,18 +18,62 @@ module DXRuby
17
18
  @margin = data[:margin] || 0
18
19
  @tile_count = data[:tilecount]
19
20
  @columns = data[:columns]
20
- @tile_offset = data[:tileoffset] # unsupported
21
+ @tile_offset = data[:tileoffset] || { x: 0, y: 0 }
21
22
  @properties = data[:properties] || {}
22
23
 
23
- image = @map.load_image(data[:image])
24
- if data[:transparentcolor]
25
- color = data[:transparentcolor].sub("#", "").scan(/../).map{|c| c.to_i(16) }
26
- image.set_color_key(color)
24
+ @tiles = []
25
+ tile_images = []
26
+ if data[:image]
27
+ image = map.load_image(data[:image], data[:transparentcolor], data_dir)
28
+ image_width = data[:imagewidth] || image.width
29
+ image_height = data[:imageheight] || image.height
30
+ tile_images = split_image(image, image_width, image_height)
31
+ image.dispose()
32
+ else
33
+ data[:tiles].each_pair do |key, value|
34
+ tile_images[key.to_s.to_i] = map.load_image(value[:image], nil, data_dir)
35
+ end
36
+ end
37
+ @tile_count = tile_images.size unless @tile_count
38
+
39
+ adjusted_offset_x = map.orientation == IsometricLayer ? map.tile_width / 2 : 0
40
+ adjusted_offset_y = @tile_offset[:y] + map.tile_height
41
+ tile_images.each_with_index do |image, i|
42
+ next unless image
43
+ @tiles[i] = Tile.new(
44
+ image,
45
+ @tile_offset[:x] - adjusted_offset_x,
46
+ @tile_offset[:y], adjusted_offset_y - image.height,
47
+ self
48
+ )
27
49
  end
28
- image_width = data[:imagewidth] || image.width
29
- image_height = data[:imageheight] || image.height
30
50
 
31
- @tile_images = []
51
+ tiles_data = data[:tiles] || {}
52
+ set_types(tiles_data)
53
+ set_animations(tiles_data)
54
+ set_collisions(tiles_data)
55
+ end
56
+
57
+ def dispose()
58
+ @tiles.each_value do |tile|
59
+ @tile.image.dispose()
60
+ end
61
+ end
62
+
63
+ def delayed_dispose()
64
+ @tiles.each_value do |tile|
65
+ @tile.image.delayed_dispose()
66
+ end
67
+ end
68
+
69
+ def disposed?()
70
+ @tiles[0].image.disposed?()
71
+ end
72
+
73
+ private
74
+
75
+ def split_image(image, image_width, image_height)
76
+ tile_images = []
32
77
  i = 0
33
78
  col = 1
34
79
  x = @margin
@@ -41,31 +86,59 @@ module DXRuby
41
86
  end
42
87
  break if y + @tile_height > image_height
43
88
 
44
- @tile_images[i] = image.slice(x, y, @tile_width, @tile_height)
89
+ tile_images[i] = image.slice(x, y, @tile_width, @tile_height)
45
90
  x += @tile_width + @spacing
46
91
  i += 1
47
92
  col += 1
48
93
  break if @tile_count && i >= @tile_count
49
94
  end
50
- @tile_count = i unless @tile_count
51
- image.dispose()
52
-
53
- @animations = {}
54
- (data[:tiles] || {}).each_pair do |key, value|
95
+ tile_images
96
+ end
97
+
98
+ def set_types(tiles_data)
99
+ tiles_data.each_pair do |key, value|
100
+ next unless value.has_key?(:type)
101
+ @tiles[key.to_s.to_i].type = value[:type]
102
+ end
103
+ end
104
+
105
+ def set_animations(tiles_data)
106
+ @animations = []
107
+ tiles_data.each_pair do |key, value|
55
108
  next unless value.has_key?(:animation)
56
- anim_time = []
57
- anim_image = []
58
- time = 0
109
+ anim_time = [0]
110
+ anim_tile = []
59
111
  value[:animation].each do |anim|
60
- anim_time.push(time)
61
- anim_image.push(@tile_images[anim[:tileid]])
62
- time += anim[:duration]
112
+ anim_time.push(anim_time.last + anim[:duration])
113
+ anim_tile.push(@tiles[anim[:tileid]])
63
114
  end
64
- anim_time.push(time)
65
- @animations[@firstgid + key.to_s.to_i] = { time: anim_time, image: anim_image }
115
+ @tiles[key.to_s.to_i].set_animation(anim_time, anim_tile)
116
+ @animations.push(key.to_s.to_i)
66
117
  end
67
118
  end
68
119
 
120
+ def set_collisions(tiles_data)
121
+ tiles_data.each_pair do |key, value|
122
+ next unless value.has_key?(:objectgroup)
123
+ next unless value[:objectgroup].has_key?(:objects)
124
+ collision = []
125
+ value[:objectgroup][:objects].each do |obj|
126
+ case
127
+ when obj[:ellipse]
128
+ collision.push([obj[:x], obj[:y], (obj[:width] + obj[:height]) / 2])
129
+ when obj[:polygon]
130
+ (obj[:polygon].size - 2).times do |i|
131
+ collision.push([obj[:polygon][0 ][:x], obj[:polygon][0 ][:y],
132
+ obj[:polygon][i + 1][:x], obj[:polygon][i + 1][:y],
133
+ obj[:polygon][i + 2][:x], obj[:polygon][i + 2][:y]])
134
+ end
135
+ else
136
+ collision.push([obj[:x], obj[:y], obj[:x] + obj[:width], obj[:y] + obj[:height]])
137
+ end
138
+ end
139
+ @tiles[key.to_s.to_i].collision = collision
140
+ end
141
+ end
69
142
  end
70
143
  end
71
144
  end
@@ -1,34 +1,68 @@
1
1
  module DXRuby
2
2
  module Tiled
3
3
  class Tilesets
4
- attr_reader :tile_images
4
+ attr_reader :tiles, :tile_left, :tile_right, :tile_top, :tile_bottom
5
5
 
6
6
  def initialize(data, map)
7
- @data = data
8
- @map = map
9
-
10
7
  @last_time = 0
11
- @tile_images = [DXRuby::Image.new(32, 32)]
12
- @tilesets = @data.map{|tileset| Tileset.new(tileset, @map)}
13
- @animations = {}
8
+ @tiles = {}
9
+ dummy_tile = Tile.new(DXRuby::Image.new(map.tile_width, map.tile_height), 0, 0, 0)
10
+ def dummy_tile.render(x, y, target = DXRuby::Window, z_index = 0); end
11
+ def dummy_tile.draw(x, y, target = DXRuby::Window, z_index = 0); end
12
+ @tiles[0] = dummy_tile
13
+ @tiles.default = dummy_tile
14
14
 
15
+ @animations = []
16
+ @tilesets = data.map { |tileset| Tileset.new(tileset, map) }
17
+ hex = map.orientation == HexagonalLayer
15
18
  @tilesets.each do |tileset|
16
- gid = tileset.firstgid || @tile_images.size
17
- tileset.tile_images.each_index{|i| @tile_images[gid + i] = tileset.tile_images[i] }
18
- @animations.merge!(tileset.animations)
19
+ gid = tileset.firstgid
20
+ tileset.tiles.each_with_index do |tile, i|
21
+ next unless tile
22
+ @tiles[gid + i] = tile
23
+ range = hex ? 1..15 : 1..7
24
+ k = hex ? 0x10000000 : 0x20000000
25
+ range.each do |j|
26
+ tileid = j * k + gid + i
27
+ @tiles[tileid] = FlippedTile.new(tile, tileid, hex)
28
+ end
29
+ end
30
+ tileset.animations.each { |i| @animations.push(gid + i) }
19
31
  end
32
+ @tile_left = @tiles.values.map { |tile| tile.offset_x }.min
33
+ @tile_top = @tiles.values.map { |tile| tile.offset_y }.min
34
+ @tile_right = @tiles.values.map { |tile| tile.offset_x + tile.width }.max
35
+ @tile_bottom = @tiles.values.map { |tile| tile.offset_y + tile.height }.max
36
+ end
37
+
38
+ def [](num)
39
+ @tiles[num]
20
40
  end
21
41
 
22
- def animation()
42
+ def animate()
23
43
  return if @last_time == DXRuby::Window::running_time
24
44
  @last_time = DXRuby::Window::running_time
25
45
 
26
- @animations.each_pair do |key, anim|
27
- time = @last_time % anim[:time].last
28
- @tile_images[key] = anim[:image][anim[:time].rindex{|t| time >= t }]
29
- end
46
+ @animations.each { |i| @tiles[i].animate!(@last_time) }
47
+ end
48
+
49
+ def gid_adjusted_by_source(gid, source)
50
+ gid + @tilesets.find { |tileset| tileset.source == source }.firstgid - 1
30
51
  end
31
52
 
53
+ def dispose()
54
+ @tilesets.each_value { |tileset| tileset.dispose() }
55
+ @tiles[0].image.dispose()
56
+ end
57
+
58
+ def delayed_dispose()
59
+ @tilesets.each_value { |tileset| tileset.delayed_dispose() }
60
+ @tiles[0].image.delayed_dispose()
61
+ end
62
+
63
+ def disposed?()
64
+ @tiles[0].image.disposed?()
65
+ end
32
66
  end
33
67
  end
34
68
  end
@@ -0,0 +1,271 @@
1
+ require "dxruby_tiled"
2
+ require "rexml/document"
3
+
4
+ module DXRuby
5
+ module Tiled
6
+ module TMXLoader
7
+ module_function
8
+
9
+ def load_tmx(tmxfile, encoding = Encoding::UTF_8, dir = nil)
10
+ Map.new(
11
+ TMXLoader.tmx_to_hash(TMXLoader.read_xmlfile(tmxfile, encoding)),
12
+ dir || File.dirname(tmxfile)
13
+ )
14
+ end
15
+
16
+ def read_xmlfile(xmlfile, encoding = Encoding::UTF_8)
17
+ REXML::Document.new(DXRuby::Tiled.read_file(xmlfile, encoding))
18
+ end
19
+
20
+ def element_to_hash(elm, attrs_i = [], attrs_f = [], attrs_b = [])
21
+ hash = {}
22
+
23
+ elm.attributes.each_pair do |key, value|
24
+ str = value.to_s
25
+ if attrs_i.include?(key)
26
+ hash[key.to_sym] = str.to_i
27
+ elsif attrs_f.include?(key)
28
+ hash[key.to_sym] = str.to_f
29
+ elsif attrs_b.include?(key)
30
+ hash[key.to_sym] = !!str && str != "0" && str != "false"
31
+ else
32
+ hash[key.to_sym] = str
33
+ end
34
+ end
35
+
36
+ hash
37
+ end
38
+
39
+ def properties_parent_to_hash(element)
40
+ properties_hash = { properties: {}, propertytypes: {} }
41
+
42
+ element.each_element("properties") do |properties|
43
+ properties.each_element_with_attribute("type") do |property|
44
+ name = property.attribute("name" ).to_s.to_sym
45
+ type = property.attribute("type" ).to_s
46
+ value = property.attribute("value").to_s
47
+ case type
48
+ when "string", "color", "file"
49
+ properties_hash[:properties][name] = value
50
+ when "int"
51
+ properties_hash[:properties][name] = value.to_i
52
+ when "float"
53
+ properties_hash[:properties][name] = value.to_f
54
+ when "bool"
55
+ properties_hash[:properties][name] = !!value && value != "0" && value != "false"
56
+ end
57
+ properties_hash[:propertytypes][name] = type
58
+ end
59
+ end
60
+
61
+ properties_hash[:properties].empty? ? {} : properties_hash
62
+ end
63
+
64
+ def tileset_to_hash(tileset_element)
65
+ tileset_hash = element_to_hash(tileset_element,
66
+ %w[firstgid tilewidth tileheight spacing margin tilecount columns]
67
+ )
68
+ tileset_hash.merge!(properties_parent_to_hash(tileset_element))
69
+ tileset_element.each_element("tileoffset") do |tileoffset|
70
+ tileset_hash[:tileoffset] = {
71
+ x: tileoffset.attribute("x").to_s.to_i,
72
+ y: tileoffset.attribute("y").to_s.to_i
73
+ }
74
+ end
75
+ tileset_element.each_element("image") do |image_element|
76
+ tileset_hash.merge!(image_to_hash(image_element))
77
+ end
78
+ tileset_hash[:tiles] = {}
79
+ tileset_element.each_element("tile") do |tile_element|
80
+ hash = element_to_hash(tile_element)
81
+ tile_hash = { type: hash[:type] }
82
+ tile_element.each_element("image") do |image_element|
83
+ tile_hash.merge!(image_to_hash(image_element))
84
+ end
85
+ tile_element.each_element("objectgroup") do |objectgroup_element|
86
+ tile_hash[:objectgroup] = objectgroup_to_hash(objectgroup_element)
87
+ end
88
+ tile_element.each_element("animation") do |animation_element|
89
+ tile_hash[:animation] = animation_to_array(animation_element)
90
+ end
91
+ tileset_hash[:tiles][hash[:id]] = tile_hash
92
+ end
93
+
94
+ tileset_hash
95
+ end
96
+
97
+ def image_to_hash(image_element)
98
+ hash = {}
99
+
100
+ image_hash = element_to_hash(image_element, %w[width height])
101
+ hash[:image] = image_hash[:source] if image_hash.has_key?(:source)
102
+ hash[:imagewidth] = image_hash[:width ] if image_hash.has_key?(:width )
103
+ hash[:imageheight] = image_hash[:height] if image_hash.has_key?(:height)
104
+ hash[:transparentcolor] = image_hash[:trans ] if image_hash.has_key?(:trans )
105
+
106
+ hash
107
+ end
108
+
109
+ def objectgroup_to_hash(objectgroup_element)
110
+ objectgroup_hash = element_to_hash(objectgroup_element,
111
+ %w[x y width height offsetx offsety],
112
+ %w[opacity],
113
+ %w[visible]
114
+ )
115
+ objectgroup_hash.merge!(properties_parent_to_hash(objectgroup_element))
116
+ objectgroup_hash[:objects] = []
117
+ objectgroup_element.each_element("object") do |object_element|
118
+ objectgroup_hash[:objects].push(object_to_hash(object_element))
119
+ end
120
+
121
+ objectgroup_hash
122
+ end
123
+
124
+ def object_to_hash(object_element)
125
+ object_hash = { rotation: 0, visible: true }
126
+
127
+ object_hash.merge!(element_to_hash(object_element,
128
+ %w[id gid],
129
+ %w[x y width height rotation opacity],
130
+ %w[visible]
131
+ ))
132
+ object_hash.merge!(properties_parent_to_hash(object_element))
133
+ object_hash[:ellipse] = true unless object_element.get_elements("ellipse").empty?
134
+ object_hash[:point] = true unless object_element.get_elements("point").empty?
135
+ object_element.each_element("polygon") do |polygon_element|
136
+ object_hash[:polygon] = polygon_element.attribute("points").to_s.split(" ").map do |point|
137
+ x, y = point.split(",").map(&:to_f)
138
+ { x: x, y: y }
139
+ end
140
+ end
141
+ object_element.each_element("polyline") do |polyline_element|
142
+ object_hash[:polyline] = polyline_element.attribute("points").to_s.split(" ").map do |point|
143
+ x, y = point.split(",").map(&:to_f)
144
+ { x: x, y: y }
145
+ end
146
+ end
147
+ object_element.each_element("text") do |text_element|
148
+ object_hash[:text] = element_to_hash(text_element,
149
+ %w[pixelsize],
150
+ %w[],
151
+ %w[wrap bold italic underline strikeout kerning]
152
+ )
153
+ object_hash[:text][:text] = text_element.text
154
+ end
155
+
156
+ object_hash
157
+ end
158
+
159
+ def animation_to_array(animation_element)
160
+ animations = []
161
+
162
+ animation_element.each_element("frame") do |frame|
163
+ animations.push({
164
+ tileid: frame.attribute("tileid" ).to_s.to_i,
165
+ duration: frame.attribute("duration").to_s.to_i
166
+ })
167
+ end
168
+
169
+ animations
170
+ end
171
+
172
+ def layers_to_array(layers_parent_element)
173
+ layers_array = []
174
+
175
+ layers_parent_element.each_child do |layer_element|
176
+ next unless layer_element.is_a?(REXML::Element)
177
+ layer_hash = element_to_hash(layer_element,
178
+ %w[x y width height offsetx offsety],
179
+ %w[opacity],
180
+ %w[visible]
181
+ )
182
+ layer_hash.merge!(properties_parent_to_hash(layer_element))
183
+
184
+ case layer_element.name.to_s
185
+ when "layer"
186
+ layer_hash[:type] = "tilelayer"
187
+ data = layer_element.get_elements("data").first
188
+ data_hash = element_to_hash(data)
189
+ layer_hash[:compression] = data_hash[:compression] if data_hash.has_key?(:compression)
190
+ layer_hash[:encoding ] = data_hash[:encoding ] if data_hash.has_key?(:encoding )
191
+ chunks = data.get_elements("chunk")
192
+ if chunks.empty?
193
+ if layer_hash[:encoding] == "csv"
194
+ layer_hash[:data] = data.text.strip.split(",").map(&:to_i)
195
+ else
196
+ layer_hash[:data] = data.text
197
+ end
198
+ else
199
+ layer_hash[:chunks] = []
200
+ chunks.each do |chunk|
201
+ chunk_hash = element_to_hash(chunk, %w[x y width height])
202
+ if layer_hash[:encoding] == "csv"
203
+ chunk_hash[:data] = chunk.text.strip.split(",").map(&:to_i)
204
+ else
205
+ chunk_hash[:data] = chunk.text
206
+ end
207
+ layer_hash[:chunks].push(chunk_hash)
208
+ end
209
+ end
210
+ when "objectgroup"
211
+ layer_hash[:type] = "objectgroup"
212
+ layer_hash.merge!(objectgroup_to_hash(layer_element))
213
+ when "imagelayer"
214
+ layer_hash[:type] = "imagelayer"
215
+ layer_element.each_element("image") do |image_element|
216
+ layer_hash.merge!(image_to_hash(image_element))
217
+ end
218
+ when "group"
219
+ layer_hash[:type] = "group"
220
+ layer_hash[:layers] = layers_to_array(layer_element)
221
+ else
222
+ next
223
+ end
224
+ layers_array.push(layer_hash)
225
+ end
226
+
227
+ layers_array
228
+ end
229
+
230
+ def tmx_to_hash(tmx)
231
+ map_element = tmx.root
232
+ hash = element_to_hash(map_element,
233
+ %w[width height tilewidth tileheight hexsidelength nextobjectid],
234
+ %w[version],
235
+ %w[]
236
+ )
237
+ hash.merge!(properties_parent_to_hash(map_element))
238
+ hash[:tilesets] = []
239
+ map_element.each_element("tileset") do |tileset_element|
240
+ hash[:tilesets].push(tileset_to_hash(tileset_element))
241
+ end
242
+ hash[:layers] = layers_to_array(map_element)
243
+
244
+ hash
245
+ end
246
+
247
+ def tsx_to_hash(tsx)
248
+ hash = tileset_to_hash(tsx.root)
249
+
250
+ hash
251
+ end
252
+
253
+ def tx_to_hash(tx)
254
+ hash = {}
255
+
256
+ tx.root.each_element("tileset") do |tileset_element|
257
+ hash[:tileset] = element_to_hash(tileset_element,
258
+ %w[firstgid],
259
+ %w[],
260
+ %w[]
261
+ )
262
+ end
263
+ tx.root.each_element("object") do |object_element|
264
+ hash[:object] = object_to_hash(object_element)
265
+ end
266
+
267
+ hash
268
+ end
269
+ end
270
+ end
271
+ end