Skip to main content

Custom Tile Types

Source Tier Only

This page documents internal engine code available exclusively to Source tier licensees.

MCE uses Unity's Tilemap system with a custom data layer that maps every tile asset to a movement type and battle scenario. This page covers the tile system architecture and how to extend it with custom tile behaviors.

Architecture Overview

The tile system has three layers:

  1. TileType enum -- Defines movement semantics for the GridController.
  2. TileData / SingleTileData -- A ScriptableObject database that maps each TileBase asset to a TileType and a BattleScenario.
  3. Tile behaviours -- MonoBehaviour components placed on tile GameObjects that implement ICharacterInteractingTile or ITileTypeOverride to add runtime logic.
TileBase (Unity asset)
└─ registered in TileData ─► SingleTileData { Tile, TileType, BattleScenario }
└─ instantiated object ─► MonoBehaviour(s) implementing ICharacterInteractingTile

The TileType Enum

Located in Assets/OpenMon/Core/Runtime/World/TileType.cs:

public enum TileType
{
NonExistent, // Tile has no data -- treated as void
Walkable, // Normal ground the player can traverse
NonWalkable, // Solid obstacle (walls, trees, rocks)
Jumpable, // Ledge the player can jump over (one-way)
Bridge, // Bridge surface (layered above water)
BridgeEntrance, // Transition tile between ground and bridge
Water, // Swimmable/surfable water
Waterfall, // Vertical water the player can climb
Slippery, // Ice -- forces movement until hitting an obstacle
WalkableNotBikable // Walkable on foot but not on a bicycle
}

GridController.GetTypeOfTileDirectlyBelowSortOrder() resolves the effective type for any world position by scanning tilemaps from highest to lowest sort order, checking for ITileTypeOverride components first, then falling back to the TileData lookup.

Adding a New TileType Value

  1. Add your value to the TileType enum (e.g., Quicksand).
  2. Handle it in MCECharacterController's movement logic (the switch/if blocks that branch on tile type).
  3. Register tiles of that type in the TileData asset via the Inspector.

The TileData System

TileData (Assets/OpenMon/Core/Runtime/World/TileData.cs) is a ScriptableObject that holds every tile in the project:

[CreateAssetMenu(menuName = "MCE/Maps/TileData", fileName = "TileData")]
public class TileData : OpenMonScriptable<TileData>
{
[SerializeField]
private List<SingleTileData> TileDataCollection = new();
}

Each entry is a SingleTileData:

[Serializable]
public class SingleTileData
{
public TileBase Tile; // Reference to the Unity tile asset
public TileType Type; // Movement semantics
public BattleScenario BattleScenario; // Visual theme for battles on this tile
}

Editor Utilities

TileData exposes several Inspector buttons (via TriInspector [Button]):

ButtonPurpose
AddGroupBatch-add a list of tiles with the same type and battle scenario
PopulateScan the project for all TileBase assets and add any missing ones (marked NonExistent)
CheckInvalidLog errors for tiles still marked NonExistent or missing a BattleScenario
RemoveCollidersStrip physics colliders from all RuleTile entries (MCE uses its own grid collision)
RemoveNullsClean up entries whose tile asset was deleted

Lookup Performance

TileData uses a Dictionary<TileBase, SingleTileData> cache populated on first access. Unknown tiles log a warning once (via missingTileCache) and are treated as walkable.

SiblingTile (Autotile with Connected Neighbors)

SiblingTile (Assets/OpenMon/Core/Runtime/World/Tiles/SiblingTile.cs) extends Unity's RuleTile to support tiles that visually connect to other tile types -- for example, a grass tile that blends into dirt, or a path that joins with road tiles.

[CreateAssetMenu(menuName = "2D/Tiles/SiblingTile", fileName = "SiblingTile")]
public class SiblingTile : RuleTile<SiblingTile.Neighbor>
{
[SerializeField]
private List<TileBase> Siblings = new();

[SerializeField]
private bool DefaultIncludesSibling = true;

public class Neighbor : RuleTile.TilingRule.Neighbor
{
public const int Sibling = 3; // Custom neighbor constant
}
}

How It Works

SiblingTile adds a third neighbor matching mode beyond Unity's built-in "This" and "Not This":

Neighbor ValueMeaning
Sibling (3)Matches if the adjacent tile is in the Siblings list
Not This (2)When DefaultIncludesSibling is true, also excludes siblings
Default (1 / other)When DefaultIncludesSibling is true, siblings also match as "This"

The RuleMatch override:

public override bool RuleMatch(int neighbor, TileBase tile) =>
neighbor switch
{
Neighbor.Sibling => Siblings.Contains(tile),
2 => DefaultIncludesSibling && !Siblings.Contains(tile) && base.RuleMatch(neighbor, tile),
_ => (DefaultIncludesSibling && Siblings.Contains(tile)) || base.RuleMatch(neighbor, tile)
};

Creating a SiblingTile

  1. Right-click in the Project window and select 2D > Tiles > SiblingTile.
  2. Set up tiling rules in the Inspector exactly like a standard RuleTile.
  3. In the Siblings list, drag the tile assets this tile should connect with.
  4. Enable DefaultIncludesSibling if the default green arrow (neighbor = "This") should also match sibling tiles. This is the most common setup for terrain blending.
  5. Use the Sibling neighbor constant in tiling rules when you need rules that fire specifically for sibling adjacency.

Tile Behaviour Interfaces

ICharacterInteractingTile

Tiles that need runtime interaction with characters implement ICharacterInteractingTile. The MCECharacterController calls these hooks during movement:

public interface ICharacterInteractingTile
{
bool HasBlockingInteraction();

// Async (coroutine) hooks -- called during movement animation
IEnumerator CharacterAboutToEnterTileAsync(MCECharacterController controller);
IEnumerator CharacterEnterTileAsync(MCECharacterController controller);
IEnumerator CharacterAboutToLeaveTileAsync(MCECharacterController controller);
IEnumerator CharacterLeftTileAsync(MCECharacterController controller);

// Callback hooks -- can cancel movement or suppress wild encounters
IEnumerator CharacterAboutToEnterTile(MCECharacterController controller, Action<bool, bool> finished);
IEnumerator CharacterEnterTile(MCECharacterController controller, Action<bool> finished);
IEnumerator CharacterAboutToLeaveTile(MCECharacterController controller, Action<bool> finished);
IEnumerator CharacterLeftTile(MCECharacterController controller, Action<bool> finished);
}

The callback variants receive an Action<bool> (or Action<bool, bool>) that lets the tile control whether the character can continue moving and whether wild encounters should trigger.

ITileTypeOverride

Implement this interface to override the TileType at runtime based on context (position, sort order, game state):

public interface ITileTypeOverride
{
TileType GetOverride(GridController gridController,
Tilemap tilemap,
TileBase tile,
Vector3Int tilePosition,
int sortOrder);
}

The simplest implementation is TileTypeOverride, which returns a fixed type:

public class TileTypeOverride : OpenMonBehaviour<TileTypeOverride>, ITileTypeOverride
{
[SerializeField]
private TileType Override;

public TileType GetOverride(...) => Override;
}

Built-In Tile Behaviours

MCE ships with several tile behaviours you can use as reference implementations:

ClassPurpose
TallGrassAdjusts sprite sort order when a character walks through; plays VFX on entry; toggles character shadow
ForceCharacterToKeepMovingForces continuous movement in a direction (conveyor belts, currents). Has HasBlockingInteraction() => true
OneWayJumpableTileRestricts a ledge jump to a single direction
BlockMovementDirectionsBlocks movement in specific directions from a tile
LadderUpAdjusts sprite sort order for vertical ladder traversal
EncounterTileMarks a tile as a wild encounter zone with a specific EncounterType
TileTypeOverrideOverrides the TileType at runtime
TileTypeOfTileBelowInherits the type from the tile directly below in sort order
WaterfallEdgeHandles waterfall edge behavior
WhirlwindInDirectionPushes the character in a wind direction

Creating a Custom Tile Behaviour

Here is a step-by-step example of creating a "Quicksand" tile that slows the player and suppresses encounters:

Step 1: Create the MonoBehaviour

using System;
using System.Collections;
using OpenMon.Foundation.Behaviours;
using MCECharacterController = OpenMon.MCE.Characters.MCECharacterController;

namespace OpenMon.MCE.World.Tiles
{
public class QuicksandTile : OpenMonBehaviour<QuicksandTile>, ICharacterInteractingTile
{
public bool HasBlockingInteraction() => false;

public IEnumerator CharacterAboutToEnterTileAsync(MCECharacterController controller)
{
// Slow down the character
controller.SpeedMultiplier = 0.5f;
yield break;
}

public IEnumerator CharacterLeftTileAsync(MCECharacterController controller)
{
// Restore normal speed
controller.SpeedMultiplier = 1.0f;
yield break;
}

// Suppress wild encounters on this tile
public IEnumerator CharacterEnterTile(MCECharacterController controller, Action<bool> finished)
{
finished.Invoke(false); // false = do NOT trigger encounters
yield break;
}

// Remaining hooks -- no-op
public IEnumerator CharacterEnterTileAsync(MCECharacterController c) { yield break; }
public IEnumerator CharacterAboutToLeaveTileAsync(MCECharacterController c) { yield break; }
public IEnumerator CharacterAboutToEnterTile(MCECharacterController c, Action<bool, bool> f) { yield break; }
public IEnumerator CharacterAboutToLeaveTile(MCECharacterController c, Action<bool> f) { yield break; }
public IEnumerator CharacterLeftTile(MCECharacterController c, Action<bool> f) { yield break; }
}
}

Step 2: Attach to a Tile Prefab

  1. Create or edit the tile asset that will be quicksand.
  2. Ensure the tile's Game Object instantiation is enabled (RuleTile > Default Game Object or per-rule).
  3. Attach your QuicksandTile component to that prefab.

Step 3: Register in TileData

  1. Open the TileData asset in the Inspector.
  2. Use the Populate button to detect any new tile assets.
  3. Set the tile's TileType to Walkable (or add a custom enum value if you need special movement logic).
  4. Assign the appropriate BattleScenario for the visual theme.

Step 4: Paint in the Tilemap

Use the standard Unity Tile Palette to paint your tile onto the appropriate tilemap layer. The GridController will automatically pick up the tile data and call your behaviour hooks during gameplay.