TileScanner - Grisgram/gml-raptor GitHub Wiki

The TileScanner offers some critical functionality for working with tile layers:

  • Scanning a map into an array of TileInfo structs
  • Finding a tile by its map-position or pixel-position
  • Filtering tiles by their index
  • Filtering tiles to get only those "currently in view"
  • Modifying tiles with all features of the room editor (rotation, flip, mirror, index, set_empty)
  • Converts the bitflag of the tile data to human-understandable rotation values instead of a bit-combination of flip, mirror and rotate
  • Retrieve and restore a delta set of tiles, which contains only those, which were modified at runtime (through the TileScanner), to have a more compact save game file

The TileInfo class

Before going into details about the TileScanner itself, lets take a look at the TileInfo class.
The TileScanner reads the entire map into an array of TileInfo elements. When you access a tile through the TileScanner, you will always get a TileInfo (or an array of them) in return.

TileInfo Functions

All functions of the TileInfo class are chainable, so you can settle multiple changes in one go.

/// @function set_empty()
/// @description Clears this tile
static set_empty = function() {
/// @function set_index(_tile_index)
/// @description Assign a new index to the tile
static set_index = function(_tile_index) {
/// @function set_flags(_flip = undefined, _rotate = undefined, _mirror = undefined)
/// @description Modify the flags of a tile (flip, rotate, mirror)
static set_flags = function(_flip = undefined, _rotate = undefined, _mirror = undefined) {
/// @function set_orientation(_tile_orientation)
/// @description Rotate a tile to a specified orientation
///		 You supply a tile_orientation enum member here:
static set_orientation = function(_tile_orientation) {
enum tile_orientation {
	right	= 0, // rotation   0° (default)
	up	= 1, // rotation  90° ccw
	left	= 2, // rotation 180° ccw
	down	= 3, // rotation 270° ccw
}

Properties of the TileInfo

In addition to the methods to modify a tile, the TileInfo struct offers some very useful information about the tile:

Property Description
scanner A pointer to the scanner, that created this TileInfo
tiledata The real tiledata of the tile
index The index (of the tileset) for this tile
orientation One of the tile_orientation values
empty Boolean, whether this tile is empty
position The position (in map fields!) of the tile
position_px The position (in pixels) in the room of the tile
center_px The center-position (in pixels) in the room of the tile

[!IMPORTANT] Due to the nature of GML, all of these properties are writable, but you should treat them as being read-only!
Only modify a tile through the offered functions, NEVER through the properties!
Only the functions update the tile in the map in the room. If you modify a property directly, you only mess up the data consistency between room and tiledata, but you will not create any visible effect with that.

Setting up a TileScanner

It's as easy as invoking the constructor:

/// @function		TileScanner(layername_or_id, scan_on_create = true)
/// @description	Creates a TileScanner for the specified layer.
///			if scan_on_create is true, the constructor will immediately scan the layer
///			and fill the "tiles" array with data. 
///			If you set it to false, tiles is an empty array of undefined's 
///			until you invoke "scan_layer()"
function TileScanner(layername_or_id = undefined, scan_on_create = true) constructor {

Access a tile

/// @function get_tile_at(map_x, map_y)
/// @description Gets the TileInfo object at the specified map coordinates.
///		 To get a tile from pixel coordinates, use get_tile_at_px(...)
static get_tile_at = function(map_x, map_y) {
/// @function get_tile_at_px(_x, _y)
/// @description Gets the TileInfo object at the specified pixel coordinates.
///		 To get a tile from map coordinates, use get_tile_at(...)
static get_tile_at_px = function(_x, _y) {

Both of these functions return undefined, if the supplied coordinates are outside of the map boundaries. They will not crash on invalid coordinates.

Here's a short example of a typical use case:

#macro DOOR_TILE_CLOSED		41
#macro DOOR_TILE_OPENED		42

if (scanner.get_tile_at_px(PLAYER.x,PLAYER.y).index == DOOR_TILE_CLOSED)
    scanner
        .get_tile_at_px(PLAYER.x, PLAYER.y)
            .set_index(DOOR_TILE_OPENED);

Finding/Filtering Tiles

In addition to looking up a single tile, as shown above, the word "Tile Scanner" suggests, that there's more than cherry-picking single tiles.
TileScanner can filter the huge array of tiles for you, so you get only those, you are interested in, for any specific situation.

Filtering has two levels:

  • Filter all tiles of specific type(s)
  • Filter tiles, which are currently in view (visible on screen)
/// @function		find_tiles(indices...)
/// @description	scans the layer for tiles. Specify up to 16 tile indices you want to find
///			either directly as arguments or specify an array, containing the indices, if
///			you are looking for more than 16 different tiles.
///			NOTE: If you supply an array, this must be the ONLY argument!
/// @returns {array}	Returns an array of TileInfo structs.
static find_tiles = function() {
/// @function		find_tiles_in_view(_tiles_array = undefined, _camera_index = 0, _viewport_index = 0)
/// @description	Returns only the tiles from the specified _tiles_array, that are currently in view
///			of the specified camera.
///			NOTE: if you do not specify a _tiles_array, the internal tiles array of the scanner is used,
///			which contains all tiles of the level.
///			But you may also supply a pre-filtered array, like a return value of find_tiles(...)
/// @returns {array}	Returns an array of TileInfo structs.
static find_tiles_in_view = function(_tiles_array = undefined, _camera_index = 0) {

[!TIP] PLEASE NOTE: The second function, find_tiles_in_view takes an array as first argument.
This means, you can do a two-step-filtering here! In your first step, you create an array of TileInfo structs of the types you are interested in (like walls or floors), and in a second step, you can reduce this to only the walls/floors currently in view!
I recommend, that you prepare some pre-filtered arrays of tile types at the beginning of your level and then use those specialized arrays to strip them down to only the tiles visible to the player. find_tiles_in_view does not modify the array, so you can reuse your prefiltered array as often as you like.

Preparing for save and restore

TileMaps can get huge and with the amount of properties offered, the size of your savegame can easily reach 50+ Megabytes, if you save the entire array of your tilemap.
Therefore it is very important, to reduce this amount of information as much as possible.

Every method you invoke on a TileInfo class sets the tile in a modified state.
When saving your game, you can request a delta map of the tile map, containing only those tiles, that were modified during runtime.

When loading your game, you can supply this loaded delta map to the TileScanner to restore the map state it had, when the game was saved.

/// @function get_modified_tiles()
/// @description Gets an array of tiles that have been modified during runtime.
///		 ATTENTION! This is only for saving them to the savegame.
///		 Upon game load, invoke "restore_modified_tiles" with this array to
///		 recover all changes
static get_modified_tiles = function() {
/// @function restore_modified_tiles(_modified_tiles)
/// @description Recovers all changed tiles from a savegame.
///		 ATTENTION! This can only be used with the return value of "get_modified_tiles"!
static restore_modified_tiles = function(_modified_tiles) {

How to use those functions

The typical scenario is, that one of your saveable objects (maybe your... MapController?) receives User Event 14 upon save, and User Event 15 upon load. For details about those events, please see the Savegame System.

Here is a typical example for saving and restoring a tile map through the GLOBALDATA object, which is always part of the savegame:

// This is User Event 14 (saving)
// In this example, the TileScanner is created by the ROOMCONTROLLER
GLOBALDATA.map_data = ROOMCONTROLLER.scanner.get_modified_tiles();

// This is User Event 15 (loading)
ROOMCONTROLLER.scanner.restore_modified_tiles(GLOBALDATA.map_data);