1.13 - Map

Maps are areas where the game takes place. They may be rooms, houses, entire dungeon floors, parts of the outside world or any place. The active map contains many objects called map entities (or just "entities" to be short). Map entities include everything that has a position on the map: the hero, the tiles, the enemies, the pickable treasures, etc. See the entity API for more details.

Overview

Coordinates and layer

A map has a rectangular size in pixels. The width and the height are always multiples of 8 pixels, and most map entities usually stay aligned on a grid of squares of 8x8 pixels (except when they are moving).

Thus, each entity has some coordinates X,Y on the map. But its position is also defined by a third value: its layer. The map has a number of distinct layers that are stacked. Each layer has its own set of entities. Layers are identified by a number. There is always a layer 0, and maps can add additional layers above or below 0.

Layers allow to implement maps with multi-level content, like a bridge between two higher platforms. The hero (as well as enemies and any other map entity) is then able to walk either above or under the bridge, depending on his layer. Entities like stairs and jumpers can change the layer of the hero automatically, and you can also do that from your Lua scripts.

Tileset

The graphic skin of a map is called a tileset. The tileset defines the small patterns used to draw tiles and also some other entities that may depend on the skin. For example, you can have a forest tileset, a castle tileset, etc. Each map has only one tileset, but tilesets have no special size limitation.

Map files

A map can contain many types of entities. Map entities can be either declared in the map data file or created dynamically. Thus, a map with id XXXX is managed by two distinct files:

When the player enters a map, the engine first creates the entities declared in the map data file maps/XXXX.dat, and then it runs your script "maps/XXXX.lua". The Lua map object is passed as parameter of your script (remember that any Lua script is implicitly a function and can have parameters). Use the Lua notation "..." to get this parameter and store it into a regular variable.

Here is a basic example of script for a map that does nothing special except playing a special music and showing an evil welcome dialog when the hero enters it.

-- First, we put the parameter into a variable called "my_map".
-- (In Lua, the notation "..." refers to the parameter(s) of the script.)
local my_map = ...

-- Event called when the player enters the map, at initialization time.
function my_map:on_started()

  -- Play an evil music.
  sol.audio.play_music("evil")
end

-- Event called when the player enters the map, after the opening transition.
function my_map:on_opening_transition_finished()

  -- Show an evil welcome dialog.
  my_map:get_game():start_dialog("welcome_to_darkness")
end

In practice, many maps have short scripts like this or even no script at all. Sometimes, everything that you define in the data file (with the help of the quest editor) is enough. You need a script when there is something dynamic to program on your map: opening a door when a pressure plate is pressed, making a treasure chest appear when some enemies are killed, showing a dialog with an NPC that depends on whether the player has accomplished a particular action before, etc.

Lifetime of maps

At any moment, during the game, only one map is active: the one drawn on the game screen. That map is also the one where the hero is currently located. Only the active map has its entities living in it. Other maps are not loaded. If the player leaves the map and comes back later, your Lua map object will be a new one the second time. Your entities will be reloaded as specified in the map data file and your map script will be executed again.

If you want some data or behavior to persist when the player comes back, like the position of an NPC or whether a puzzle was solved, you can store the information into the savegame. Alternatively, if you don't want these data to be saved, but you want to remember then during the current game session, you can store the information as a field of the game object. The game object persists until the player stops playing and comes back to the title screen or restarts the game.

Remarks
Some entities save their state automatically if you want, like whether a treasure chest is open, whether a door is open or whether an enemy is killed. Therefore, you have nothing to do for such entities: they keep their state.

Accessing maps like tables

Like other essential Solarus types (including game, items and map entities), a fundamental property of maps is that even if they are userdata, they can also be used like Lua tables.

This property is actually what allows you to define callbacks on your map. But you can store any data in your map object, including new functions specific to your particular map. Here is an example that uses this feature:

-- Example of a map with 3 switches to activate in the correct order.
-- We assume that 3 switches exist with names "puzzle_switch_1", "puzzle_switch_2" and "puzzle_switch_3".
local map = ...

function map:on_started()
  map.next_switch_index = 1
  -- Equivalent to: map["next_switch_index"] = 1
end

-- Called when the player walks on the switch named "puzzle_switch_1".
function puzzle_switch_1:on_activated()
  -- Check that puzzle_switch_1 is the correct switch to activate.
  map:check_switch(self)
end

function puzzle_switch_2:on_activated()
  map:check_switch(self)
end

function puzzle_switch_3:on_activated()
  map:check_switch(self)
end

function map:check_switch(switch)
  if switch:get_name() == "puzzle_switch_" .. map.next_switch_index then
    -- Okay so far.
    map.next_switch_index = map.next_switch_index + 1
    if map.next_switch_index > 3 then
      -- Finished!
      sol.audio.play_sound("secret")
      map:get_game():start_dialog("congratulations")
    end
  else
    -- Wrong switch: reset the puzzle.
    sol.audio.play_sound("wrong")
    puzzle_switch_1:set_activated(false)
    puzzle_switch_2:set_activated(false)
    puzzle_switch_3:set_activated(false)
    map.next_switch_index = 1
  end
end

In this example, you see that we add three values to the map object, like it was a table: next_switch_index (a number), on_started (a function) and check_switch (a function). Actually, we also use this feature on the three switches: we add a value on_activated on them.

Remarks
You may wonder how we can access puzzle_switch_1, puzzle_switch_2 and puzzle_switch_3 without declaring them. There is a mechanism that makes all named map entities directly accessible in the environment of the map script. See map:get_entity() for more details.

But this example has some issues. The three switches are managed by duplicated code. This is error-prone because one day, we will probably want to make a similar puzzle with 50 entities instead of just 3. To make your map easier to maintain, the following and equivalent version is much more preferred:

local map = ...

function map:on_started()
  map.next_switch_index = 1
  map.nb_switches = map:get_entities_count("puzzle_switch_")
end

local function puzzle_switch_activated(switch)
  map:check_switch(switch)
end

-- Define the same on_activated() method for all entities whose name
-- starts with "puzzle_switch_".
for switch in map:get_entities("puzzle_switch_") do
  switch.on_activated = puzzle_switch_activated
end

function map:check_switch(switch)
  if switch:get_name() == "puzzle_switch_" .. map.next_switch_index then
    -- Okay so far.
    map.next_switch_index = map.next_switch_index + 1
    if map.next_switch_index > map.nb_switches then
      -- Finished!
      sol.audio.play_sound("secret")
      map:start_dialog("congratulations")
    end
  else
    -- Wrong switch: reset the puzzle.
    sol.audio.play_sound("wrong")
    for s in map:get_entities("puzzle_switch_") do
      s:set_activated(false)
    end
    map.next_switch_index = 1
  end
end

This improved version is more evolutive: it does not even hardcode the number of switches in the puzzle. Thus, if you add a switch called "puzzle_switch_4" from the editor one day, the script will be directly take it into account in the puzzle.

Methods of the type map

map:get_id()

Returns the id of this map.

Remarks
This id appears in the name of map files.

map:get_game()

Returns the current game.

map:get_world()

Returns the world name that was set on this map.

The world name is an optional property defined in the map data file. Worlds allow to group maps together. The world can be any arbitrary name. Maps that have the same world name are considered to be part of the same environment. For example, your map can be in a world named "outside_world", "dungeon_1" or "some_scary_cave". A map that has no world is always considered to be alone in its own environment.

The world property is used to decide when to set the starting location of the player (the place where he starts when loading his savegame). By default, the starting location is automatically set by the engine when the world changes (not when the map changes). This can be changed by defining the "Save starting location" property of destinations, from the quest editor or from a script (with destination:set_starting_location_mode()).

Some other features may also rely on the world property, like the state of crystal blocks. Their state persists between all maps of the current world and is reset when entering a map whose world is different.

map:set_world(world)

Changes the world of this map.

The world property remains until the map is destroyed: If you reload the same map again later, the world is reset to the one defined in the map data file.

map:get_floor()

Returns the floor of the current map if any.

The floor is an optional property defined in the map data file.

The engine does not do anything particular with this floor property. But you can use it in scripts, for example to show the current floor on the HUD when it changes or to make a minimap menu.

map:set_floor(floor)

Changes the floor of this map.

The floor property remains until the map is destroyed: If you reload the same map again later, the floor is reset to the one defined in the map data file.

map:get_min_layer()

Returns the index of the lowest layer of this map.

map:get_max_layer()

Returns the index of the highest layer of this map.

map:get_size()

Returns the size of this map in pixels.

map:get_location()

Returns the x,y location of this map in its world.

The engine uses this information to implement scrolling between two adjacent maps.

For example, you can also use this property in scripts if you want to show the position of the hero on the minimap menu of your outside world. Indeed, your outside world is probably not a single map, but it is usually composed of several adjacent maps.

map:get_tileset()

Returns the name of the tileset of the current map.

map:set_tileset(tileset_id)

Changes the tileset of the current map.

It is your responsability to make sure that the new tileset is be compatible with the previous one: every tile of the previous tileset must exist in the new one and have the exact same properties, and only the images can differ.

Remarks
If the new tileset is not compatible with the previous one, tiles will be displayed with wrong graphics.

map:get_music()

Returns the name of the music associated to this map.

This is the music to play when the map starts, as specified in the map file. It may be different from the music currently being played. To get the music currently being played, see sol.audio.get_music().

map:get_camera()

Returns the camera entity of the map.

map:get_ground(x, y, layer)

Returns the kind of ground (terrain) of a point.

The ground is defined by tiles (and other entities that may change it like dynamic tiles) that overlap this point.

map:draw_visual(drawable, x, y)

Draws a drawable object (surface, text surface or sprite) on the camera at the given map coordinates.

This function can be used as an alternative to drawable:draw() in order to draw the object relative to the map (instead of relative to the screen).

If the object to draw is a sprite, its origin point will be displayed at the given location, relative to the the upper left corner of the map.

This function should only be called during the drawing phase of the map, for example from map:on_draw() or from custom_entity:on_pre_draw().

map:draw_sprite(sprite, x, y)

Draws a sprite on the screen at the given map coordinates.

Warning
This method is deprecated since Solarus 1.5.

Use map:draw_visual() instead.

map:get_crystal_state()

Returns the configuration of crystal blocks.

map:set_crystal_state(state)

Sets the configuration of crystal blocks.

This state persists accross maps of the same world. It is reset when the world changes and when the savegame is reloaded.

map:change_crystal_state()

Inverts the configuration of crystal blocks.

Remarks
Equivalent to map:set_crystal_state(not map:get_crystal_state()).

map:open_doors(prefix)

Opens the doors whose name starts with the specified prefix, enables or disables relative dynamic tiles accordingly and plays the "door_open" sound.

Opening a door may be more complex than just modifying a single door entity. Indeed, there is often a corresponding door is the adjacent room that you also want to open (that corresponding door is another entity). Name both doors with the same prefix, and you can use this function to open both of them.

Furthermore, you sometimes want dynamic tiles to be shown or hidden depending on the state of a door. When a door is open, all dynamic tiles whose prefix is the door's name followed by _open or _closed are automatically enabled or disabled, respectively.

Remarks
The doors will be really closed once the opening animation of their sprite is finished. However, they immediately become obstalces.

map:close_doors(prefix)

Closes the doors whose name starts with the specified prefix, enables or disables relative dynamic tiles accordingly and plays the "door_closed" sound.

Closing a door may be more complex than just modifying a single door entity. Indeed, there is often a corresponding door is the adjacent room that you also want to open (that corresponding door is another entity). Name both doors with the same prefix, and you can use this function to close both of them.

Furthermore, you sometimes want dynamic tiles to be shown or hidden depending on the state of a door. When a door is closed, all dynamic tiles whose prefix is the door's name followed by _open or _closed are automatically disabled or enabled, respectively.

map:set_doors_open(prefix, [open])

Like map:open_doors() or map:close_doors(), but does not play any sound or any sprite animation.

This function is intended to be called when you don't want the player to notice the change, typically when your map starts (i.e. from the map:on_started() event).

map:get_entity(name)

Returns the map entity with the specified name if it exists on this map. Entity names are unique (two entities cannot exist on the map with the same name at the same time). The name is optional: some entities may have no name. In this case, you cannot access them from this function.

As a convenient feature, map entities can also be accessed directly through the environment of the map script. In other words, you can just write bob:get_position() as an equivalent to map:get_entity("bob"):get_position().

Remarks
Technical note for curious Lua experts: the mechanism that makes map entities directly accessible in the map script environment is lazy (it is implemented as an __index metamethod). Entities are imported to the Lua side only when your script requests them. If you have thousands of named entities in your map, you won't have thousands of useless objects living in Lua. Only the ones your script tries to access are imported.

map:has_entity(name)

Returns whether there currently exists a map entity with the specified name on the map.

Remarks
Equivalent to map:get_entity(name) ~= nil (but a bit lighter because it avoids to export the entity to Lua).

map:get_entities([prefix])

Returns an iterator to all map entities whose name has the specified prefix.

The typical usage of this function is:

for entity in map:get_entities("your_prefix") do
  -- some code related to the entity
end

map:get_entities_count(prefix)

Returns the number of map entities having the specified prefix.

map:has_entities(prefix)

Returns whether there exists at least one map entity having the specified prefix.

This function can be used for example to checker whether a group of enemies is dead.

Remarks
Equivalent to map:get_entities_count(prefix) > 0 but faster when there are a lot of entities (because it stops searching as soon as there is a match).

map:get_entities_by_type(type)

Returns an iterator to all map entities of the given type on the map.

The typical usage of this function is:

for entity in map:get_entities_by_type(type) do
  -- some code related to the entity
end

map:get_entities_in_rectangle(x, y, width, height)

Returns an iterator to all map entities whose maximum bounding box intersects the given rectangle. The maximum bounding box is the union of the entity's own bounding box and of the bounding boxes from its sprites.

The typical usage of this function is:

for entity in map:get_entities_in_rectangle(x, y, width, height) do
  -- some code related to the entity
end

map:get_entities_in_region(x, y), map:get_entities_in_region(entity)

Returns an iterator to all map entities that are in a region. Regions of the map are defined by the position of separators and map limits. The region of an entity is the one of its center point.

Regions should be rectangular. Non-convex regions, for example with an "L" shape, are not supported by this function.

The typical usage of this function is:

for entity in map:get_entities_in_region(my_entity) do
  -- some code related to the entity
end

To get entities in the same region as a point:

To get entities in the same region as another entity:

map:get_hero()

Returns the hero.

Remarks
Equivalent to map:get_entity("hero") but shorter to write. This function is provided for convenience as getting the hero is often needed.

map:set_entities_enabled(prefix, [enabled])

Enables or disables all map entities having the specified prefix.

Disabled entities are not displayed and are not updated. Therefore, they don't move and their collisions are no longer detected. But they still exist and can be enabled back later.

Remarks
Equivalent to calling entity:set_enabled() on a group of entities.

map:remove_entities(prefix)

Removes and destroys all map entities having the specified prefix.

Once an entity is removed, it is destroyed and it no longer exists on the map. A good practice is to avoid keeping references to destroyed entities in your scripts so that they can be garbage-collected by Lua.

Remarks
Equivalent to calling entity:remove() on a group of entities.

map:create_destination(properties)

Creates an entity of type destination on the map.

map:create_teletransporter(properties)

Creates an entity of type teletransporter on the map.

map:create_pickable(properties)

Creates an entity of type pickable treasure on the map.

map:create_destructible(properties)

Creates an entity of type destructible object on the map.

Remarks
The state of the pickable treasure placed in the destructible object (obtained or not) and the possessed variant of the item (a number) are two independent values that have different meanings and are saved separately.

map:create_chest(properties)

Creates an entity of type treasure chest on the map.

Remarks
The state of the treasure chest (obtained or not) and the possessed variant of its item are two independent values that have different meanings and are saved separately.

map:create_jumper(properties)

Creates an entity of type jumper on the map.

map:create_enemy(properties)

Creates an entity of type enemy on the map.

Remarks
The state of the enemy (alive or dead), the state of its treasure dropped (obtained or not) and the possessed variant of the item dropped (a number) are three independent values that have different meanings and are saved separately.

map:create_npc(properties)

Creates an entity of type non-playing character (NPC) on the map.

map:create_block(properties)

Creates an entity of type block on the map.

map:create_dynamic_tile(properties)

Creates an entity of type dynamic tile on the map.

map:create_switch(properties)

Creates an entity of type switch on the map.

map:create_wall(properties)

Creates an entity of type wall on the map.

map:create_sensor(properties)

Creates an entity of type sensor on the map.

map:create_crystal(properties)

Creates an entity of type crystal on the map.

map:create_crystal_block(properties)

Creates an entity of type crystal block on the map.

map:create_shop_treasure(properties)

Creates an entity of type shop treasure on the map.

Remarks
The state of the shop treasure (purchased or not) and the possessed variant of its item (a number) are two independent values that have different meanings and are saved separately.

map:create_stream(properties)

Creates an entity of type stream on the map.

map:create_door(properties)

Creates an entity of type door on the map.

map:create_stairs(properties)

Creates an entity of type stairs on the map.

map:create_bomb(properties)

Creates an entity of type bomb on the map.

map:create_explosion(properties)

Creates an entity of type explosion on the map.

map:create_fire(properties)

Creates an entity of type fire on the map.

map:create_separator(properties)

Creates an entity of type separator on the map.

map:create_custom_entity(properties)

Creates an entity of type custom entity on the map.

Events of a map

Events are callback methods automatically called by the engine if you define them. In the case of maps, they are only called on the current map.

map:on_started(destination)

Called when this map starts (when the player enters it).

map:on_finished()

Called when this map stops (when the player leaves it).

map:on_update()

Called at each cycle of the main loop while this map is the current one.

Remarks
As this function is called at each cycle, it is recommended to use other solutions when possible, like timers and other events.

map:on_draw(dst_surface)

Called when the map has just been redrawn by the engine.

The engine has already drawn the map, but not the menus of this map if any. Use this event if you want to draw some additional content on the map before the menus, for example an overlay.

map:on_suspended(suspended)

Called when the map has just been suspended or resumed.

The map is suspended by the engine in a few cases, like when the game is paused or when the camera is being moved by a script. When this happens, all map entities stop moving and most sprites stop their animation.

map:on_opening_transition_finished(destination)

When the map begins, called when the opening transition effect finishes. After that moment, the player has the control of the hero.

map:on_obtaining_treasure(treasure_item, treasure_variant, treasure_savegame_variable)

Called when the hero is obtaining a treasure on this map, before the treasure's dialog (if any).

map:on_obtained_treasure(treasure_item, treasure_variant, treasure_savegame_variable)

Called after the hero has obtained a treasure on this map.

In the case of a brandished treasure, this event is called once the treasure's dialog is finished. Otherwise, it is called immediately after map:on_obtaining_treasure().

map:on_key_pressed(key, modifiers)

Called when the user presses a keyboard key while your map is active.

Remarks
This event indicates the raw keyboard key pressed. If you want the corresponding character instead (if any), see map:on_character_pressed(). If you want the corresponding higher-level game command (if any), see map:on_command_pressed().

map:on_key_released(key, modifiers)

Called when the user releases a keyboard key while your map is active.

Remarks
This event indicates the raw keyboard key pressed. If you want the corresponding character instead (if any), see map:on_character_pressed(). If you want the corresponding higher-level game command (if any), see map:on_command_pressed().

map:on_character_pressed(character)

Called when the user enters text while your map is active.

Remarks
When a character key is pressed, two events are called: map:on_key_pressed() (indicating the raw key) and map:on_character_pressed() (indicating the utf-8 character). If your script needs to input text from the user, map:on_character_pressed() is what you want because it considers the keyboard's layout and gives you international utf-8 strings.

map:on_joypad_button_pressed(button)

Called when the user presses a joypad button while your map is active.

map:on_joypad_button_released(button)

Called when the user releases a joypad button while your map is active.

map:on_joypad_axis_moved(axis, state)

Called when the user moves a joypad axis while your map is active.

map:on_joypad_hat_moved(hat, direction8)

Called when the user moves a joypad hat while your map is active.

map:on_command_pressed(command)

Called when the player presses a game command (a keyboard key or a joypad action mapped to a built-in game behavior) while this map is active. You can use this event to override the normal built-in behavior of the game command.

Remarks
This event is not triggered if you already handled its underlying low-level keyboard or joypad event.

map:on_command_released(command)

Called when the player released a game command (a keyboard key or a joypad action mapped to a built-in game behavior). while this map is active. You can use this event to override the normal built-in behavior of the game command.

Remarks
This event is not triggered if you already handled its underlying low-level keyboard or joypad event.

map:on_mouse_pressed(button, x, y)

Called when the user presses a mouse button while this map is active.

map:on_mouse_released(button, x, y)

Called when the user releases a mouse button while this map is active.

map:on_finger_pressed(finger, x, y, pressure)

Called when the user presses a finger while this map is active.

map:on_finger_released(finger, x, y, pressure)

Called when the user releases a finger while this map is active.

map:on_finger_moved(finger, x, y, dx, dy, pressure)

Called when the user moves a finger while this map is active.

Deprecated methods of the type map

The following methods are deprecated and may be removed it future releases.

map:get_camera_position()

Returns the currently visible area of the map.

Warning
This method is deprecated because since Solarus 1.5, the camera is now an entity. Therefore, you can get its coordinates and size like any other entity.

Use map:get_camera():get_bounding_box() instead.

map:move_camera(x, y, speed, callback, [delay_before], [delay_after])

Starts a camera moving sequence.

Warning
This method is deprecated because since Solarus 1.5, the camera is now an entity. Therefore, you can move the camera like any other entity and have much more customization possibilities.

It can be rewritten in pure Lua as follows.

function map:move_camera(x, y, speed, callback, delay_before, delay_after)

  local camera = map:get_camera()
  local game = map:get_game()
  local hero = map:get_hero()

  delay_before = delay_before or 1000
  delay_after = delay_after or 1000

  local back_x, back_y = camera:get_position_to_track(hero)
  game:set_suspended(true)
  camera:start_manual()

  local movement = sol.movement.create("target")
  movement:set_target(camera:get_position_to_track(x, y))
  movement:set_ignore_obstacles(true)
  movement:set_speed(speed)
  movement:start(camera, function()
    local timer_1 = sol.timer.start(map, delay_before, function()
      callback()
      local timer_2 = sol.timer.start(map, delay_after, function()
        local movement = sol.movement.create("target")
        movement:set_target(back_x, back_y)
        movement:set_ignore_obstacles(true)
        movement:set_speed(speed)
        movement:start(camera, function()
          game:set_suspended(false)
          camera:start_tracking(hero)
          if map.on_camera_back ~= nil then
            map:on_camera_back()
          end
        end)
      end)
      timer_2:set_suspended_with_map(false)
    end)
    timer_1:set_suspended_with_map(false)
  end)
end

This function temporarily moves the camera somewhere else, like to a place where a chest or an enemy will appear.

The camera first moves towards a target point. When the target is reached, after a first delay, your callback function is called. Then, after a second delay, the camera moves back towards the hero. The game is suspended during the whole sequence.

Deprecated events of a map

The following events are deprecated and may be removed it future releases.

map:on_camera_back()

After a camera sequence initiated by map:move_camera(), this event is triggered when the camera gets back to the hero.

Warning
This event is deprecated because since Solarus 1.5, camera:move() is deprecated. The camera is now an entity and now has much more customization possibilities.

Use camera:on_state_changed() instead, or the callback parameter of movement:start()