Skip to content

Scripting

This page is for reading and changing a map from your own C# code: runtime editing, procedural maps, switching maps and saving them. The two pages under it cover positions and tile queries in detail, and the pathfinding component.

Concept

Everything in the asset lives in one namespace. Add this line to any script that uses it:

using CreativeSpore.RpgMapEditor;

The pathfinding internals (IPathNode, PathFinding) sit in a sub-namespace, CreativeSpore.RpgMapEditor.PathFindingLib. You only need it for Pathfinding.

Three types do most of the work:

Type What it is
AutoTileMap The component on your map object. It holds the tiles, answers collision queries and loads and saves the map.
AutoTileset The tileset asset: the atlas texture plus the collision type of every tile.
AutoTileMapData The map asset. Its Data field holds the saved tiles, one run-length encoded list per layer.

RpgMapHelper is a class of static helper methods that convert world positions to tiles and back, read and write tiles by position, raycast against the map and remove fog of war. It is the easiest way in, and Tiles and Positions covers every method.

A tile is an integer id. Tile 0 is the first tile of the first sub-tileset, and each sub-tileset has 256 ids, so the sub-tileset of a tile is tileId / 256. An id of -1 means an empty cell. To find the id of a tile, select it in the palette while painting: the RPG Map Editor overlay in the Scene view shows it as Selected Tile Id, next to Brush Pos, the tile coordinates under the brush.

Getting the map

AutoTileMap.Instance returns the map in the scene. It is set in the map's Awake in play mode. In edit mode it is set the first time the map loads.

using UnityEngine;
using CreativeSpore.RpgMapEditor;

public class MapInfo : MonoBehaviour
{
    void Start()
    {
        AutoTileMap map = AutoTileMap.Instance;
        Debug.Log("Map size in tiles: " + map.MapTileWidth + " x " + map.MapTileHeight);
    }
}

A few things to know about it:

  • Read it from Start or later. In another script's Awake it can still be null, because Unity does not promise which Awake runs first.
  • There is one map per scene. When a second AutoTileMap wakes up while another one is the instance, the second one destroys its own GameObject. Loading a new scene on its own works, because the old map is gone by the time the new one wakes up. Loading a map scene additively on top of another does not.
  • Instance is reset when play mode starts, so it works with Reload Domain turned off in Enter Play Mode Options.

RpgMapHelper always works on AutoTileMap.Instance. It has no overload that takes a map.

Layers are indices

Every method that reads or writes tiles takes a layer index, iLayer. It is an index into AutoTileMap.MapLayers, the list you see in the map inspector, counted from 0 at the top of the list. Layers are whatever you made them, so look them up instead of hard-coding a number:

AutoTileMap map = AutoTileMap.Instance;

// First layer of a given type, or -1 if there is none.
int ground = map.FindFirstLayerIdx(eLayerType.Ground);

// A layer by the name you gave it in the inspector.
int roofs = map.MapLayers.FindIndex(layer => layer.Name == "Roofs");

Each MapLayer has Name, LayerType, Visible, SortingLayer, SortingOrder and Depth. The layer type decides what the tiles do: only Ground layers take part in collisions, and FogOfWar layers store fog instead of tile ids. Layers explains each type. After changing Depth, SortingLayer, SortingOrder or Visible from code, call UpdateChunkLayersData(). Nothing checks those fields for changes.

Note

Some doc comments in the code still say the layer is "0 ground, 1 ground overlay, 2 overlay". That fixed scheme ended in 1.6.1. The index is only a position in MapLayers.

Changing tiles

SetAutoTile writes one tile by tile coordinates. Tile (0, 0) is the top-left cell, x grows to the right and y grows downwards.

AutoTileMap map = AutoTileMap.Instance;
int ground = map.FindFirstLayerIdx(eLayerType.Ground);

map.SetAutoTile(10, 4, 18, ground);   // put tile 18 at column 10, row 4
map.SetAutoTile(11, 4, -1, ground);   // erase the tile next to it

What SetAutoTile does:

  • It ignores coordinates outside the map.
  • It clamps the id to the range from -1 to the last tile of the tileset, so a too high id paints the last tile instead of failing.
  • It recalculates the autotile shape of the cell and its eight neighbours, and marks the chunks that need redrawing.

In play mode the map redraws marked chunks in its own Update, so the change shows without any extra call. In edit mode the map's Update does not run. Editor scripts call UpdateChunks() after their changes.

Tiles and Positions has RpgMapHelper.SetAutoTileByPosition, which does the same from a world position, such as the mouse.

Filling many tiles

Recalculating neighbours on every write is wasted work when you fill a whole area. Pass refreshTile: false, then call RefreshAllTiles() once at the end. It recalculates every tile of every layer and marks all of them for redrawing.

using UnityEngine;
using CreativeSpore.RpgMapEditor;

public class MeadowGenerator : MonoBehaviour
{
    public int grassTileId = 16;
    public int flowerTileId = 176;

    public void Generate(int seed)
    {
        AutoTileMap map = AutoTileMap.Instance;
        int ground = map.FindFirstLayerIdx(eLayerType.Ground);
        Random.InitState(seed);

        for (int y = 0; y < map.MapTileHeight; ++y)
        {
            for (int x = 0; x < map.MapTileWidth; ++x)
            {
                int id = Random.value < 0.05f ? flowerTileId : grassTileId;
                map.SetAutoTile(x, y, id, ground, false);
            }
        }

        map.RefreshAllTiles();   // one pass for the whole map
        map.UpdateChunks();      // draw now instead of waiting for the next Update
    }
}

ClearMap() empties every layer and removes every chunk. ClearLayer(mapLayer) takes a MapLayer object, not an index. On a normal layer it empties the tiles but does not mark anything for redrawing, so follow it with RefreshAllTiles(). On a fog of war layer it fills the whole layer with fog.

Warning

GetAutoTile and RpgMapHelper.GetAutoTileByPosition return the map's own AutoTile object for a painted cell. Treat it as read-only. Changing its Id does not update the shape, collision or mesh. Write through SetAutoTile instead.

Loading a map

LoadMap() rebuilds the map from the MapData asset, which throws away any tile changes that were not saved into it. You rarely call it yourself, because assigning a different tileset or map asset calls it for you:

public AutoTileMapData houseInterior;

void EnterHouse()
{
    // Save first if the current map was changed and the changes should survive.
    AutoTileMap.Instance.MapData = houseInterior;   // loads it straight away
}

Assigning the asset that is already there does nothing. The map also loads in its own Awake, so a map placed in a scene is ready by the time your Start runs.

LoadMap() finishes before it returns. There is also LoadMapAsync(), which returns an IEnumerator, but nothing in the asset runs it as a coroutine and it has not been tested that way.

To change a map's size from code, save it with the new size and load it again:

AutoTileMap map = AutoTileMap.Instance;
map.SaveMap(300, 200);   // must run while the map still has its old size
map.LoadMap();

New cells are empty. Cells outside the new size are dropped.

Knowing when a load finished

OnMapLoaded is a delegate field on AutoTileMap, called at the end of every load. Add a handler with += and remove it with -=.

using UnityEngine;
using CreativeSpore.RpgMapEditor;

public class MapWatcher : MonoBehaviour
{
    void Start()
    {
        AutoTileMap map = AutoTileMap.Instance;
        map.OnMapLoaded += HandleMapLoaded;
        HandleMapLoaded(map);   // the first load already happened in the map's Awake
    }

    void OnDestroy()
    {
        if (AutoTileMap.Instance != null)
            AutoTileMap.Instance.OnMapLoaded -= HandleMapLoaded;
    }

    void HandleMapLoaded(AutoTileMap map)
    {
        if (!map.IsInitialized)
            return;   // it is also called when the tileset or map asset is missing
        Debug.Log("Loaded a map of " + map.MapTileWidth + " x " + map.MapTileHeight + " tiles");
    }
}

IsInitialized is true once the map has layers in memory. IsLoading is true during a load.

Saving a map

SaveMap() writes the tiles in memory into the MapData asset and returns true on success. It returns false and writes nothing while the map is loading or before it has loaded. HasUnsavedChanges tells you whether any tile changed since the last load or save.

Where the data ends up depends on where the game runs:

  • In the editor, SaveMap() also saves the map asset to disk, in play mode too. The change stays after you stop playing, and Undo does not bring the old tiles back.
  • In a build, assets cannot be written. SaveMap() updates the map object in memory only, and the changes are gone when the game quits. To keep them, store the map as XML (below) in a file or PlayerPrefs.

The map inspector's Save Changes After Playing toggle (SaveChangesAfterPlaying in code) saves play mode changes automatically when the map is disabled. Saving and Loading covers it and the inspector buttons.

XML

AutoTileMapSerializeData, the type of MapData.Data, can write itself as XML and read itself back. That is how you keep a map a player edited in a build, or send one over the network.

using UnityEngine;
using CreativeSpore.RpgMapEditor;

public static class MapXml
{
    public static string Export()
    {
        AutoTileMap map = AutoTileMap.Instance;
        map.SaveMap();                      // copy the current tiles into MapData first
        return map.MapData.Data.GetXmlString();
    }

    public static void Import(string xml)
    {
        // A new map object, so the map asset in your project is left alone.
        AutoTileMapData data = ScriptableObject.CreateInstance<AutoTileMapData>();
        data.Data = AutoTileMapSerializeData.LoadFromXmlString(xml);
        AutoTileMap.Instance.MapData = data;   // assigning it loads it
    }
}
Member What it does
GetXmlString() Returns the map data as an XML string.
SaveToFile(path) Writes the XML to a file, replacing it if it exists.
AutoTileMapSerializeData.LoadFromXmlString(xml) Static. Creates map data from an XML string.
AutoTileMapSerializeData.LoadFromFile(path) Static. Creates map data from an XML file.
CopyData(other) Copies another map data's size, layers and tiles into this one.

The map component also has ShowSaveDialog() and ShowLoadDialog(), which the in-game editor uses. In the editor they open a file dialog. In a build they save to and load from a single PlayerPrefs key, XmlMapData.

Reference

AutoTileMap member What it does
Instance Static. The map in the scene.
Tileset, MapData The tileset and map assets. Assigning a different one loads the map.
MapLayers The layers, in inspector order. Layer indices point into this list.
MapTileWidth, MapTileHeight Map size in tiles.
CellSize Size of one tile in world units. See Tiles and Positions.
ViewCamera The camera RpgMapHelper.GetMouseWorldPosition uses.
IsCollisionEnabled Shown as Collision Enabled in play mode. When off, collision queries report no collisions.
SetAutoTile(x, y, tileId, iLayer, refreshTile) Writes a tile. refreshTile defaults to true.
GetAutoTile(x, y, iLayer) Reads a tile. Never returns null.
IsValidAutoTilePos(x, y) True when the tile coordinates are inside the map.
RefreshAllTiles() Recalculates every tile after writes made with refreshTile: false.
UpdateChunks() Redraws the marked chunks now.
UpdateChunkLayersData() Applies layer depth, sorting and visibility changes.
FindFirstLayerIdx(type), FindLastLayerIdx(type) Index of the first or last layer of a type, or -1.
LoadMap(), SaveMap() Load from and save to MapData.
ClearMap(), ClearLayer(mapLayer) Empty the whole map or one layer.
OnMapLoaded Delegate called at the end of every load.