Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Global Observables

A Unity package for managing global observables across scenes and components. Share data and state throughout your Unity project without creating tight coupling between systems.

πŸ“‹ Table of Contents

✨ Features

  • Cross-Scene Data Sharing: Share state between different scenes without complex serialization
  • Type-Safe API: Generic-based design ensures compile-time type safety
  • Decoupled Architecture: Components communicate without direct references
  • Observable Pattern: Subscribe to changes and react automatically
  • Zero Boilerplate: Simple, clean API for quick setup
  • Memory Efficient: Automatic cleanup and Unity domain reload support
  • Well Tested: Comprehensive test suite with 50+ unit tests

πŸ“¦ Installation

Via Unity Package Manager

  1. Open Window > Package Manager
  2. Click the + button and select Add package from git URL
  3. Enter your repository URL

Via manifest.json

Add this to your Packages/manifest.json:

{
  "dependencies": {
    "com.biswajit.globalobservables": "1.0.0"
  }
}

🧠 How It Works

The Global Observables package follows a two-step pattern:

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 1: Define Keys (What data/events exist globally)     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Your Custom Class : GlobalVariablesRegistryKeys     β”‚   β”‚
β”‚  β”‚  Your Custom Class : GlobalEventsRegistryKeys        β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                           ↓                                  β”‚
β”‚  Step 2: Use Handlers (Access and manipulate the data)      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  GlobalVariablesHandler.SetValue()                   β”‚   β”‚
β”‚  β”‚  GlobalEventsHandler.Invoke()                        β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Concepts

  1. Abstract Registry Classes (provided by package):

    • GlobalVariablesRegistryKeys - base class for variable keys
    • GlobalEventsRegistryKeys - base class for event keys
  2. Your Custom Registry Classes (you create):

    • Inherit from abstract classes
    • Define static key fields for your specific needs
  3. Global Handlers (provided by package):

    • GlobalVariablesHandler - manage variables
    • GlobalEventsHandler - manage events

πŸš€ Getting Started

Step 1: Create Your Registry Keys Classes

IMPORTANT: You must inherit from the abstract base classes to define your keys. These act as a central registry of all global data in your project.

Create two classes in your project (recommended location: Scripts/GlobalObservables/ or similar):

1. Define Your Variable Keys

using GlobalObservables.Abstract;
using GlobalObservables.Core.Registries;

namespace YourGame
{
    /// <summary>
    /// Central registry for all global variable keys in the game
    /// </summary>
    public class MyGlobalVariables : GlobalVariablesRegistryKeys
    {
        //===================================================================
        // Player Stats
        //===================================================================
        public static RegistryVariableKey<int> PlayerHealth = new RegistryVariableKey<int>();
        public static RegistryVariableKey<int> PlayerMaxHealth = new RegistryVariableKey<int>();
        public static RegistryVariableKey<int> PlayerScore = new RegistryVariableKey<int>();
        public static RegistryVariableKey<string> PlayerName = new RegistryVariableKey<string>();
        
        //===================================================================
        // Game State
        //===================================================================
        public static RegistryVariableKey<bool> IsGamePaused = new RegistryVariableKey<bool>();
        public static RegistryVariableKey<float> GameTimeElapsed = new RegistryVariableKey<float>();
        public static RegistryVariableKey<int> CurrentLevel = new RegistryVariableKey<int>();
        
        //===================================================================
        // Custom Types
        //===================================================================
        public static RegistryVariableKey<PlayerData> CurrentPlayerData = new RegistryVariableKey<PlayerData>();
    }
    
    // Your custom data class
    public class PlayerData
    {
        public string Name;
        public int Level;
        public float Experience;
        public Vector3 Position;
    }
}

2. Define Your Event Keys

using GlobalObservables.Abstract;
using GlobalObservables.Core.Registries;
using UnityEngine;

namespace YourGame
{
    /// <summary>
    /// Central registry for all global event keys in the game
    /// </summary>
    public class MyGlobalEvents : GlobalEventsRegistryKeys
    {
        //===================================================================
        // Simple Events (no parameters)
        //===================================================================
        public static RegistryEventKey OnGameStarted = new RegistryEventKey();
        public static RegistryEventKey OnGameOver = new RegistryEventKey();
        public static RegistryEventKey OnGamePaused = new RegistryEventKey();
        public static RegistryEventKey OnGameResumed = new RegistryEventKey();
        public static RegistryEventKey OnLevelComplete = new RegistryEventKey();
        
        //===================================================================
        // Parameterized Events (with data)
        //===================================================================
        public static RegistryEventKey<int> OnScoreChanged = new RegistryEventKey<int>();
        public static RegistryEventKey<int> OnHealthChanged = new RegistryEventKey<int>();
        public static RegistryEventKey<string> OnPlayerDied = new RegistryEventKey<string>();
        public static RegistryEventKey<EnemySpawnData> OnEnemySpawned = new RegistryEventKey<EnemySpawnData>();
        public static RegistryEventKey<ItemData> OnItemCollected = new RegistryEventKey<ItemData>();
    }
    
    // Event data classes
    public class EnemySpawnData
    {
        public Vector3 Position;
        public int EnemyType;
        public int Health;
    }
    
    public class ItemData
    {
        public string ItemName;
        public int Quantity;
    }
}

Step 2: Use Global Handlers

Now use the Global Handlers to interact with your defined keys from anywhere in your code.

Quick Example

using GlobalObservables.Core.GlobalHandlers;
using UnityEngine;

public class GameController : MonoBehaviour
{
    void Start()
    {
        // Set initial values using your keys
        GlobalVariablesHandler.SetValue(MyGlobalVariables.PlayerHealth, 100);
        GlobalVariablesHandler.SetValue(MyGlobalVariables.PlayerScore, 0);
        
        // Invoke an event
        GlobalEventsHandler.Invoke(MyGlobalEvents.OnGameStarted);
    }
    
    public void PlayerTakeDamage(int damage)
    {
        // Get current health
        int health = GlobalVariablesHandler.GetValue(MyGlobalVariables.PlayerHealth);
        
        // Update health
        health -= damage;
        GlobalVariablesHandler.SetValue(MyGlobalVariables.PlayerHealth, health);
        
        // Invoke event with parameter
        GlobalEventsHandler.Invoke(MyGlobalEvents.OnHealthChanged, health);
        
        if (health <= 0)
        {
            GlobalEventsHandler.Invoke(MyGlobalEvents.OnGameOver);
        }
    }
}

🧩 Core Components

1. Global Variables

Global Variables are observable values that automatically notify subscribers when they change.

Setting and Getting Values

using GlobalObservables.Core.GlobalHandlers;

public class HealthManager : MonoBehaviour
{
    void Start()
    {
        // Set a value
        GlobalVariablesHandler.SetValue(MyGlobalVariables.PlayerHealth, 100);
        
        // Get a value
        int currentHealth = GlobalVariablesHandler.GetValue(MyGlobalVariables.PlayerHealth);
        Debug.Log($"Current Health: {currentHealth}");
    }
    
    public void Heal(int amount)
    {
        int health = GlobalVariablesHandler.GetValue(MyGlobalVariables.PlayerHealth);
        int maxHealth = GlobalVariablesHandler.GetValue(MyGlobalVariables.PlayerMaxHealth);
        
        health = Mathf.Min(health + amount, maxHealth);
        GlobalVariablesHandler.SetValue(MyGlobalVariables.PlayerHealth, health);
    }
}

Subscribing to Value Changes

The power of observables is that other components can react automatically when values change:

using GlobalObservables.Core.GlobalHandlers;
using UnityEngine;
using UnityEngine.UI;

public class HealthUI : MonoBehaviour
{
    [SerializeField] private Slider healthSlider;
    [SerializeField] private Text healthText;
    
    void OnEnable()
    {
        // Subscribe to health changes
        GlobalVariablesHandler.SubscribeOnChange(
            MyGlobalVariables.PlayerHealth, 
            OnHealthChanged
        );
        
        // Get initial value and update UI
        int currentHealth = GlobalVariablesHandler.GetValue(MyGlobalVariables.PlayerHealth);
        UpdateHealthUI(currentHealth);
    }
    
    void OnDisable()
    {
        // IMPORTANT: Always unsubscribe to prevent memory leaks
        GlobalVariablesHandler.UnsubscribeOnChange(
            MyGlobalVariables.PlayerHealth, 
            OnHealthChanged
        );
    }
    
    // This gets called automatically whenever health changes
    void OnHealthChanged(int newHealth)
    {
        UpdateHealthUI(newHealth);
    }
    
    void UpdateHealthUI(int health)
    {
        healthSlider.value = health / 100f;
        healthText.text = $"HP: {health}";
    }
}

Working with Custom Types

// First, define your custom data class
public class PlayerData
{
    public string Name;
    public int Level;
    public float Experience;
    public Vector3 LastCheckpoint;
}

// In your registry keys class
public static RegistryVariableKey<PlayerData> CurrentPlayerData = 
    new RegistryVariableKey<PlayerData>();

// Usage
public class PlayerManager : MonoBehaviour
{
    void Start()
    {
        // Create and set player data
        var playerData = new PlayerData 
        { 
            Name = "Hero", 
            Level = 5, 
            Experience = 1250.5f,
            LastCheckpoint = new Vector3(10, 0, 5)
        };
        
        GlobalVariablesHandler.SetValue(MyGlobalVariables.CurrentPlayerData, playerData);
    }
    
    void LevelUp()
    {
        // Get current data
        var data = GlobalVariablesHandler.GetValue(MyGlobalVariables.CurrentPlayerData);
        
        // Modify it
        data.Level++;
        data.Experience = 0;
        
        // Update the global variable (this triggers OnChange for subscribers)
        GlobalVariablesHandler.SetValue(MyGlobalVariables.CurrentPlayerData, data);
    }
}

// Another component reacting to player data changes
public class PlayerInfoUI : MonoBehaviour
{
    void OnEnable()
    {
        GlobalVariablesHandler.SubscribeOnChange(
            MyGlobalVariables.CurrentPlayerData, 
            OnPlayerDataChanged
        );
    }
    
    void OnDisable()
    {
        GlobalVariablesHandler.UnsubscribeOnChange(
            MyGlobalVariables.CurrentPlayerData, 
            OnPlayerDataChanged
        );
    }
    
    void OnPlayerDataChanged(PlayerData data)
    {
        Debug.Log($"Player: {data.Name}, Level: {data.Level}, XP: {data.Experience}");
        // Update your UI
    }
}

2. Global Events

Global Events enable decoupled communication between components without direct references.

Simple Events (No Parameters)

using GlobalObservables.Core.GlobalHandlers;
using UnityEngine;

// Component that triggers the event
public class GameController : MonoBehaviour
{
    public void EndGame()
    {
        // Invoke a simple event
        GlobalEventsHandler.Invoke(MyGlobalEvents.OnGameOver);
    }
    
    public void PauseGame()
    {
        GlobalEventsHandler.Invoke(MyGlobalEvents.OnGamePaused);
    }
}

// Component that listens to the event
public class GameOverUI : MonoBehaviour
{
    [SerializeField] private GameObject gameOverPanel;
    
    void OnEnable()
    {
        // Subscribe to the event
        GlobalEventsHandler.Subscribe(MyGlobalEvents.OnGameOver, OnGameOver);
    }
    
    void OnDisable()
    {
        // Unsubscribe
        GlobalEventsHandler.Unsubscribe(MyGlobalEvents.OnGameOver, OnGameOver);
    }
    
    void OnGameOver()
    {
        Debug.Log("Game Over!");
        gameOverPanel.SetActive(true);
        // Show game over UI, stop gameplay, etc.
    }
}

// Another component also listening (multiple subscribers allowed!)
public class AudioManager : MonoBehaviour
{
    void OnEnable()
    {
        GlobalEventsHandler.Subscribe(MyGlobalEvents.OnGameOver, OnGameOver);
        GlobalEventsHandler.Subscribe(MyGlobalEvents.OnGamePaused, OnGamePaused);
    }
    
    void OnDisable()
    {
        GlobalEventsHandler.Unsubscribe(MyGlobalEvents.OnGameOver, OnGameOver);
        GlobalEventsHandler.Unsubscribe(MyGlobalEvents.OnGamePaused, OnGamePaused);
    }
    
    void OnGameOver()
    {
        // Play game over music
    }
    
    void OnGamePaused()
    {
        // Pause background music
    }
}

Parameterized Events (With Data)

using GlobalObservables.Core.GlobalHandlers;
using UnityEngine;

// Component that triggers events with data
public class ScoreManager : MonoBehaviour
{
    private int currentScore = 0;
    
    public void AddScore(int points)
    {
        currentScore += points;
        
        // Invoke event with the new score
        GlobalEventsHandler.Invoke(MyGlobalEvents.OnScoreChanged, currentScore);
    }
}

// Component listening to score changes
public class ScoreUI : MonoBehaviour
{
    [SerializeField] private Text scoreText;
    
    void OnEnable()
    {
        // Subscribe to parameterized event
        GlobalEventsHandler.Subscribe(MyGlobalEvents.OnScoreChanged, OnScoreChanged);
    }
    
    void OnDisable()
    {
        GlobalEventsHandler.Unsubscribe(MyGlobalEvents.OnScoreChanged, OnScoreChanged);
    }
    
    // The callback receives the parameter
    void OnScoreChanged(int newScore)
    {
        scoreText.text = $"Score: {newScore}";
    }
}

Events with Custom Data Types

// Define your event data in the registry keys file
public class EnemySpawnData
{
    public Vector3 Position;
    public int EnemyType;
    public int Health;
}

// In your registry keys
public static RegistryEventKey<EnemySpawnData> OnEnemySpawned = 
    new RegistryEventKey<EnemySpawnData>();

// Spawner - invokes the event
public class EnemySpawner : MonoBehaviour
{
    public void SpawnEnemy(Vector3 position, int enemyType)
    {
        // Create the enemy spawn data
        var spawnData = new EnemySpawnData
        {
            Position = position,
            EnemyType = enemyType,
            Health = 100
        };
        
        // Actually spawn the enemy (your code here)
        GameObject enemy = Instantiate(enemyPrefab, position, Quaternion.identity);
        
        // Notify all listeners
        GlobalEventsHandler.Invoke(MyGlobalEvents.OnEnemySpawned, spawnData);
    }
}

// Minimap - listens and shows enemy on minimap
public class MinimapController : MonoBehaviour
{
    void OnEnable()
    {
        GlobalEventsHandler.Subscribe(MyGlobalEvents.OnEnemySpawned, OnEnemySpawned);
    }
    
    void OnDisable()
    {
        GlobalEventsHandler.Unsubscribe(MyGlobalEvents.OnEnemySpawned, OnEnemySpawned);
    }
    
    void OnEnemySpawned(EnemySpawnData data)
    {
        Debug.Log($"Enemy spawned at {data.Position}");
        // Add marker to minimap
    }
}

// Enemy counter - also listens
public class EnemyCounter : MonoBehaviour
{
    private int activeEnemies = 0;
    
    void OnEnable()
    {
        GlobalEventsHandler.Subscribe(MyGlobalEvents.OnEnemySpawned, OnEnemySpawned);
    }
    
    void OnDisable()
    {
        GlobalEventsHandler.Unsubscribe(MyGlobalEvents.OnEnemySpawned, OnEnemySpawned);
    }
    
    void OnEnemySpawned(EnemySpawnData data)
    {
        activeEnemies++;
        Debug.Log($"Active enemies: {activeEnemies}");
    }
}

πŸ’‘ Complete Examples

Example 1: Player Health System (Variables + Events)

// 1. Define your keys
public class GameKeys : GlobalVariablesRegistryKeys
{
    public static RegistryVariableKey<int> PlayerHealth = new RegistryVariableKey<int>();
    public static RegistryVariableKey<int> PlayerMaxHealth = new RegistryVariableKey<int>();
}

public class GameEvents : GlobalEventsRegistryKeys
{
    public static RegistryEventKey OnPlayerDied = new RegistryEventKey();
    public static RegistryEventKey<int> OnPlayerDamaged = new RegistryEventKey<int>();
}

// 2. Health Manager - manages health logic
public class HealthManager : MonoBehaviour
{
    void Start()
    {
        // Initialize health
        GlobalVariablesHandler.SetValue(GameKeys.PlayerMaxHealth, 100);
        GlobalVariablesHandler.SetValue(GameKeys.PlayerHealth, 100);
    }
    
    public void TakeDamage(int damage)
    {
        int health = GlobalVariablesHandler.GetValue(GameKeys.PlayerHealth);
        health = Mathf.Max(0, health - damage);
        GlobalVariablesHandler.SetValue(GameKeys.PlayerHealth, health);
        
        // Invoke damage event
        GlobalEventsHandler.Invoke(GameEvents.OnPlayerDamaged, damage);
        
        if (health == 0)
        {
            GlobalEventsHandler.Invoke(GameEvents.OnPlayerDied);
        }
    }
    
    public void Heal(int amount)
    {
        int health = GlobalVariablesHandler.GetValue(GameKeys.PlayerHealth);
        int maxHealth = GlobalVariablesHandler.GetValue(GameKeys.PlayerMaxHealth);
        
        health = Mathf.Min(health + amount, maxHealth);
        GlobalVariablesHandler.SetValue(GameKeys.PlayerHealth, health);
    }
}

// 3. Health UI - displays health
public class HealthUI : MonoBehaviour
{
    [SerializeField] private Slider healthSlider;
    [SerializeField] private Text healthText;
    
    void OnEnable()
    {
        GlobalVariablesHandler.SubscribeOnChange(GameKeys.PlayerHealth, UpdateHealthDisplay);
        UpdateHealthDisplay(GlobalVariablesHandler.GetValue(GameKeys.PlayerHealth));
    }
    
    void OnDisable()
    {
        GlobalVariablesHandler.UnsubscribeOnChange(GameKeys.PlayerHealth, UpdateHealthDisplay);
    }
    
    void UpdateHealthDisplay(int health)
    {
        int maxHealth = GlobalVariablesHandler.GetValue(GameKeys.PlayerMaxHealth);
        healthSlider.value = (float)health / maxHealth;
        healthText.text = $"{health}/{maxHealth}";
    }
}

// 4. Damage Indicator - shows damage numbers
public class DamageIndicator : MonoBehaviour
{
    void OnEnable()
    {
        GlobalEventsHandler.Subscribe(GameEvents.OnPlayerDamaged, OnPlayerDamaged);
    }
    
    void OnDisable()
    {
        GlobalEventsHandler.Unsubscribe(GameEvents.OnPlayerDamaged, OnPlayerDamaged);
    }
    
    void OnPlayerDamaged(int damage)
    {
        // Show floating damage text
        Debug.Log($"-{damage} HP");
    }
}

// 5. Game Over Manager
public class GameOverManager : MonoBehaviour
{
    [SerializeField] private GameObject gameOverPanel;
    
    void OnEnable()
    {
        GlobalEventsHandler.Subscribe(GameEvents.OnPlayerDied, OnPlayerDied);
    }
    
    void OnDisable()
    {
        GlobalEventsHandler.Unsubscribe(GameEvents.OnPlayerDied, OnPlayerDied);
    }
    
    void OnPlayerDied()
    {
        gameOverPanel.SetActive(true);
        Time.timeScale = 0f;
    }
}

Example 2: Cross-Scene Data Persistence

// Scene A: Main Menu
public class MainMenuController : MonoBehaviour
{
    [SerializeField] private InputField playerNameInput;
    
    public void OnStartGameClicked()
    {
        // Save data to global variables before scene change
        GlobalVariablesHandler.SetValue(GameKeys.PlayerName, playerNameInput.text);
        GlobalVariablesHandler.SetValue(GameKeys.PlayerLevel, 1);
        GlobalVariablesHandler.SetValue(GameKeys.PlayerScore, 0);
        
        // Load next scene
        SceneManager.LoadScene("GameScene");
    }
}

// Scene B: Game Scene
public class GameSceneInitializer : MonoBehaviour
{
    [SerializeField] private Text welcomeText;
    
    void Start()
    {
        // Retrieve data from previous scene
        string playerName = GlobalVariablesHandler.GetValue(GameKeys.PlayerName);
        int playerLevel = GlobalVariablesHandler.GetValue(GameKeys.PlayerLevel);
        
        welcomeText.text = $"Welcome back, {playerName}! (Level {playerLevel})";
        
        // Continue using the data...
    }
}

🎯 Best Practices

1. Always Unsubscribe

CRITICAL: Always unsubscribe in OnDisable() or OnDestroy() to prevent memory leaks.

// βœ… GOOD
void OnEnable()
{
    GlobalEventsHandler.Subscribe(MyGlobalEvents.OnGameOver, OnGameOver);
}

void OnDisable()
{
    GlobalEventsHandler.Unsubscribe(MyGlobalEvents.OnGameOver, OnGameOver);
}

// ❌ BAD - Memory leak! Object won't be garbage collected
void OnEnable()
{
    GlobalEventsHandler.Subscribe(MyGlobalEvents.OnGameOver, OnGameOver);
}
// Missing OnDisable!

2. Use Clear Naming Conventions

// βœ… GOOD - Clear and organized
public static RegistryVariableKey<int> Player_Health = new RegistryVariableKey<int>();
public static RegistryEventKey MainMenu_StartButton_Clicked = new RegistryEventKey();

// ❌ AVOID - Unclear naming
public static RegistryVariableKey<int> h = new RegistryVariableKey<int>();
public static RegistryEventKey event1 = new RegistryEventKey();

3. Organize Keys by Feature/System

public class MyGlobalVariables : GlobalVariablesRegistryKeys
{
    //===================================================================
    // Player System
    //===================================================================
    public static RegistryVariableKey<int> PlayerHealth = new RegistryVariableKey<int>();
    public static RegistryVariableKey<string> PlayerName = new RegistryVariableKey<string>();
    
    //===================================================================
    // UI System
    //===================================================================
    public static RegistryVariableKey<bool> IsMenuOpen = new RegistryVariableKey<bool>();
    public static RegistryVariableKey<bool> IsInventoryOpen = new RegistryVariableKey<bool>();
    
    //===================================================================
    // Audio System
    //===================================================================
    public static RegistryVariableKey<float> MasterVolume = new RegistryVariableKey<float>();
    public static RegistryVariableKey<float> MusicVolume = new RegistryVariableKey<float>();
}

4. Initialize Values Early

public class GameInitializer : MonoBehaviour
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Initialize()
    {
        // Set all default values at game start
        GlobalVariablesHandler.SetValue(MyGlobalVariables.PlayerHealth, 100);
        GlobalVariablesHandler.SetValue(MyGlobalVariables.PlayerScore, 0);
        GlobalVariablesHandler.SetValue(MyGlobalVariables.IsGamePaused, false);
    }
}

5. Choose the Right Tool

  • Variables: For state that needs to be read frequently (health, score, settings)
  • Events: For one-time notifications (button clicked, enemy died, level completed)

πŸ“š API Reference

GlobalVariablesHandler

// Subscribe to value changes
void SubscribeOnChange<T>(RegistryVariableKey<T> key, Action<T> callback)

// Unsubscribe from value changes
void UnsubscribeOnChange<T>(RegistryVariableKey<T> key, Action<T> callback)

// Get current value
T GetValue<T>(RegistryVariableKey<T> key)

// Set new value (triggers OnChange for subscribers)
void SetValue<T>(RegistryVariableKey<T> key, T value)

GlobalEventsHandler

// Simple Events (no parameters)
void Subscribe(RegistryEventKey key, Action callback)
void Unsubscribe(RegistryEventKey key, Action callback)
void Invoke(RegistryEventKey key)

// Parameterized Events
void Subscribe<T>(RegistryEventKey<T> key, Action<T> callback)
void Unsubscribe<T>(RegistryEventKey<T> key, Action<T> callback)
void Invoke<T>(RegistryEventKey<T> key, T value)

❓ FAQ

Q: Do I need to instantiate the registry key classes?
A: No! The registry key classes (MyGlobalVariables, MyGlobalEvents, etc.) should only contain static fields. Never create instances of them.

Q: Why inherit from abstract classes?
A: Inheriting from GlobalVariablesRegistryKeys and GlobalEventsRegistryKeys provides structure and follows the intended pattern. It also allows the package to potentially add shared functionality in the future.

Q: Can I use this across different scenes?
A: Yes! That's one of the main benefits. All global observables persist across scene loads automatically.

Q: What happens if I forget to unsubscribe?
A: Memory leak! The subscribed object won't be garbage collected even when destroyed. Always unsubscribe in OnDisable() or OnDestroy().

Q: Can multiple objects subscribe to the same observable?
A: Yes! Any number of objects can subscribe. All subscribers will be notified of changes.

Q: Are these thread-safe?
A: The package is designed for Unity's main thread. Don't use from background threads.

Q: How is this different from ScriptableObjects?
A: ScriptableObjects are asset-based and require serialization. Global Observables are runtime-only, require no assets, and provide automatic subscription/notification.

Q: Can I reset all values?
A: The registry automatically clears when entering play mode (if Domain Reload is enabled). For runtime resets, you'll need to manually reset values you care about.

Q: What's the performance impact?
A: Minimal. The registry uses GUID-based O(1) lookups. Subscriptions use C# events/delegates which are highly optimized.


πŸ“„ License

This package is licensed under a custom license. See LICENSE.txt for details.

🀝 Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

πŸ“§ Support


Version: 1.0.0
Unity: 2022.3+
Package: com.biswajit.globalobservables

About

A Unity package for managing global observables across scenes and components. Share data and state throughout your Unity project without creating tight coupling between systems.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages