MapLoaderFramework is a modular Unity framework for loading, managing, and integrating 2D map data (TMX/JSON). It supports dynamic map connections, warp events, Lua scripting, mod support, episodic/chapter-based loading, localization, and additive scene management — all without recompiling your game.
- TMX/JSON map loading — loads maps from
InternalMaps(bundled) andExternalMaps(runtime-writable, e.g. mods or DLC) - Map connections — edge-to-edge connections and warp/teleport events; configurable recursive depth
- Modding system — drop mod folders into
Mods/, each with amod_manifest.json; enable/disable mods at runtime without recompiling - Chapter / episode loading — group maps by
chapter_id; callLoadChapter(n)to load all maps for that episode - Additive scene loading — mark a map as
"is_additive": trueto load it alongside the current scene (e.g. a persistent interior) - Transition callback — assign any fade/cutscene implementation to
MapLoaderFramework.TransitionCallback; natively integrated with CutsceneManager viaMapLoaderBridge - Localization — embed
localized_namesarrays in map JSON for any number of languages; switch at runtime - Lua scripting — sandboxed MoonSharp Lua scripts in
Scripts/for event-driven map logic - Inspector integration — all registries, loaded maps, warp events, and Lua scripts are visible in the Unity Inspector
- StateManager integration —
Loadingstate is pushed at load start and popped on map loaded by StateManager'sMapLoaderBridge(consumed viaSTATEMANAGER_MLF) - LoadScreenManager integration —
MapLoaderLoadScreenBridgein LoadScreenManager auto-shows the load screen onOnChapterChangedand hides it onOnMapLoaded(activated viaLOADSCREENMANAGER_MLFin the LoadScreenManager module) - DOTween Pro integration — hooks
TransitionCallbackto wrap every map and chapter switch in a full-screenImage.DOFade(fade-to-black → load → fade-to-clear) (activated viaMAPLOADER_DOTWEEN) - Odin Inspector integration —
SerializedMonoBehaviourbase for full Inspector serialization of complex types; runtime-display fields marked[ReadOnly]in Play Mode (activated viaODIN_INSPECTOR)
-
Open
Packages/manifest.jsonin your project. -
Add to the
dependenciessection:"com.rolandkaechele.maploaderframework": "https://github.com/RolandKaechele/MapLoaderFramework.git"
-
Save — Unity fetches the package automatically and keeps it updatable.
git clone https://github.com/RolandKaechele/MapLoaderFramework.git Assets/MapLoaderFrameworkAssets › Import Package › Custom Package… — select the exported .unitypackage.
Copy the MapLoaderFramework/ folder into Assets/.
Run postinstall.js (Node.js required) to create the required folder structure under Assets/:
Assets/
InternalMaps/ ← bundled map JSON files
ExternalMaps/ ← runtime/modded map JSON files
Scripts/ ← Lua event scripts
Mods/ ← mod subfolders (each with mod_manifest.json)
Resources/
InternalMaps/ ← SuperTiled2Unity prefabs for internal maps
ExternalMaps/ ← SuperTiled2Unity prefabs for external maps
- Add an empty GameObject to your scene named
MapLoader. - Attach MapLoaderFramework — it auto-adds
MapLoaderManager,AutoMapLoader, and other helpers. - Set
defaultMapNameonAutoMapLoaderto the id of your starting map. - Place your map JSON files in
Assets/InternalMaps/and your SuperTiled2Unity prefabs inAssets/Resources/InternalMaps/. - Press Play.
{
"id": "town_square",
"name": "Town Square",
"layout": "LAYOUT_TOWN_SQUARE",
"music": "town_theme",
"map_type": "outdoor",
"show_map_name": true,
"chapter_id": 1,
"gameplay_type": "exploration",
"is_additive": false,
"map_variant": "present",
"localization_key": "map.town_square",
"localized_names": [
{ "lang": "en", "value": "Town Square" },
{ "lang": "de", "value": "Stadtplatz" }
],
"unlock_condition": "",
"mod_id": "",
"audio": {
"backgroundMusic": "town_theme.ogg",
"ambientSounds": ["birds.ogg", "wind.ogg"]
},
"connections": [
{ "mapId": "cave_entrance", "direction": "down" }
],
"warp_events": [
{ "id": "warp_to_inn", "src_x": 5, "src_y": 3, "dest_map": "inn_interior", "dest_x": 2, "dest_y": 1 }
]
}| Field | Type | Description |
|---|---|---|
id |
string | Unique map identifier |
layout |
string | SuperTiled2Unity prefab name (without extension) |
chapter_id |
int | Groups maps into chapters/episodes; 0 = always available |
gameplay_type |
string | "exploration", "combat", "stealth", "minigame", "cutscene", "space", "underwater", "racing" |
is_additive |
bool | Load alongside current scene instead of replacing it |
map_variant |
string | "present", "past", "future" — for timeline variants |
localization_key |
string | Key for external localization lookup |
localized_names |
array | Inline translated name entries { "lang", "value" } |
unlock_condition |
string | Flag/condition that must be true to access this map |
mod_id |
string | Set automatically by ModManager for mod-provided maps |
Place mod subfolders inside Assets/Mods/ (editor) or Application.persistentDataPath/Mods/ (build):
Mods/
my_extra_maps/
mod_manifest.json
maps/
extra_dungeon.json
scripts/
extra_events.lua
minigames/ ← NEW: MiniGameManager JSON definitions
bonus_race.json
dlcpacks/ ← NEW: DlcManager pack definitions
vip_pack.json
mod_manifest.json example:
{
"mod_id": "my_extra_maps",
"name": "Extra Dungeon Pack",
"author": "Community",
"version": "1.0.0",
"description": "Adds a bonus dungeon.",
"enabled": true,
"map_files": ["extra_dungeon.json"],
"script_files": ["extra_events.lua"],
"minigame_files": ["bonus_race.json"],
"dlc_pack_files": ["vip_pack.json"],
"min_game_version": "1.0.0",
"dependencies": []
}Enable/disable mods at runtime via MapLoaderManager:
mapLoaderManager.EnableMod("my_extra_maps");
mapLoaderManager.DisableMod("my_extra_maps");
// List all discovered mods
foreach (var mod in mapLoaderManager.GetDiscoveredMods())
Debug.Log($"{mod.name} — enabled: {mod.enabled}");Mod maps that share an id with a base-game map will overwrite it. Dependency load order is resolved automatically.
// Load all maps belonging to chapter 3
mapLoaderManager.LoadChapter(3);
// Query which maps belong to a chapter
var maps = mapLoaderManager.GetMapsForChapter(3);LoadChapter calls TransitionCallback(displayName, doLoad) before swapping maps if the callback is set; otherwise the load happens immediately.
Subscribe to chapter change events on MapLoaderManager (or directly on MapLoaderFramework):
// Preferred: subscribe on the public facade
mapLoaderManager.OnChapterChanged += (previous, current) =>
Debug.Log($"Chapter {previous} → {current}");
// Alternative: subscribe on the inner component
mapLoaderFramework.OnChapterChanged += (previous, current) =>
Debug.Log($"Chapter {previous} → {current}");MapLoaderFramework exposes a delegate that any external system can hook to drive the fade:
// Signature: Action<string displayName, Action doLoad>
mapLoaderFramework.TransitionCallback = (displayName, doLoad) =>
{
// fade out, show displayName, call doLoad(), fade in
myFader.FadeOutAndIn(onMiddle: doLoad);
};When TransitionCallback is not set the map swap happens immediately with no animation.
Install CutsceneManager, add the scripting define CUTSCENEMANAGER_MLF, and attach MapLoaderBridge to any GameObject. MapLoaderBridge.Awake() wires TransitionCallback automatically using FadeController.
Install AudioManager, add the scripting define AUDIOMANAGER_MLF, and attach MapLoaderAudioBridge to any GameObject. It subscribes to OnMapLoaded and automatically crossfades to each map's audio.backgroundMusic and audio.ambientSounds.
OnMapLoaded fires on every root map change — including LoadChapter(), direct LoadMap() calls, and warp-event navigation:
mapLoaderFramework.OnMapLoaded += mapData =>
Debug.Log($"Map loaded: {mapData.id}, BGM: {mapData.audio?.backgroundMusic}");mapLoaderFramework.OnChapterChanged += (previous, current) =>
Debug.Log($"Chapter {previous} → {current}");OnChapterChanged fires only when LoadChapter() is called.
mapLoaderFramework.OnMapLoaded += mapData =>
Debug.Log($"Map loaded: {mapData.id}");OnMapLoaded fires on every root map change — LoadChapter(), direct LoadMap() calls, and warp-event navigation. Prefer this event for anything that needs to react to any map transition (e.g. audio changes).
The currently loaded map id is also available as a property at any time:
string currentMap = mapLoaderManager.CurrentMapId; // null until first map loadsMapRegistryEntry.hasBeenVisited is set to true the first time a map loads. Read it directly from the registry, or use SaveManager.HasVisited(mapId) when the SAVEMANAGER_MLF bridge is active.
mapLoaderFramework.SubscribeToRawJson((mapId, rawJson) =>
Debug.Log($"Map {mapId} updated"));
mapLoaderFramework.UnsubscribeFromRawJson(callback);
string json = mapLoaderFramework.GetRawJson("town_square");Place .lua files in Assets/Scripts/ (editor) or persistentDataPath/Scripts/ (build). Scripts are loaded and executed in a sandboxed MoonSharp environment (no os, io, require, or debug globals).
Trigger a script from a GameObject:
- Attach
LuaScriptTrigger. - Set
scriptFileNameto your.luafilename. - Call
TriggerScript()from a UI button or event.
| Class | Key Members |
|---|---|
MapLoaderManager |
LoadMap(name), LoadChapter(id), GetMapsForChapter(id), GetAvailableMaps(), EnableMod(id), DisableMod(id), GetDiscoveredMods() |
MapLoaderManager (events) |
OnChapterChanged event Action<int,int>, OnMapLoaded event Action<MapData> — forwarded from MapLoaderFramework; subscribe here instead of reaching through to the inner component |
MapLoaderManager (properties) |
CurrentMapId — id of the most-recently loaded root map; TransitionCallback property — get/set proxy to MapLoaderFramework.TransitionCallback |
MapLoaderFramework |
LoadMapAndConnections(name), LoadChapter(id), GetMapsForChapter(id), PreloadAllMaps(), GetRawJson(id), SubscribeToRawJson(cb), OnChapterChanged, OnMapLoaded, CurrentMapId |
ModManager |
DiscoverMods(), EnableMod(id), DisableMod(id), GetEnabledModMapFiles(), GetEnabledModScriptFiles(), GetEnabledModMiniGameFiles(), GetEnabledModDlcPackFiles() |
MapLoaderFramework (TransitionCallback) |
public Action<string, Action> — assign to drive fade-out → load → fade-in from any external system |
AutoMapLoader |
Loads defaultMapName on Start |
MapLoadTrigger |
TriggerLoad() — call from UI or events |
MapDropdownLoader |
Populates a Dropdown with available maps |
LuaScriptTrigger |
TriggerScript() — executes a named Lua file |
MapLoaderFramework does not define its own scripting symbols. Other modules activate their bridge to this package using *_MLF defines on their own side:
| Define | Module | Effect |
|---|---|---|
SAVEMANAGER_MLF |
SaveManager | Auto-save on chapter change |
EVENTMANAGER_MLF |
EventManager | Fire map.loaded / chapter.changed events |
AUDIOMANAGER_MLF |
AudioManager | Auto-play map music/ambient |
LOCALIZATIONMANAGER_MLF |
LocalizationManager | Register inline map names |
DIALOGUEMANAGER_MLF |
DialogueManager | Auto-play map intro dialogue |
INVENTORYMANAGER_MLF |
InventoryManager | Auto-grant map items on first visit |
DLCMANAGER_MLF |
DlcManager | Gate restricted maps behind DLC |
CUTSCENEMANAGER_MLF |
CutsceneManager | Fade transitions on map load |
MINIGAMEMANAGER_MLF |
MiniGameManager | Abort active mini-game on map load |
GAMEMANAGER_MLF |
GameManager | Load chapters via MapLoader |
TITLESCREEN_MLF |
TitleScreenManager | Load gameplay scene via MapLoader |
BOOTMANAGER_MLF |
BootStartupManager | Load TitleScreen via MapLoader |
STATEMANAGER_MLF |
StateManager | Push/pop Loading state on map load |
ANIMATIONMANAGER_MLF |
AnimationManager | Stop active animation on chapter change |
UIMANAGER_MLF |
UiManager | Show/hide loading panel on chapter change |
INPUTMANAGER_MLF |
InputManager | Block/unblock input during chapter loads |
CAMERAMANAGER_MLF |
CameraManager | Reset camera to default on chapter change |
AIMANAGER_MLF |
AiManager | Deregister all agents and reset alert level on chapter change |
ENEMYMANAGER_MLF |
EnemyManager | Abort active wave and clear all live instances on chapter change |
SCENEMANAGER_MLF |
SceneManager | Auto-load mapData.sceneId scene on chapter change |
PHYSICSMANAGER_MLF |
PhysicsManager | Activate mapData.physicsProfileId physics profile on chapter change |
SPAWNMANAGER_MLF |
SpawnManager | DespawnAll on chapter change; auto-spawn definitions in mapData.autoSpawnIds |
Requires ODIN_INSPECTOR define and the Odin Inspector Asset Store package. MapLoaderManager inherits from SerializedMonoBehaviour for full Inspector serialization; runtime-display fields are marked [ReadOnly] in Play Mode.
https://github.com/RolandKaechele/MapLoaderFramework
MIT License
