Ben Traje
← Back to unity

Godot Signals in Unity: UnityEvents vs. C# Actions Guide

09 Aug 26 (1mo ago)

The short answer: Unity’s direct equivalents to Godot Signals are UnityEvents (for Inspector-based drag-and-drop setups) and C# Actions (for performant, code-to-code communication).

1. Quick Comparison

FeatureGodot (GDScript)Unity (C# Action)UnityEvent
Declarationsignal door_openedpublic event Action OnDoor;public UnityEvent OnDoor;
Emissiondoor_opened.emit()OnDoor?.Invoke();OnDoor.Invoke();
Connection.connect(_on_door)+= MyFunction;Inspector Drag & Drop
DisconnectionAuto-cleaned on free-= MyFunction;N/A (Handled in UI)
Best ForEverythingHigh-performance gameplay logicUI and Level Designers

2. The Critical "Gotcha": Memory Leaks

In Godot, if a node listening to a signal is freed (queue_free), the engine automatically severs the connection. Unity does not do this.

If you subscribe to a C# event (+=) and the listening object is destroyed, the active publisher still holds a reference to that destroyed object. You must explicitly unsubscribe (-=)—typically in Unity's OnDisable() or OnDestroy() methods—to prevent dangling references, unintended callbacks, and memory leaks.

3. Global Architecture: ScriptableObjects vs. AutoLoads

While Godot relies heavily on AutoLoads (Singletons) for global signal routing, modern Unity architecture favors ScriptableObject Event Channels. This allows completely decoupled global communication (e.g., a "Game Channel") without establishing direct script-to-script dependencies.

4. Code Example: C# Action Pattern

Here is the cleanest Unity C# pattern for a standard Godot-style signal setup.

using System;
using UnityEngine;

// THE PUBLISHER (Emits the signal)
public class Player : MonoBehaviour
{
    // Declare the event
    public static event Action OnPlayerDeath;

    void Die()
    {
        // Emit safely using the null-conditional operator
        OnPlayerDeath?.Invoke();
    }
}

// THE SUBSCRIBER (Listens to the signal)
public class UIManager : MonoBehaviour
{
    // Connect when enabled
    void OnEnable() 
    {
        Player.OnPlayerDeath += ShowGameOver;
    }

    // DISCONNECT when disabled (CRITICAL to prevent leaks)
    void OnDisable() 
    {
        Player.OnPlayerDeath -= ShowGameOver;
    }

    void ShowGameOver() 
    {
        Debug.Log("Game Over Screen Shown!");
    }
}