retmx 0.0.1
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.
- data/lib/retmx.rb +476 -0
- metadata +45 -0
data/lib/retmx.rb
ADDED
@@ -0,0 +1,476 @@
|
|
1
|
+
#Libreria para leer archivo TMX de mapeditor.org
|
2
|
+
#Author:: Jovany Leandro G.C (mailto: info@manadalibre.org)
|
3
|
+
#Copyright:: Copyright (c) 2011 Jovany Leandro G.C
|
4
|
+
#License:: GPLv3 or any later version
|
5
|
+
#date: 2011-12-10
|
6
|
+
require 'rexml/document'
|
7
|
+
require 'base64'
|
8
|
+
require 'zlib'
|
9
|
+
require 'csv'
|
10
|
+
|
11
|
+
module RETMX
|
12
|
+
include REXML
|
13
|
+
|
14
|
+
def RETMX.load(file)
|
15
|
+
doc = Document.new (File.new(file))
|
16
|
+
m = Map.new(doc.root)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
#Wraps any number of custom properties. Can be used as a child of the map, tile (when part of a tileset), layer, objectgroup and object elements.
|
21
|
+
class Properties
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
attr_reader :xml
|
25
|
+
|
26
|
+
|
27
|
+
def initialize(xml)
|
28
|
+
@xml = xml
|
29
|
+
@property = {}
|
30
|
+
@xml.elements.each('properties/property') {|e|
|
31
|
+
@property[e.attributes['name']] = e.attributes['value']
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](i)
|
36
|
+
@property[i]
|
37
|
+
end
|
38
|
+
|
39
|
+
def each
|
40
|
+
@property.each {|i| yield i}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
#The +tilewidth+ and +tileheight+ properties determine the general grid size of the map. The individual tiles may have different sizes. Larger tiles will extend at the top and right (anchored to the bottom left).
|
46
|
+
class Map
|
47
|
+
|
48
|
+
|
49
|
+
#The TMX format version, generally 1.0
|
50
|
+
attr_reader :version
|
51
|
+
|
52
|
+
#Map orientation. Tiled supports "orthogonal" and "isometric" at the moment.
|
53
|
+
attr_reader :orientation
|
54
|
+
|
55
|
+
#The map width in tiles.
|
56
|
+
attr_reader :width
|
57
|
+
|
58
|
+
#The map height in tiles.
|
59
|
+
attr_reader :height
|
60
|
+
|
61
|
+
#The width of a tile.
|
62
|
+
attr_reader :tilewidth
|
63
|
+
|
64
|
+
#The height of a tile.
|
65
|
+
attr_reader :tileheight
|
66
|
+
|
67
|
+
#Layers on map
|
68
|
+
attr_reader :layers
|
69
|
+
|
70
|
+
#Tileset on map
|
71
|
+
attr_reader :tilesets
|
72
|
+
|
73
|
+
#Objects groups on map
|
74
|
+
attr_reader :objectgroups
|
75
|
+
|
76
|
+
#Properties on map
|
77
|
+
attr_reader :property
|
78
|
+
|
79
|
+
#REXML internal
|
80
|
+
attr_reader :xml
|
81
|
+
def initialize(doc)
|
82
|
+
@version = doc.attributes['version']
|
83
|
+
@orientation = doc.attributes['orientation']
|
84
|
+
@width = doc.attributes['width'].to_i
|
85
|
+
@height = doc.attributes['height'].to_i
|
86
|
+
@tilewidth = doc.attributes['tilewidth'].to_i
|
87
|
+
@tileheight = doc.attributes['tileheight'].to_i
|
88
|
+
|
89
|
+
@tilesets = {}
|
90
|
+
@layers = {}
|
91
|
+
@objectgroups = {}
|
92
|
+
@xml = doc
|
93
|
+
|
94
|
+
build(doc)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def build(doc)
|
99
|
+
doc.elements.each("tileset") { |e|
|
100
|
+
@tilesets[e.attributes['name']] = TileSet.new(self, e)
|
101
|
+
}
|
102
|
+
doc.elements.each("layer") { |e|
|
103
|
+
@layers[e.attributes['name']] = Layer.new(self, e)
|
104
|
+
}
|
105
|
+
doc.elements.each("objectgroup") { |e|
|
106
|
+
@objectgroups[e.attributes['name']] = ObjectGroup.new(self, e)
|
107
|
+
}
|
108
|
+
@property = Properties.new(doc)
|
109
|
+
end
|
110
|
+
|
111
|
+
#The object group is in fact a map layer, and is hence called "object layer" in Tiled Qt.
|
112
|
+
class ObjectGroup
|
113
|
+
include Enumerable
|
114
|
+
|
115
|
+
#The name of the object group.
|
116
|
+
attr_reader :name
|
117
|
+
|
118
|
+
#The x coordinate of the object group in tiles. Defaults to 0 and can no longer be changed in Tiled Qt.
|
119
|
+
attr_reader :x
|
120
|
+
|
121
|
+
#The y coordinate of the object group in tiles. Defaults to 0 and can no longer be changed in Tiled Qt.
|
122
|
+
attr_reader :y
|
123
|
+
|
124
|
+
#The width of the object group in tiles. Meaningless.
|
125
|
+
attr_reader :width
|
126
|
+
|
127
|
+
#The height of the object group in tiles. Meaningless
|
128
|
+
attr_reader :height
|
129
|
+
|
130
|
+
|
131
|
+
def initialize(map, xml)
|
132
|
+
@map = map
|
133
|
+
@objects = []
|
134
|
+
build(xml)
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def each
|
139
|
+
@objects.each { |i| yield i}
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
def build(xml)
|
144
|
+
xml.elements.each('object') {|e|
|
145
|
+
@objects << Object.new(self, e)
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
=begin
|
151
|
+
While tile layers are very suitable for anything repetitive aligned to the tile grid, sometimes you want to annotate your map with other information, not necessarily aligned to the grid. Hence the objects have their coordinates and size in pixels, but you can still easily align that to the grid when you want to.
|
152
|
+
|
153
|
+
You generally use objects to add custom information to your tile map, such as spawn points, warps, exits, etc.
|
154
|
+
|
155
|
+
When the object has a gid set, then it is represented by the image of the tile with that global ID. Currently that means width and height are ignored for such objects. The image alignment currently depends on the map orientation. In orthogonal orientation it's aligned to the bottom-left while in isometric it's aligned to the bottom-center.
|
156
|
+
=end
|
157
|
+
class Object
|
158
|
+
#The name of the object. An arbitrary string.
|
159
|
+
attr_reader :name
|
160
|
+
|
161
|
+
#The type of the object. An arbitrary string.
|
162
|
+
attr_reader :type
|
163
|
+
|
164
|
+
#The x coordinate of the object in pixels.
|
165
|
+
attr_reader :x
|
166
|
+
|
167
|
+
#The y coordinate of the object in pixels.
|
168
|
+
attr_reader :y
|
169
|
+
|
170
|
+
#The width of the object in pixels.
|
171
|
+
attr_reader :width
|
172
|
+
|
173
|
+
#The height of the object in pixels.
|
174
|
+
attr_reader :height
|
175
|
+
|
176
|
+
#An reference to a tile (optional).
|
177
|
+
attr_reader :gid
|
178
|
+
|
179
|
+
def initialize(og, e)
|
180
|
+
@objectgroup = og
|
181
|
+
@name = e.attributes['name']
|
182
|
+
@type = e.attributes['type']
|
183
|
+
@x = e.attributes['x'].to_i
|
184
|
+
@y = e.attributes['y'].to_i
|
185
|
+
@width = e.attributes['width'].to_i
|
186
|
+
@height = e.attributes['height'].to_i
|
187
|
+
@gid = e.attributes['gid'].nil? ? nil : e.attributes['gid'].to_i
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class Layer
|
193
|
+
#The name of the layer.
|
194
|
+
attr_reader :name
|
195
|
+
|
196
|
+
#The x coordinate of the layer in tiles. Defaults to 0 and can no longer be changed in Tiled Qt.
|
197
|
+
attr_reader :x
|
198
|
+
|
199
|
+
#The y coordinate of the layer in tiles. Defaults to 0 and can no longer be changed in Tiled Qt.
|
200
|
+
attr_reader :y
|
201
|
+
|
202
|
+
#The width of the layer in tiles. Traditionally required, but as of Tiled Qt always the same as the map width.
|
203
|
+
attr_reader :width
|
204
|
+
|
205
|
+
#The height of the layer in tiles. Traditionally required, but as of Tiled Qt always the same as the map height.
|
206
|
+
attr_reader :height
|
207
|
+
|
208
|
+
#The opacity of the layer as a value from 0 to 1. Defaults to 1.
|
209
|
+
attr_reader :opacity
|
210
|
+
|
211
|
+
#Whether the layer is shown (1) or hidden (0). Defaults to 1.
|
212
|
+
attr_reader :visible
|
213
|
+
|
214
|
+
#Data
|
215
|
+
attr_reader :data
|
216
|
+
|
217
|
+
#Map belongs
|
218
|
+
attr_reader :map
|
219
|
+
|
220
|
+
#Properties
|
221
|
+
attr_reader :property
|
222
|
+
|
223
|
+
#REXML internal
|
224
|
+
attr_reader :xml
|
225
|
+
def initialize(map, xml)
|
226
|
+
|
227
|
+
@name = xml.attributes['name']
|
228
|
+
@x = xml.attributes['x'].nil? ? 0 : xml.attributes['x']
|
229
|
+
@y = xml.attributes['y'].nil? ? 0 : xml.attributes['y']
|
230
|
+
@width = xml.attributes['width'].nil? ? xml.parent.attributes['width'].to_i : xml.attributes['width'].to_i
|
231
|
+
@height = xml.attributes['height'].nil? ? xml.parent.attributes['height'].to_i : xml.attributes['height'].to_i
|
232
|
+
@opacity = xml.attributes['opacity'].nil? ? 1 : xml.attributes['opacity'].to_i
|
233
|
+
@visible = xml.attributes['visible'].nil? ? 1 : xml.attributes['visible'].to_i
|
234
|
+
@opacity = xml.attributes['opacity'].nil? ? 1 : xml.attributes['opacity'].to_f
|
235
|
+
@data = nil
|
236
|
+
@map = map
|
237
|
+
build(xml)
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
#This function is used for render the layer
|
242
|
+
def render(&block) #:yields: block_y, block_x, tileset, index
|
243
|
+
@height.times {|by| #block row
|
244
|
+
@width.times {|bx| #block col
|
245
|
+
cell = @data[(by * @height) + bx]
|
246
|
+
@map.tilesets.each {|k, t|
|
247
|
+
if t.firstgid <= cell.gid
|
248
|
+
cell.gid -= t.firstgid
|
249
|
+
block.call(bx, by, t, cell)
|
250
|
+
break
|
251
|
+
end
|
252
|
+
}
|
253
|
+
}
|
254
|
+
}
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
def build(doc)
|
259
|
+
@data = Data.new(self, doc.elements['data'])
|
260
|
+
@property = Properties.new(doc)
|
261
|
+
end
|
262
|
+
|
263
|
+
class Data
|
264
|
+
include Enumerable
|
265
|
+
GID_FLIP_X = 1.<< 31
|
266
|
+
GID_FLIP_Y = 1.<< 30
|
267
|
+
|
268
|
+
#The encoding used to encode the tile layer data. When used, it can be "base64" and "csv" at the moment.
|
269
|
+
attr_reader :encoding
|
270
|
+
|
271
|
+
#The compression used to compress the tile layer data. Tiled Qt supports "gzip" and "zlib".
|
272
|
+
attr_reader :compression
|
273
|
+
|
274
|
+
#Layer belongs
|
275
|
+
attr_reader :layer
|
276
|
+
|
277
|
+
#REXML internal
|
278
|
+
attr_reader :xml
|
279
|
+
|
280
|
+
|
281
|
+
def initialize(layer, xml)
|
282
|
+
@layer = layer
|
283
|
+
@xml = xml
|
284
|
+
@encoding = xml.attributes['encoding']
|
285
|
+
@compression = xml.attributes['compression']
|
286
|
+
|
287
|
+
@raw = []
|
288
|
+
@raw_data = xml.text
|
289
|
+
@data = []
|
290
|
+
|
291
|
+
#decoding
|
292
|
+
case @encoding
|
293
|
+
when 'base64'
|
294
|
+
@raw_data = Base64.decode64(@raw_data)
|
295
|
+
when 'csv'
|
296
|
+
@raw_data = @raw_data.tr("\n",'')
|
297
|
+
end
|
298
|
+
|
299
|
+
#inflate compress
|
300
|
+
case @compression
|
301
|
+
when 'zlib'
|
302
|
+
zs = Zlib::Inflate.new
|
303
|
+
@raw_data = zs.inflate(@raw_data)
|
304
|
+
zs.finish
|
305
|
+
zs.close
|
306
|
+
when 'gzip'
|
307
|
+
gz = Zlib::GzipReader.new(StringIO.new(@raw_data))
|
308
|
+
@raw_data = gz.read
|
309
|
+
gz.close
|
310
|
+
end
|
311
|
+
|
312
|
+
#Data XML
|
313
|
+
if @encoding.nil? and @compression.nil?
|
314
|
+
xml.elements.each('tile') {|e|
|
315
|
+
@raw << e.attributes['gid'].to_i
|
316
|
+
}
|
317
|
+
elsif @encoding == 'csv'
|
318
|
+
@raw = @raw_data.split(',').collect {|x| x.to_i}
|
319
|
+
else
|
320
|
+
@raw = @raw_data.unpack("V*")
|
321
|
+
end
|
322
|
+
|
323
|
+
get_data
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
def [] (i)
|
329
|
+
@data[i]
|
330
|
+
end
|
331
|
+
|
332
|
+
def each
|
333
|
+
@data.each { |i| yield i }
|
334
|
+
end
|
335
|
+
|
336
|
+
private
|
337
|
+
|
338
|
+
#Discover, global tile id
|
339
|
+
def decode_gid(raw_gid)
|
340
|
+
flags = 0
|
341
|
+
flags += 1 if raw_gid & GID_FLIP_X == GID_FLIP_X
|
342
|
+
flags += 2 if raw_gid & GID_FLIP_Y == GID_FLIP_Y
|
343
|
+
gid = raw_gid & ~(GID_FLIP_X | GID_FLIP_Y)
|
344
|
+
return [gid, flags]
|
345
|
+
end
|
346
|
+
|
347
|
+
#Extract array of gids from array of ints
|
348
|
+
def get_data
|
349
|
+
tile_index = 0
|
350
|
+
gi = Struct.new(:gid, :flags)
|
351
|
+
@layer.height.times {|y|
|
352
|
+
@layer.width.times {|x|
|
353
|
+
next_gid = @raw[tile_index]
|
354
|
+
gid, flags = decode_gid(next_gid)
|
355
|
+
@data[tile_index] = gi.new(gid, flags)
|
356
|
+
tile_index += 1
|
357
|
+
}
|
358
|
+
}
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
class TileSet
|
365
|
+
#The first global tile ID of this tileset (this global ID maps to the first tile in this tileset).
|
366
|
+
attr_reader :firstgid
|
367
|
+
|
368
|
+
#If this tileset is stored in an external TSX (Tile Set XML) file, this attribute refers to that file.
|
369
|
+
attr_reader :source
|
370
|
+
|
371
|
+
#The name of this tileset.
|
372
|
+
attr_reader :name
|
373
|
+
|
374
|
+
#The (maximum) width of the tiles in this tileset.
|
375
|
+
attr_reader :tilewidth
|
376
|
+
|
377
|
+
#The (maximum) height of the tiles in this tileset.
|
378
|
+
attr_reader :tileheight
|
379
|
+
|
380
|
+
#The spacing in pixels between the tiles in this tileset (applies to the tileset image).
|
381
|
+
attr_reader :spacing
|
382
|
+
|
383
|
+
#The margin around the tiles in this tileset (applies to the tileset image).
|
384
|
+
attr_reader :margin
|
385
|
+
|
386
|
+
#Image has
|
387
|
+
attr_reader :image
|
388
|
+
|
389
|
+
#Properties
|
390
|
+
attr_reader :property
|
391
|
+
|
392
|
+
#Map belongs
|
393
|
+
attr_reader :map
|
394
|
+
def initialize(map, xml)
|
395
|
+
@map = map
|
396
|
+
@firstgid = xml.attributes['firstgid'].to_i
|
397
|
+
@source = xml.attributes['source']
|
398
|
+
@name = xml.attributes['name']
|
399
|
+
@tilewidth = xml.attributes['tilewidth'].to_i
|
400
|
+
@tileheight = xml.attributes['tileheight'].to_i
|
401
|
+
@spacing = xml.attributes['spacing'].nil? ? 0 : xml.attributes['spacing'].to_i
|
402
|
+
@margin = xml.attributes['margin'].nil? ? 0 : xml.attributes['margin'].to_i
|
403
|
+
|
404
|
+
@image = nil
|
405
|
+
#array of Recs that know how cut a image
|
406
|
+
@tiles = []
|
407
|
+
build(xml)
|
408
|
+
end
|
409
|
+
|
410
|
+
|
411
|
+
#Get tile at index +i+
|
412
|
+
def at(i)
|
413
|
+
return @tiles[i.gid] if i.respond_to? :gid
|
414
|
+
return @tiles[i]
|
415
|
+
end
|
416
|
+
|
417
|
+
private
|
418
|
+
def build(xml)
|
419
|
+
@image = Image.new(xml)
|
420
|
+
@property = Properties.new(xml)
|
421
|
+
xml.elements.each('tile') { |e| @tiles << Tile.new(e) }
|
422
|
+
|
423
|
+
|
424
|
+
|
425
|
+
#Rect: x, y, w, h
|
426
|
+
srect = Struct.new(:x, :y, :w, :h)
|
427
|
+
stop_width = @image.width - @tilewidth
|
428
|
+
stop_height = @image.height - @tileheight
|
429
|
+
tile_num = 0
|
430
|
+
#calculate how cut image
|
431
|
+
@margin.step(stop_height, @tileheight + @spacing) {|y|
|
432
|
+
@margin.step(stop_width, @tilewidth + @spacing) {|x|
|
433
|
+
@tiles[tile_num] = srect.new(x, y, @tilewidth, @tileheight)
|
434
|
+
tile_num += 1
|
435
|
+
}
|
436
|
+
}
|
437
|
+
end
|
438
|
+
|
439
|
+
class Tile
|
440
|
+
#The local tile ID within its tileset.
|
441
|
+
attr_reader :id
|
442
|
+
|
443
|
+
def initialize(doc)
|
444
|
+
@id = doc.attributes['id']
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
class Image
|
449
|
+
#The reference to the tileset image file (Tiled supports most common image formats).
|
450
|
+
attr_reader :source
|
451
|
+
|
452
|
+
#Defines a specific color that is treated as transparent (example value: "FF00FF" for magenta).
|
453
|
+
attr_reader :trans
|
454
|
+
|
455
|
+
#Width of image
|
456
|
+
attr_reader :width
|
457
|
+
|
458
|
+
#Height of image
|
459
|
+
attr_reader :height
|
460
|
+
|
461
|
+
def initialize(xml)
|
462
|
+
doc = xml.elements['image']
|
463
|
+
@source = doc.attributes['source']
|
464
|
+
@trans = doc.attributes['trans']
|
465
|
+
@width = doc.attributes['width'].to_i
|
466
|
+
@height = doc.attributes['height'].to_i
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
if $0 == __FILE__
|
475
|
+
RETMX.load('test.tmx')
|
476
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: retmx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jovany Leandro G.C
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-10 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: info@manadalibre.org
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/retmx.rb
|
21
|
+
homepage: https://www.manadalibre.org/desarrollos/doku.php?id=videojuegos:librerias:retmx
|
22
|
+
licenses: []
|
23
|
+
post_install_message:
|
24
|
+
rdoc_options: []
|
25
|
+
require_paths:
|
26
|
+
- lib
|
27
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 1.8.11
|
42
|
+
signing_key:
|
43
|
+
specification_version: 3
|
44
|
+
summary: Libray for read TMX (mapeditor.org)
|
45
|
+
test_files: []
|