class Demiurge::Tmx::TmxCache
A TmxCache
loads and remembers TMX file maps from the tmx gem. For a variety of reasons, it's not great to reload TMX files every time we need to know about them, but it can also be a problem to store a copy of the parsed version every time and place we use it. Caching is, of course, a time-honored solution to this problem.
@since 0.3.0
Attributes
@return [String] Root directory the cache was created relative to
Public Class Methods
Create the TmxCache
@param options [Hash] Options @option options [String] :root_dir The root directory to read TMX and TSX files relative to @return [void] @since 0.3.0
# File lib/demiurge/tmx.rb, line 273 def initialize(options = {}) @root_dir = options[:root_dir] || Dir.pwd end
Public Instance Methods
Clear the TMX cache. Remove all existing entries. This can potentially break existing TMX-based objects. Objects often refer repeatedly back into the cache to avoid duplicating structures, and to pick up new changes to those structures.
@return [void] @since 0.3.0
# File lib/demiurge/tmx.rb, line 284 def clear_cache @tile_cache = {} nil end
Change the root directory this cache uses. Note that any existing TMX files with a different root would presumably change, so this clears the cache as a side effect.
@param new_root [String] The new directory to use as TMX root for this cache @return [void] @since 0.3.0
# File lib/demiurge/tmx.rb, line 296 def root_dir=(new_root) clear_cache @root_dir = new_root nil end
Return a TMX entry for the given layout type and filename. A “layout type” is something like normal TMX (called “tmx”) or Manasource-style (called “manasource”.) But additional types are likely to be added in future versions of Pixiurge.
@param layout_type [String] The subtype of TMX file; currently may be one of “tmx” or “manasource” @param layout_filename [String] The path to the TMX file relative to the tile cache's TMX root directory @return [Hash] The TMX cache entry, in an internal and potentially changeable format @api private @since 0.3.0
# File lib/demiurge/tmx.rb, line 312 def tmx_entry(layout_type, layout_filename) @tile_cache ||= {} @tile_cache[layout_type] ||= {} if @tile_cache[layout_type][layout_filename] return @tile_cache[layout_type][layout_filename] end if layout_type == "manasource" @tile_cache[layout_type][layout_filename] = sprites_from_manasource_tmx(layout_filename) elsif layout_type == "tmx" @tile_cache[layout_type][layout_filename] = sprites_from_tmx(layout_filename) else raise "A TMX location must have a known type of layout (tmx or manasource), not #{layout_type.inspect}!" end @tile_cache[layout_type][layout_filename] end
Private Instance Methods
Extract the animations included in the TMX file
@since 0.1.0
# File lib/demiurge/tmx.rb, line 410 def animations_from_tilesets tilesets tilesets.flat_map do |tileset| (tileset["tiles"] || []).map do |tile| p = tile["properties"] if p && p["animation-frame0"] section = 0 anim = [] while p["animation-frame#{section}"] section_hash = { "frame" => p["animation-frame#{section}"].to_i + tileset[:firstgid], "duration" => p["animation-delay#{section}"].to_i } anim.push section_hash section += 1 end { "tile_anim_#{tile["id"].to_i + tileset[:firstgid]}" => anim } else nil end end.compact end.inject({}, &:merge) end
This is to support TMX files for ManaSource, ManaWorld, Land of Fire, Source of Tales and other Mana Project games. It can't be perfect since there's some variation between them, but it can follow most major conventions.
Load a TMX file and calculate additional information like animations from the given properties. Assume this TMX file obeys ManaSource conventions such as fields for exits and names for layers.
@since 0.1.0
# File lib/demiurge/tmx.rb, line 343 def sprites_from_manasource_tmx(filename) entry = sprites_from_tmx filename stack_layers = entry["map"]["layers"].select { |layer| layer["type"] == "tilelayer" } # Remove the collision layer, add as separate collision top-level entry collision_index = stack_layers.index { |l| l["name"].downcase == "collision" } collision_layer = stack_layers.delete_at collision_index if collision_index # Some games make this true/false, others have separate visibility # or swimmability in it. In general, we'll just expose the data. entry["collision"] = collision_layer["data"] if collision_layer # Remove the heights layer, add as separate heights top-level entry heights_index = stack_layers.index { |l| ["height", "heights"].include?(l["name"].downcase) } heights_layer = stack_layers.delete_at heights_index if heights_index entry["heights"] = heights_layer fringe_index = stack_layers.index { |l| l["name"].downcase == "fringe" } raise ::Demiurge::Errors::TmxFormatError.new("No Fringe layer found in ManaSource TMX File #{filename.inspect}!", "filename" => filename) unless fringe_index stack_layers.each_with_index do |layer, index| # Assign a Z value based on layer depth, with fringe = 0 as a special case layer["z"] = (index - fringe_index) * 10.0 end entry end
Load a TMX file and JSONify it. This includes its various tilesets, such as embedded tilesets and TSX files. Do not assume this TMX file obeys any particular additional conventions beyond basic TMX format.
@since 0.1.0
# File lib/demiurge/tmx.rb, line 377 def sprites_from_tmx(filename) cache_entry = {} # This recursively loads things like tileset .tsx files, so we # change to the root dir. Dir.chdir(@root_dir) do tmx_map = Tmx.load filename filename.sub!(/\.tmx\Z/, ".json") cache_entry["map"] = MultiJson.load(tmx_map.export_to_string(:filename => filename, :format => :json)) end tiles = cache_entry["map"] cache_entry["tilesets"] = tiles["tilesets"] # Add entries to the top-level cache entry, but not inside the "map" structure cache_entry["tmx_name"] = File.basename(filename).split(".")[0] cache_entry["name"] = tiles["name"] || cache_entry["tmx_name"] cache_entry["filename"] = filename cache_entry["dir"] = filename.split("/")[0..-2].join(".") cache_entry["animations"] = animations_from_tilesets cache_entry["tilesets"] cache_entry["objects"] = tiles["layers"].flat_map { |layer| layer["objects"] || [] } # Copy most-used properties into top-level cache entry ["width", "height", "tilewidth", "tileheight"].each do |property| cache_entry[property] = tiles[property] end cache_entry end