Class: RGame::Engine::TileMap
- Inherits:
-
Object
- Object
- RGame::Engine::TileMap
- Defined in:
- lib/rgame/engine/tile_map.rb
Overview
An orthogonal tile map parsed from a Tiled .tmx. Holds the per-layer gid arrays and geometry, and answers collision queries via its Tileset.
parse takes a string, so the parsing itself needs no filesystem at all;
load adds the file plumbing on top — reading the .tmx, following it to the
.tsx it names, and working out where the tileset image sits relative to
that. Both belong here. What does not is the image: a texture is a GPU
handle, and the renderer that owns one lives a layer below and may not name
this class (see CLAUDE.md, "The rule points both ways"). So load hands
back a path and stops there.
Constant Summary collapse
- FLIP_MASK =
strip Tiled's flip/rotation flags from a gid
0x1FFFFFFF
Instance Attribute Summary collapse
-
#firstgid ⇒ Object
readonly
Returns the value of attribute firstgid.
-
#height ⇒ Object
readonly
Returns the value of attribute height.
-
#pixel_height ⇒ Object
readonly
Returns the value of attribute pixel_height.
-
#pixel_width ⇒ Object
readonly
Returns the value of attribute pixel_width.
-
#tile_height ⇒ Object
readonly
Returns the value of attribute tile_height.
-
#tile_width ⇒ Object
readonly
Returns the value of attribute tile_width.
-
#tileset ⇒ Object
Returns the value of attribute tileset.
-
#tileset_source ⇒ Object
readonly
Returns the value of attribute tileset_source.
-
#width ⇒ Object
readonly
Returns the value of attribute width.
Class Method Summary collapse
-
.layer_flag?(layer_el, name) ⇒ Boolean
True if a layer carries a Tiled bool custom property
nameset to "true". -
.load(tmx_path) ⇒ Object
Reads a .tmx and everything it points at, returning
[map, image_path]. - .parse(tmx_string) ⇒ Object
Instance Method Summary collapse
-
#above_layer?(index) ⇒ Boolean
Whether a layer is drawn above the actors (a tree canopy, roof, etc.) rather than beneath them.
- #gid(layer_index, col, row) ⇒ Object
- #in_bounds?(col, row) ⇒ Boolean
-
#initialize(width:, height:, tile_width:, tile_height:, tileset_source:, firstgid:, layers:, above: []) ⇒ TileMap
constructor
A new instance of TileMap.
- #layer_count ⇒ Object
- #solid_at?(world_x, world_y) ⇒ Boolean
-
#solid_tile?(col, row) ⇒ Boolean
Solid if any layer has a solid tile at (col, row).
Constructor Details
#initialize(width:, height:, tile_width:, tile_height:, tileset_source:, firstgid:, layers:, above: []) ⇒ TileMap
Returns a new instance of TileMap.
85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/rgame/engine/tile_map.rb', line 85 def initialize(width:, height:, tile_width:, tile_height:, tileset_source:, firstgid:, layers:, above: []) @width = width @height = height @tile_width = tile_width @tile_height = tile_height @pixel_width = width * tile_width @pixel_height = height * tile_height @tileset_source = tileset_source @firstgid = firstgid @above = above @tileset = nil @tiles = build_tiles(layers) end |
Instance Attribute Details
#firstgid ⇒ Object (readonly)
Returns the value of attribute firstgid.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def firstgid @firstgid end |
#height ⇒ Object (readonly)
Returns the value of attribute height.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def height @height end |
#pixel_height ⇒ Object (readonly)
Returns the value of attribute pixel_height.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def pixel_height @pixel_height end |
#pixel_width ⇒ Object (readonly)
Returns the value of attribute pixel_width.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def pixel_width @pixel_width end |
#tile_height ⇒ Object (readonly)
Returns the value of attribute tile_height.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def tile_height @tile_height end |
#tile_width ⇒ Object (readonly)
Returns the value of attribute tile_width.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def tile_width @tile_width end |
#tileset ⇒ Object
Returns the value of attribute tileset.
27 28 29 |
# File 'lib/rgame/engine/tile_map.rb', line 27 def tileset @tileset end |
#tileset_source ⇒ Object (readonly)
Returns the value of attribute tileset_source.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def tileset_source @tileset_source end |
#width ⇒ Object (readonly)
Returns the value of attribute width.
25 26 27 |
# File 'lib/rgame/engine/tile_map.rb', line 25 def width @width end |
Class Method Details
.layer_flag?(layer_el, name) ⇒ Boolean
True if a layer carries a Tiled bool custom property name set to "true".
75 76 77 78 79 80 81 82 83 |
# File 'lib/rgame/engine/tile_map.rb', line 75 def self.layer_flag?(layer_el, name) props = layer_el.elements['properties'] return false unless props props.each_element('property') do |property| return property.attributes['value'] == 'true' if property.attributes['name'] == name end false end |
.load(tmx_path) ⇒ Object
Reads a .tmx and everything it points at, returning [map, image_path].
Two values rather than one because they are two kinds of thing: the map is the grid, and the path is where its pixels happen to live. Keeping the second off the map means a stand-in map in a spec has one less method to answer, and the renderer's protocol stays "things about the grid".
Every path is resolved relative to the file that named it — the .tsx relative to the .tmx, the image relative to the .tsx — which is what Tiled itself writes and what lets a map be moved as a set.
39 40 41 42 43 44 45 46 |
# File 'lib/rgame/engine/tile_map.rb', line 39 def self.load(tmx_path) map = parse(File.read(tmx_path)) tsx_path = File.join(File.dirname(tmx_path), map.tileset_source) map.tileset = Tileset.parse(File.read(tsx_path), firstgid: map.firstgid) [map, File.join(File.dirname(tsx_path), map.tileset.image_source)] end |
.parse(tmx_string) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/rgame/engine/tile_map.rb', line 48 def self.parse(tmx_string) root = REXML::Document.new(tmx_string).root tileset_el = root.elements['tileset'] layers = [] above = [] root.each_element('layer') do |layer_el| raw = Base64.decode64(layer_el.elements['data'].text.strip) gids = Zlib::Inflate.inflate(raw).unpack('V*') gids.map! { |g| g & FLIP_MASK } layers << gids above << layer_flag?(layer_el, 'above') end new( width: root.attributes['width'].to_i, height: root.attributes['height'].to_i, tile_width: root.attributes['tilewidth'].to_i, tile_height: root.attributes['tileheight'].to_i, tileset_source: tileset_el.attributes['source'], firstgid: tileset_el.attributes['firstgid'].to_i, layers: layers, above: above ) end |
Instance Method Details
#above_layer?(index) ⇒ Boolean
Whether a layer is drawn above the actors (a tree canopy, roof, etc.) rather
than beneath them. Marked in Tiled with a bool layer property above; layers
without it default to below. Purely a rendering distinction (collision still
considers every layer).
107 108 109 |
# File 'lib/rgame/engine/tile_map.rb', line 107 def above_layer?(index) @above[index] || false end |
#gid(layer_index, col, row) ⇒ Object
115 116 117 118 119 |
# File 'lib/rgame/engine/tile_map.rb', line 115 def gid(layer_index, col, row) return 0 unless in_bounds?(col, row) @tiles[col, row, layer_index] end |
#in_bounds?(col, row) ⇒ Boolean
111 112 113 |
# File 'lib/rgame/engine/tile_map.rb', line 111 def in_bounds?(col, row) col >= 0 && row >= 0 && col < @width && row < @height end |
#layer_count ⇒ Object
99 100 101 |
# File 'lib/rgame/engine/tile_map.rb', line 99 def layer_count @tiles.depth end |
#solid_at?(world_x, world_y) ⇒ Boolean
130 131 132 |
# File 'lib/rgame/engine/tile_map.rb', line 130 def solid_at?(world_x, world_y) solid_tile?((world_x / @tile_width).floor, (world_y / @tile_height).floor) end |
#solid_tile?(col, row) ⇒ Boolean
Solid if any layer has a solid tile at (col, row). Out of bounds is not solid — the camera/bounds clamp keeps the player inside the map.
123 124 125 126 127 128 |
# File 'lib/rgame/engine/tile_map.rb', line 123 def solid_tile?(col, row) return false unless in_bounds?(col, row) @tiles.depth.times { |layer| return true if @tileset.solid?(@tiles[col, row, layer]) } false end |