dxruby_tiled 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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