TileScanner - coldrockgames/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 set_... functions of the TileInfo class are chainable, so you can settle multiple changes in one go.

/// @func	is_empty()
static is_empty = function() {
/// @func	set_empty()
/// @desc	Clears this tile
static set_empty = function() {
/// @func	get_index()
static get_index = function() {
/// @func	set_index(_tile_index)
/// @desc	Assign a new index to the tile
static set_index = function(_tile_index) {
/// @func	set_flags(_flip = undefined, _rotate = undefined, _mirror = undefined)
/// @desc	Modify the flags of a tile (flip, rotate, mirror)
static set_flags = function(_flip = undefined, _rotate = undefined, _mirror = undefined) {
/// @func	set_orientation(_tile_orientation)
/// @desc	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
}
/// @func	is_flipped()
/// @desc	Returns whether this tile has the "flip" flag set
static is_flipped = function() {
/// @func	is_mirrored()
/// @desc	Returns whether this tile has the "mirror" flag set
static is_mirrored = function() {
/// @func	is_rotated()
/// @desc	Returns whether this tile has the "rotate" flag set
static is_rotated = function() {

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.

Creating a TileScanner

The constructor is straightforward.
You set the layer (name or id) and tell the scanner, whether to scan immediately or not.

[!NOTE] If you don't scan on create, you have to call scan_layer manually, whenever you are done with room setup (or whatever the reason was, why you didn't scan on create).

The final argument of the constructor lets you change the default data type to use to store tile information. You may supply any class here, but keep in mind, that it must be a child of TypeInfo. Otherwise, your game will crash.

/// @func	TileScanner(_layername_or_id, _scan_on_create = true, _tileinfo_type = TileInfo)
/// @desc	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()"
///		You may derive a class from TileInfo() and supply it as final argument.
///		TileScanner will use this to instantiate the TileInfo members when scanning.
///		NOTE: Supplying a type that is not a child of TileInfo will crash the game!
function TileScanner(_layername_or_id = undefined, _scan_on_create = true, _tileinfo_type = TileInfo) constructor {

TileScanner Functions

Layer and Scanning

/// @func	set_layer(_layername_or_id, scan_now = true)
/// @desc	Sets or changes the layer used. 
///		NOTE: This will delete all currently stored TileInfos!
static set_layer = function(_layername_or_id, scan_now = true) {
/// @func	scan_layer()
/// @desc	Returns (and fills) the "tiles" array of this TileScanner
static scan_layer = function() {

Savegame Management

TileMaps can get huge and with the amount of properties offered, the size of your savegame can easily reach many 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.

/// @func	get_modified_tiles()
/// @desc	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() {
/// @func	restore_modified_tiles(_modified_tiles)
/// @desc	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);

Finding Tiles

/// @func	find_tiles(indices...)
/// @desc	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 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() {
/// @func	find_tiles_in_view(_tiles_array = undefined, _camera_index = 0, _viewport_index = 0)
/// @desc	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] 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.

/// @func 	get_tile_at(map_x, map_y)
/// @desc 	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) {
/// @func 	get_tile_at_px(_x, _y)
/// @desc 	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 the get_tile_at... 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);

Manipulating Maps

/// @func	fill_map(_index = 0) 
/// @desc	Clears the entire layer, all tiles get the specified _index
static fill_map = function(_index = 0) {
/// @func	fill_area(_index, _left, _top, _width, _height) 
/// @desc	Fills an area in map coordinates in the layer with a specified tile index
static fill_area = function(_index, _left, _top, _width, _height) {
/// @func	fill_area_px(_index, _left, _top, _width, _height) 
/// @desc	Fills an area in pixel coordinates in the layer with a specified tile index
///		NOTE: ALL coordinates are in px here! Even _width and _height!
static fill_area_px = function(_index, _left, _top, _width, _height) {