Skip to content

Core.ObjectManager

Dennis Steffen edited this page Jan 4, 2026 · 1 revision

The ObjectManager is the backbone of your engine's architecture. It functions as a Service Locator and Dependency Injection container, allowing different parts of the game (Input, Sound, Physics, AI) to communicate without being tightly coupled.

Why this is the "Modular Core"

In a typical game engine, you might be tempted to make everything a "Singleton." However, Singletons are hard to reset and difficult to test. The ObjectManager solves this by:

  1. Decoupling: A system (like a UI menu) doesn't need to know where the ArcadeGame data comes from; it just asks the manager for it.
  2. Scope Management: By using different instances of the manager, you can isolate data.
  3. Automatic Cleanup: The Dispose method ensures that when a manager is destroyed, all registered textures, sounds, and services are cleaned up correctly.

Global vs. Scene-Specific Management

Your engine employs a hierarchical approach to data:

  • GlobalObjectManager (Static): Used for "Evergreen" services that must exist from the moment the app starts until it closes. Examples include the LoggingService, MQTTModule, and AssetManager.
  • Scene Object Managers: Each Raylib scene typically maintains its own instance. When you transition from an IntroScene to a LevelScene, the IntroScene's manager is disposed of, clearing its specific UI buttons and local timers, while the GlobalObjectManager keeps the background music and input settings alive.

Detailed Function Breakdown

1. Registration (Register and RegisterOnce)

  • Register<T>(instance, tag): Maps a type and a unique string (tag) to an object. If you register a new object with the same type and tag, it overwrites the old one.
  • RegisterOnce<T>(instance, tag): Only registers the object if the key doesn't exist yet. This is perfect for "lazy loading" or ensuring you don't accidentally overwrite a core system like the Logger.

2. Collection Handling (RegisterList, RegisterSet, Add, Remove)

Instead of just single objects, the manager can handle groups of objects.

  • RegisterList<T> / RegisterSet<T>: Pre-allocates a container in the registry.
  • Add<T>(instance, tag): Finds a registered List or HashSet of that type and adds the instance to it. This is how you manage "Groups" (e.g., a list of all active enemies in a scene).

3. Retrieval (Get, GetList, GetSet)

  • These are the "Search" functions. They use the (Type, Tag) tuple to perform a lightning-fast dictionary lookup. Because they use generics (<T>), they return correctly typed objects, eliminating the need for manual casting ((MyClass)obj).

4. The Dispose Logic

This is the most complex part of the code. When Dispose() is called:

  • It iterates through every object in the registry.
  • If an object implements IDisposable, it calls .Dispose() on it.
  • It handles Collections: If you have a List<Texture>, it will iterate through the list and dispose of every texture inside.
  • It uses a ReferenceEqualityComparer to ensure that if the same object is registered under two different tags, it is only disposed once to prevent crashes.

Examples of Usage

Example 1: Global Service Registration

In your Program.cs, you register the global game configuration so any scene can access it:

// Registering the global game data
GlobalObjectManager.ObjectManager.Register(new ArcadeGame { Name = "CyberMaze" });

// Somewhere else in the code (e.g., a UI component)
var gameName = GlobalObjectManager.ObjectManager.Get<ArcadeGame>()?.Name;

Example 2: Scene-Specific Entities

Imagine a game scene where you want to track multiple enemies:

Notice This is a bare bone example. For RayLib scenes with a BaseScene it will be done for you. For more detail check the documentation about Scenes / GameObjects in RayLib

public class BattleScene {
    private ObjectManager _sceneManager = new ObjectManager();

    public void Initialize() {
        // Prepare a list for enemies
        _sceneManager.RegisterList<Enemy>();

        // Add enemies to the scene's list
        _sceneManager.Add(new Enemy("Goblin"));
        _sceneManager.Add(new Enemy("Orc"));
    }

    public void Update() {
        // Retrieve and update all enemies
        var enemies = _sceneManager.GetList<Enemy>();
        enemies?.ForEach(e => e.Update());
    }
    
    public void Close() {
        // This clears all enemies and disposes their textures automatically
        _sceneManager.Dispose();
    }
}

Example 3: Using Tags for Multiple Instances

If you have two different fonts (one for UI, one for World Space), you use tags to distinguish them:

// Registration
_manager.Register(fontMain, "UI_Font");
_manager.Register(fontSmall, "Tooltip_Font");

// Retrieval
var uiFont = _manager.Get<Font>("UI_Font");

Clone this wiki locally