Skip to main content

Architecture Overview

This guide is for Source and Studio edition users who have access to the full MCE C# source code.

Module architecture

The arrows only point one way on purpose. Core never depends on Online or on the importer, which is what lets the online layer be genuinely optional instead of optional-in-the-README.

This document covers the module hierarchy, assembly structure, dependency injection patterns, and key runtime systems that make up the Monster Capture Engine.

Module Hierarchy​

MCE is organized into modules that follow a layered architecture:

Assets/
OpenMon/
Core/
Runtime/ # Core engine (REQUIRED)
Initialization/ # Engine startup, first-time wizard
Monster/ # MonsterInstance, Roster, evolution, breeding
MonsterDatabase/ # ScriptableObject definitions (species, moves, items, abilities)
Battle/ # BattleManager, 15 modules, state machine, AI
Characters/ # MCECharacterController, PlayerCharacter, followers
Actors/ # Actor system, CommandGraph visual scripting
World/ # GridController, GlobalGridManager, tiles, encounters
Saves/ # SavegameManager, SavableObject, GameVariables
Player/ # GlobalGameData, player state
UI/ # All UI controllers and views
Quests/ # Quest system (objectives, rewards, tracking)
SDK/ # Public API interfaces
GameFlow/ # Game state management
Configuration/ # ScriptableObject configs
Rendering/ # URP shaders, time-of-day, weather
Audio/ # AudioSettingsManager, AudioConfiguration (the playback engine is in Foundation)
Localization/ # Multi-language support
Badges/ # Badge tracking
Input/ # Input abstraction
MonsterDex/ # Dex tracking (seen/caught)
Animation/ # Animation utilities
WorldGenerator/ # Procedural region generator
Licensing/ # Tier and license checks
Shaders/ # Shader source files
Editor/ # Editor-only tooling
MonsterCreator/ # Monster creation wizard
Tools/ # Database Browser, Type Chart Editor, validators
Followers/ # Follower spritesheet tools
Actors/ # CommandGraph editor
Hub/ # The OpenMon Hub window
MapEditor/ # Scene View map painting overlays
Callbacks/ # Editor callback utilities

Dependency Rules​

  1. Core Runtime has zero external dependencies on Online or Importer modules.
  2. Online features are opt-in via the MCE_ONLINE scripting define symbol.
  3. Editor code is strictly separated from Runtime via .asmdef boundaries.
  4. The SDK interfaces in Runtime/SDK/ are the public API surface for users of the compiled (DLL) editions.

Assembly Structure​

MCE uses Unity Assembly Definitions to enforce compilation boundaries:

AssemblyLocationPurposeReferences
OpenMon.Foundation.RuntimeFoundation/Runtime/Shared framework: DI, audio, localization, scene management, tweeningZenject and Unity packages
OpenMon.Foundation.EditorFoundation/Editor/Foundation editor toolingFoundation Runtime + UnityEditor
OpenMon.MCE.RuntimeCore/Runtime/Core game systemsFoundation libs only
OpenMon.MCE.EditorCore/Editor/Editor toolingRuntime + UnityEditor
OpenMon.Hub.EditorCore/Editor/Hub/The OpenMon Hub window (it calls importer and online tools by name, so it depends on neither)Foundation Runtime
MCE.Online.RuntimeOnline/Runtime/Nakama networkingRuntime + Nakama SDK
MCE.Online.EditorOnline/Editor/Online editor toolsOnline Runtime + UnityEditor
MCE.EssentialsImporter.RuntimeImporter/Runtime/PBS/rxdata parsers and the import validatorRuntime + KaitaiStruct
MCE.EssentialsImporter.EditorImporter/Editor/Import pipelineRuntime + KaitaiStruct
OpenMon.SetupSetup/Package dependency installer and quick setupnone (isolated on purpose)
MCE.Runtime.TestsCore/Tests/Runtime/Core play-mode testsRuntime + NUnit
MCE.Editor.TestsCore/Tests/Editor/Core edit-mode testsRuntime + Editor + NUnit

Why Assembly Definitions Matter​

  • Compile isolation: Changes to Editor code do not recompile Runtime.
  • Dependency enforcement: The compiler prevents Runtime from referencing Editor APIs.
  • Build stripping: Editor assemblies are excluded from player builds.
  • Test isolation: Test assemblies can reference internals without exposing them in production.

Dependency Injection (Zenject)​

MCE uses Zenject (Extenject) with the installer pattern for dependency injection.

Installer Pattern​

Each subsystem has its own installer that registers bindings. Most are ScriptableObject installers; the battle launcher's is typical:

[CreateAssetMenu(menuName = "MCE/Dependency Injection/Battle Launcher Installer",
fileName = "BattleLauncherInstaller")]
public class BattleLauncherInstaller : LazySingletonScriptableInstaller<BattleLauncher>
{
public override void InstallBindings()
{
Reference.Reset();

Container.QueueForInject(Reference);

Container.Bind<IBattleLauncher>().FromInstance(Reference).AsSingle().Lazy();
}
}

BattleLauncher is itself a ScriptableObject (an asset), and the installer asset points at it. The Dependency Injection page lists every installer.

Key Installers​

InstallerBinds
GameFlowInstallerCore game flow services
PlayerCharacterInstallerPlayerCharacter, character services
BattleLauncherInstallerIBattleLauncher, the battle entry point
GridInstallerThe scene's grid (a MonoInstaller)
SceneInfoInstallerSceneInfo, scene metadata
WorldDatabaseInstallerWorldDatabase, global map data
TileDataInstallerTile type data
GlobalGridManagerInstallerCross-map navigation
SavegameInstallerSavegameManager, serialization
EvolutionManagerInstallerEvolutionManager

Conventions​

  • Bindings are .AsSingle().Lazy() by convention (single instance, created on first use).
  • [Inject] is used on Construct() methods, not constructors.
  • DialogManager.Factory pattern is used for runtime UI instantiation.
  • Scene-scoped installers (for example GridInstaller, a MonoInstaller) handle per-scene services.
  • Project-scoped installers (ScriptableObject assets on the ProjectContext prefab) handle global services.

Injection Points​

public class SomeManager : MonoBehaviour
{
// Field injection (for MonoBehaviours).
// Note: of the SDK interfaces, only IMonsterCaptureEngine is bound —
// take Database/Battle/Player/SaveSystem from its properties.
[Inject] private IMonsterCaptureEngine engine;

private IBattleSystem battle;

private void Awake()
{
battle = engine.Battle;
}

// Method injection (preferred for explicit dependencies on engine-internal types)
[Inject]
public void Construct(MCESettings settings, MonsterDatabaseInstance database)
{
this.settings = settings;
this.database = database;
}
}

Key Runtime Systems​

MCEInitialization Flow​

MCEInitialization is the first thing that runs when the game starts:

1. Screen setup (resolution, orientation)
2. Load databases (monsters, moves, items, abilities)
3. Download localization data (if remote)
4. Run first-time wizard (if needed)
5. Initialize subsystems (save manager, audio, input)
6. Signal ready
7. Load main menu or continue saved game

This is a MonoBehaviour on a persistent GameObject that survives scene loads.

Monster System​

The monster data model:

MonsterEntry (ScriptableObject - species definition)
├── Base stats, types, abilities
├── List<DataByFormEntry> (private per-form overrides)
├── DataByFormEntry.Evolutions: List<EvolutionData> (evolution paths)
└── Move lists (level-up, TM, egg, tutor)

MonsterInstance (runtime - individual monster)
├── Species reference (MonsterEntry)
├── Level, XP
├── IVs[6], EVs[6]
├── Nature
├── Current HP, Status
├── MoveSlot[4] (with PP)
├── Friendship, OT data
└── Form index

Roster (party of up to 6 MonsterInstances)
MonsterStorage (PC boxes)

Battle System​

See Battle Internals for the deep dive.

World System​

GlobalGridManager (cross-map navigation)
└── GridController (per-scene grid)
├── TileData grid (collision, type per tile)
├── EncounterTile zones
└── SceneInfo (metadata)

MCECharacterController (movement)
├── PlayerCharacter (input + encounters)
└── ActorCharacter (NPC AI movement)

Save System​

SavegameManager
├── MCESavesSerializer (JSON read/write)
├── SavableObject[] (registered savables)
└── GameVariables (global state)

Code Style​

The codebase follows these conventions:

  • 4-space indentation, braces on new lines.
  • PascalCase for public types, methods, properties.
  • camelCase for private fields and locals.
  • [SerializeField] private over public fields.
  • Separate Runtime/ and Editor/ behind .asmdef boundaries.
  • Conventional Commits: feat:, fix:, docs:, refactor:.
Serialized Fields

Never rename serialized fields without a migration plan. Scenes and prefabs depend on serialized field names. Use [FormerlySerializedAs("oldName")] for renames.

Extending the Engine​

Adding a New System​

  1. Create your classes in Runtime/YourSystem/.
  2. Create an installer for dependency injection.
  3. Add the installer to the appropriate scene context or project context.
  4. If it needs save data, extend SavableObject.
  5. If it has editor tooling, create classes in Editor/YourSystem/.

Modifying an Existing System​

  1. Read the existing code and understand the module's dependencies.
  2. Check if the change can be done via configuration or extension rather than modification.
  3. If modifying, follow the existing patterns in the module.
  4. Update tests to cover the change.
  5. Document the change in commit messages.

Performance Considerations​

  • Database lookups through the SDK (GetMonster(dexNumber), GetMonster(name)) scan the list of species. Cache the entry if you call them often.
  • Battle modules are components of the battle GameObject, created with it once per battle, not per turn.
  • Large tilemaps cost more per step and per frame. Split very large maps into connected scenes (see Neighbours in Grid-Based Movement).
  • Save serialization is synchronous on the main thread. Keep save data sizes reasonable.
  • CommandGraph execution is coroutine-based. Complex graphs with many nodes are fine for event scripting but should not be used for per-frame logic.