Custom Tile Types
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:
TileTypeenum -- Defines movement semantics for theGridController.TileData/SingleTileData-- A ScriptableObject database that maps eachTileBaseasset to aTileTypeand aBattleScenario.- Tile behaviours -- MonoBehaviour components placed on tile GameObjects that implement
ICharacterInteractingTileorITileTypeOverrideto 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
- Add your value to the
TileTypeenum (e.g.,Quicksand). - Handle it in
MCECharacterController's movement logic (the switch/if blocks that branch on tile type). - Register tiles of that type in the
TileDataasset 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]):
| Button | Purpose |
|---|---|
| AddGroup | Batch-add a list of tiles with the same type and battle scenario |
| Populate | Scan the project for all TileBase assets and add any missing ones (marked NonExistent) |
| CheckInvalid | Log errors for tiles still marked NonExistent or missing a BattleScenario |
| RemoveColliders | Strip physics colliders from all RuleTile entries (MCE uses its own grid collision) |
| RemoveNulls | Clean 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 Value | Meaning |
|---|---|
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
- Right-click in the Project window and select 2D > Tiles > SiblingTile.
- Set up tiling rules in the Inspector exactly like a standard RuleTile.
- In the Siblings list, drag the tile assets this tile should connect with.
- Enable DefaultIncludesSibling if the default green arrow (neighbor = "This") should also match sibling tiles. This is the most common setup for terrain blending.
- 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:
| Class | Purpose |
|---|---|
TallGrass | Adjusts sprite sort order when a character walks through; plays VFX on entry; toggles character shadow |
ForceCharacterToKeepMoving | Forces continuous movement in a direction (conveyor belts, currents). Has HasBlockingInteraction() => true |
OneWayJumpableTile | Restricts a ledge jump to a single direction |
BlockMovementDirections | Blocks movement in specific directions from a tile |
LadderUp | Adjusts sprite sort order for vertical ladder traversal |
EncounterTile | Marks a tile as a wild encounter zone with a specific EncounterType |
TileTypeOverride | Overrides the TileType at runtime |
TileTypeOfTileBelow | Inherits the type from the tile directly below in sort order |
WaterfallEdge | Handles waterfall edge behavior |
WhirlwindInDirection | Pushes 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
- Create or edit the tile asset that will be quicksand.
- Ensure the tile's Game Object instantiation is enabled (RuleTile > Default Game Object or per-rule).
- Attach your
QuicksandTilecomponent to that prefab.
Step 3: Register in TileData
- Open the
TileDataasset in the Inspector. - Use the Populate button to detect any new tile assets.
- Set the tile's
TileTypetoWalkable(or add a custom enum value if you need special movement logic). - Assign the appropriate
BattleScenariofor 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.