Console::Map
console 0.2.0 · app/console/map.rb · 380 lines · view source
Console::Map -- load a level out of an LDTK export.
LDTK (https://ldtk.io) is the one editor whose data model is close enough to the console's to be worth native support: it places entities in a level, and an entity here is already a rect with a kind and a bag of properties.
map = Console::Map.load 'levels/Level_0.ldtk' map.solids # collision rects, harvested from IntGrid layers map.entities # [{ kind: :enemy, x:, y:, w:, h:, hp: 3, label: 'boss' }] map.spawn_all # hand every entity to the entity store
Two details decide whether this works or quietly does not, so they are worth stating up front.
COORDINATES. LDTK is a top-left origin with y growing DOWN. The console (and DragonRuby underneath it) is bottom-left with y growing UP. Everything is flipped on load, so a platform near the top of the LDTK level lands at a high y -- which is what bottom-left thinking expects. Pass flip_y: false to keep raw LDTK coordinates.
FIELD TYPES. An Int/Float/String/Bool field stores its payload under 'value'. A Point field does NOT: it stores 'cx'/'cy' as siblings and the 'value' key is absent entirely. In mruby an absent key is not a nil value -- chaining into one raises :[] method missing on ~NilClass~ rather than returning nil. So every field is read through a __type branch and never assumed to have a 'value'. See self.field_value.
def initialize(data, options = {})- line 110
def entities_of(kind)-
Entities of one kind, by LDTK identifier ('Enemy') or console kind (:enemy). Both spellings work because LDTK's own naming is PascalCase.
def spawn_all(extra = {})-
Push every entity into the store and return what was created.
kind:is taken from the LDTK __identifier, so an 'Enemy' entity becomes a :enemy entity without the cart naming anything. Extra keys are merged per entity, for carts that want to stamp a cart-specific id. def to_s- line 154
def read_layers(level)- line 193
entities
def read_entity_layer(layer)- line 210
def solid_entity?(inst, attrs)-
Whether an entity is collision rather than a thing to spawn.
The map gets to decide this, so a cart never has to keep a list of every entity type in the level that happens to be solid -- a list that silently rots the moment someone adds a platform in LDTK. Three ways to say it, in order of explicitness:
1. `solid_entities:` names the LDTK identifier outright. 2. The entity carries a truthy `Solid` bool field. 3. The entity carries a truthy `OneWay` bool field. A pass-through platform is still a platform, so one-way implies solid instead of being treated as an unrelated flag.
(2) and (3) are read off the entity instance, and LDTK bakes an entity type's field defaults into every placed copy -- so ticking
Solidonce on the Platform TYPE in the editor marks every Platform in the level, and there is nothing left to remember on the console side. def entity_attributes(inst)-
LDTK entity -> console entity hash.
Fields are applied BEFORE the geometry so that a stray field can never overwrite x/y/w/h and produce an entity with no usable rect.
def solid_from_entity(attrs)- line 282
intgrid (collision)
def read_intgrid_layer(layer)-
LDTK's own convention for collision: an IntGrid layer where any non-zero cell is solid. The layer's grid size is the tile size, and rows run top-down, so each row is mirrored into console space.
def one_way_cell?(value)-
one_way_value:lets a level mark pass-through platforms with a distinct IntGrid value (2, say) instead of requiring a second layer.
tiles
def read_tile_layer(layer)-
Tiles are collected but not drawn: this is data, not a renderer. Each entry keeps both LDTK's source position and a console-space px so a future draw pass never has to re-derive the flip.
def console_y(ldtk_y, height = 0)-
LDTK y (down, from the top of the level) -> console y (up, from the bottom), converting a top-down height to a bottom-up one.