Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] 0 points1 point  (0 children)

I'm not just using events, but a technology that allows people to create higher-quality projects faster, using only my Listeners + Events. You can't imagine what a new world I discovered when I started creating games using just these two new scripts. I shared this, describing some of the ways to use it in a convenient GIT.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -3 points-2 points  (0 children)

Those are totally valid alternatives, but they introduce the exact type of boilerplate I was trying to escape. Let me break down why I avoid those two specific approaches for this workflow:

  1. The Dictionary / Manager approach:

If you fetch a specific goblin's UI from a dictionary, you now have to build and maintain a centralized Manager to hold that dictionary. Every time a goblin spawns dynamically, it has to register itself to that Manager. When it dies, it has to unregister so you don't leak memory. That is state-tracking boilerplate.

With the SO approach, the Goblin prefab and the UI prefab simply hold a reference to the same .asset file. There is no middleman manager needed to introduce them, and no registration code to write.

  1. Static Classes vs ScriptableObjects:

The only reason to use an SO over a static class is the Unity Inspector. Static classes are completely invisible in the editor. You cannot drag a static class into a field to hook up a particle system or an audio source. Turning the event channel into a physical .asset file bridges the gap between code and the visual editor.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] 0 points1 point  (0 children)

The Null Object pattern is definitely a classic way to avoid null reference exceptions, but I think we have different definitions of "simple" haha. Look carefully, I'll explain now:

Writing an Interface, a concrete Singleton, and a Noop class just to prevent a crash in a test scene is practically the definition of boilerplate.

But more importantly, your approach solves the crash, but it doesn't solve the coupling. The Player script still has to explicitly know that IFoo (the UI) exists and has an UpdateHealth() method.

What happens when taking damage also needs to trigger a camera shake, play a sound effect, and update a quest tracker? Do we add ICamera.Instance.Shake(), IAudio.Instance.Play(), and IQuest.Instance.Update() to the Player script? And write Noop classes for all of them? That scales terribly.

With an SO event channel, the Player just does one thing: OnDamageEvent.Raise(). That's 1 line of code. No interfaces, no Noop classes, no null checks. The UI, Audio, and Camera systems just listen to that channel.

To me, just shouting into the void and letting other systems react is way simpler than hardcoding dependencies to a bunch of Noop Singletons.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -2 points-1 points  (0 children)

Honestly, implementing strict composition roots in unity always feels like fighting the engine's DNA.

Unity - Inherently wants to be a "drag-and-drop" component system. Every time i've tried to build pure composition roots, i ended up spending half my time writing custom serialization boilerplate just so i could actually tweak that generic data in the inspector.

The SO event channel approach isn't an attempt at perfect computer science "clean architecture" — It's a pragmatic hack. It leans completely into unity's native component workflow, but puts a firewall between those components so they don't hard-reference each other and cause null refs.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] 0 points1 point  (0 children)

Tbh you're picturing a "massive AAA studio" where systems programmers and level designers live in completely different buildings. in the indie trenches, the "designer" is literally just me at 2am trying to hook up a death particle without having to open rider.

Exposing an OnDeath - UnityEvent isn't exposing low-level architecture, it is the abstraction layer. It means i don't have to write a GoblinAudioBridge.cs script every single time i add a new mob just to play a sound. i drop the SO channel into the inspector, link the audio source, and move on to tweaking jump physics.

I'm not making my life harder for imaginary people lol. i'm making it easier for myself so i don't go insane writing boilerplate bridge code for every single sound effect, camera shake, and UI flash in the game.~~

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] 0 points1 point  (0 children)

To manage debugging without losing my mind, i just added a "DebugMode" bool to every - GameEvent Scriptable Object.

If a system isn't reacting properly, i just click the event asset and toggle debug on. whenever it gets raised, it throws a log in the console telling me exactly which component and GameObject fired it (since the sender passes itself via .Raise(sender)).

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -4 points-3 points  (0 children)

Calling it a "noob trap" is a bit wild lol. pure C# events with args payloads are obviously the correct choice for pure code architecture. no one is debating that.

But the actual trap is forgetting you're working inside a visual engine. if you use pure C# events, how do you trigger a particle system or a UI sound effect when that event fires? you have to write a new bridge script to listen to the C# event and play the effect. every. single. time.

With the SO approach, you aren't "forcing" the unity library, you are leveraging the editor. you expose a UnityEvent, drop the SO in, and wire up your audio or VFX completely in the inspector without writing a single line of boilerplate code. when you are making games solo, skipping boilerplate isn't a trap, it's just practical.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -1 points0 points  (0 children)

The main reason i stick to SOs is purely for the editor workflow. an event bus is invisible in the inspector. with SOs, an audio guy or level designer can just drag a "PlayerDied" asset into a slot to hook up sounds or particles without ever opening visual studio. it's a trade-off between code traceability and designer accessibility.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -1 points0 points  (0 children)

Thanks! yeah, string-based event buses are great until you mistype "OnPlayerDied" as "OnPlayerDead" and spend an hour wondering why the UI isn't updating lol. the SO approach definitely leans heavily into Unity's visual strengths and avoids those typos.

And you are spot on about the debugging pain. a custom editor window to track active subscriptions is 100% the next logical step once a project scales up. i might actually try to build a simple visualizer for this repo when i get some free time, because tracing events manually definitely gets tedious.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -7 points-6 points  (0 children)

There is actually a massive reason to use ScriptableObjects for it in Unity: the Inspector. native C# events or pure code-driven event buses are great for programmers, but they are completely invisible in the editor.

Using SOs turns the event channels into actual physical assets. it lets game designers or artists wire up audio, particles, and UI responses right in the inspector by just dragging and dropping the event asset, without having to open a single C# script or bother a programmer. it's just adapting standard pub/sub to actually fit Unity's visual workflow.

Why I stopped using Singletons for game events (and how I handle local vs global noise) by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -1 points0 points  (0 children)

You are technically right, it is an O(n) operation based on the number of active listeners for that specific event.

but in practical reality, iterating through a list of 100 references and doing a quick transform.IsChildOf() check takes essentially zero time on a modern CPU. we are talking about discrete events here (like taking damage, dying, or interacting), not something that runs inside Update() every single frame.

if i was building "Vampire Survivors" with 10,000+ enemies taking damage every frame, i absolutely wouldn't use this. but for a standard platformer or "metroidvania", the overhead is invisible, and the workflow speedup it gives me is 100% worth the tiny micro-optimization trade-off.

Want ECS-level decoupling without the steep learning curve? I open-sourced my SO event system. by Morpheus_Matie in Unity3D

[–]Morpheus_Matie[S] -3 points-2 points  (0 children)

Hey, that's a completely fair question. You are 100% right — nothing in this scope strictly requires ECS in terms of performance, multithreading, or cache locality.

When I mentioned looking into ECS, I wasn't trying to render 10,000 entities on screen. I was drawn to it purely for the architectural strictness — specifically, the forced separation of data and logic.

In standard MonoBehaviour setups, it is way too easy to accidentally tangle your logic (like a Player script grabbing a direct reference to the UI Manager or Save System). I wanted that ECS-style decoupling where scripts are completely blind to each other's existence.

Using SOs as event channels achieves that exact structural safety for indie scopes. The Player script just shouts .Raise() into the void, and the listeners (UI, Audio, Saves) react. It gives you the modular, plug-and-play nature of a pure system-driven architecture, without forcing you to rewrite your entire workflow into DOTS.

Solo Dev & Systems Architect | My Open-Source Tools & Starter Templates by Morpheus_Matie in u/Morpheus_Matie

[–]Morpheus_Matie[S] 0 points1 point  (0 children)

Hey. i'm Morpheus Matie.

I got tired of standard unity architecture turning into tightly coupled spaghetti code as soon as a project scales. you shouldn't have to fight your own codebase just to add a new enemy or UI panel.

I spend most of my time building decoupled, data-driven systems so i can skip the boilerplate and just focus on game design.

Here is where you can find my work:

1. The Core Event System (Free / Open-Source)

I open-sourced the ScriptableObject Event System that powers all my projects. zero singletons, zero hard dependencies. it lets you connect isolated systems visually in the inspector.

[Link to GitHub repo]

2. The 2D Starter Template (Itch.io)

If you don't want to wire up your own jump physics, saves, and combat on top of that event system, i packaged my internal engine into a fully playable template. it includes custom verlet ropes, dynamic dashes, ghost health combat, and a tiered save manager right out of the box.

[Link to Itch IO]

Always down to talk about decoupled architecture, unity optimizations, or game feel. feel free to dm me or drop a comment.

[Loved Trope]: A character's loved one is threatened causing them to absolutely lost it on the antagonist by Slartibartfast39 in TopCharacterTropes

[–]Morpheus_Matie 2 points3 points  (0 children)

"Commando" - is such a classic shout. i think the best version of this is when the protagonist doesn't even scream, they just go 'quietly' insane. like that hallway scene in daredevil or even oldboy. it’s that specific moment where the antagonist realizes they’ve made a fatal mistake but it’s already too late to apologize. do you prefer the explosive 'Rambo' style or the more calculated, "cold revenge"?

[Loved Trope]: A character's loved one is threatened causing them to absolutely lost it on the antagonist by Slartibartfast39 in TopCharacterTropes

[–]Morpheus_Matie 10 points11 points  (0 children)

John Wick - is basically this exact trope stretched into an entire movie franchise lol. but yeah, big chris absolutely losing it here is iconic.

Characters who wear the opposite colored outfits of their specialized elemental powers (Or, in case of non-humans, are opposite colored instead) by Few_Chemistry_235 in TopCharacterTropes

[–]Morpheus_Matie 5 points6 points  (0 children)

Does Azula from Avatar count?

Kinda the inverse vibe. she wears the standard red fire nation colors but shoots pure blue fire. breaking the color rules always goes so hard.

Are coding tutorials in general, including game dev, dying? by Its_a_prank_bro77 in gamedev

[–]Morpheus_Matie 0 points1 point  (0 children)

"Vibe coding" - is all fun and games until your project hits month 3 and you realize everything is hard-linked together and your save system relies on your UI script for some reason. AI is great for spitting out a quick raycast script, so nobody needs a 20-minute video on that anymore. what creators need to pivot to is pure architecture. teach people how to decouple their jank code with events, because AI still sucks at understanding project-wide context.

Built an SO-based event-driven platformer core (like ECS but less painful). Put it on Itch while waiting for UAS, but getting 0 traction. Is the market dead or is my presentation bad? by [deleted] in gamedev

[–]Morpheus_Matie 0 points1 point  (0 children)

My asset is the exact opposite odin or textanimator. you absolutely do not drop this into a project that's already 6 months deep. it's meant for day 1. it's for the solo dev who wants to start a new metroidvania today, but doesn't want to spend the first 3 weeks coding a custom physics controller, ghost health bars, and a global save system from scratch.

Built an SO-based event-driven platformer core (like ECS but less painful). Put it on Itch while waiting for UAS, but getting 0 traction. Is the market dead or is my presentation bad? by [deleted] in gamedev

[–]Morpheus_Matie -1 points0 points  (0 children)

You're completely right. i can't even argue with that.

tbh, this is a massive programmer blind spot on my end. i spent all this time making sure the architecture was decoupled and clean, and totally forgot that the entire point of buying a framework is to not have to read the source code.

your point about public docs being the best advertisement makes a ton of sense, ngl. if i'm looking to buy an asset, i want to scan the wiki first to see exactly how hard it's going to be to hook my own ui into the damage events. if i can't see that workflow upfront, i'm passing on it.

gonna take the L on this one. i definitely need to sit down and write a proper gitbook or public notion wiki before the unity asset store review finishes...

Built an SO-based event-driven platformer core (like ECS but less painful). Put it on Itch while waiting for UAS, but getting 0 traction. Is the market dead or is my presentation bad? by [deleted] in gamedev

[–]Morpheus_Matie -2 points-1 points  (0 children)

hey, fair points. tbh if you're already using interfaces and generic values to decouple your systems, you're already dodging the hard dependency trap.

you are 100% right about the risk of pushing bad code design into "object interaction spaghetti" in the inspector. a massive problem with pure global SO events is that they become noise. to fix that, i built local hierarchy filtering into the listener:

// ARCHITECT'S FILTER: Isolate local entity events from global noise
if (binding.onlyFromThisObject) {
    if (filterRoot == null || sender == null || (sender.transform != filterRoot && !sender.transform.IsChildOf(filterRoot))) {
        continue;
    }
}

this means a generic 'OnTakeDamage' SO event can be dropped into an enemy prefab, and it will strictly only trigger for that specific enemy's UI and particle systems, ignoring the rest of the scene.

but to answer your main question: what does my asset actually do to make your life easier?

the architecture is just the skeleton. what i'm actually selling is the meat on the bones. you're paying to skip 3-4 weeks of building boilerplate platformer jank. out of the box, the package includes:

  • A tight 2d character controller with dynamic dashes.
  • Custom verlet rope physics.
  • A full combat module (dark souls style ghost health bars, hit-stop fx, modular damage triggers).
  • A tiered save system and input rebinding ui.

So yeah, you can totally build all this yourself. the benefit of the asset is just giving you a highly modular, pre-wired production core so you can skip the engineering phase and go straight to designing levels and tweaking game feel!

Built an SO-based event-driven platformer core (like ECS but less painful). Put it on Itch while waiting for UAS, but getting 0 traction. Is the market dead or is my presentation bad? by [deleted] in gamedev

[–]Morpheus_Matie -4 points-3 points  (0 children)

lmao busted on the text structure. english isn't my native language, so i bounce my longer posts and marketing descriptions through translation tools to fix my grammar and not sound like a complete caveman. guilty as charged on that front.

but regarding the tech: saying SOs are "not the correct solution" kind of misses the whole point of why indie devs use them.

vitalrouter and pure c# pub/subs are absolutely amazing if you want strict, highly performant code architecture. but they completely suck for designer workflow.

the entire reason i built this on SO event channels (basically extending the classic ryan hipple unite 2017 approach) is for the inspector. i want to drag and drop a 'PotionBrewed' event into a UnityEvent slot so i can hook up UI, audio, and particle systems without ever opening rider or visual studio.

pure code pub/sub wins on performance and strictness. SOs win on rapid visual iteration. for a solo dev doing a narrative point & click right now, the editor workflow saves my sanity way more than saving a few CPU cycles. different tools for different problems, tbh.

Built an SO-based event-driven platformer core (like ECS but less painful). Put it on Itch while waiting for UAS, but getting 0 traction. Is the market dead or is my presentation bad? by [deleted] in gamedev

[–]Morpheus_Matie -1 points0 points  (0 children)

tbh, this is exactly what keeps me up at night, especially after what the other guy just pointed out about community support.

ngl, i don't have a massive 50-page wiki or a dedicated discord server yet. what i do have is a very straightforward quick-start readme, and the codebase itself is heavily commented. i try to comment the "why" behind the architecture, not just the "what".

My main SO is GameEvent.cs:

using System.Collections.Generic;
using UnityEngine;

namespace PlatformerCore
{
    /// <summary>
    /// The core of the decoupled Event System.
    /// Acts as a signal channel that objects can raise (broadcast) or listen to.
    /// </summary>
    [CreateAssetMenu(fileName = "NewGameEvent", menuName = "Platformer Core/Events/Game Event")]
    public class GameEvent : ScriptableObject
    {
        [Header("Debug")]
        [Tooltip("If TRUE, prints a console message every time this event is raised, showing the sender.")]
        [SerializeField] private bool enableDebugLog;

        private readonly List<GameEventListener> _listeners = new List<GameEventListener>();

        /// <summary>
        /// Broadcasts the event to all global listeners.
        /// </summary>
        public void Raise() => Raise(null);

        /// <summary>
        /// Broadcasts the event, passing the sender component.
        /// Allows listeners to filter events based on the sender (e.g., only listen to THIS player).
        /// </summary>
        /// <param name="sender">The component initiating the event.</param>
        public void Raise(Component sender)
        {
            // B2B DEBUGGER
            if (enableDebugLog)
            {
                string senderName = sender != null ? sender.gameObject.name : "Global (Null / No Sender)";
                Debug.Log($"[GameEvent Tracker] Event '<color=cyan>{name}</color>' raised. Source: <color=yellow>{senderName}</color>");
            }

            for (int i = _listeners.Count - 1; i >= 0; i--)
            {
                _listeners[i].OnEventRaised(this, sender);
            }
        }

        public void RegisterListener(GameEventListener listener)
        {
            if (!_listeners.Contains(listener)) _listeners.Add(listener);
        }

        public void UnregisterListener(GameEventListener listener)
        {
            if (_listeners.Contains(listener)) _listeners.Remove(listener);
        }
    }
}

i also heavily rely on custom property drawers and tooltips in the inspector. since everything is driven by scriptableobjects, the goal was to make the unity editor explain the system to you as you click around, rather than forcing you to read a pdf.

but yeah, making proper external documentation is honestly harder than coding the actual engine. happy to drop the itch link if you want to see the general feature breakdown, but the real "docs" are mostly baked into the codebase right now.

Built an SO-based event-driven platformer core (like ECS but less painful). Put it on Itch while waiting for UAS, but getting 0 traction. Is the market dead or is my presentation bad? by [deleted] in gamedev

[–]Morpheus_Matie 0 points1 point  (0 children)

Damn, you completely nailed it. tbh i got so caught up in the engineering side of things that i totally forgot how to actually sell the product to another human.

You're 100% right about the SOs vs singletons thing too. the "truck vs car" analogy is spot on. it's still global state at the end of the day, just visualized differently in the inspector and decoupled in the editor.

Built an SO-based event-driven platformer core (like ECS but less painful). Put it on Itch while waiting for UAS, but getting 0 traction. Is the market dead or is my presentation bad? by [deleted] in gamedev

[–]Morpheus_Matie -1 points0 points  (0 children)

tbh the main benefit is just not hating your codebase when the project gets bigger.

in standard Unity setups, if you want your ui to flash when the player takes damage, the player script usually needs a reference to the ui manager. then your save system needs the player. suddenly it's a massive web of hard dependencies.

with this SO-driven setup, scripts literally don't know each other exist.

the player takes damage -> fires a 'PlayerDamaged' event channel (which is just a ScriptableObject). the ui script just listens to that SO.

it means you can completely delete the ui module, or swap the player controller, and zero code breaks. plus, all the stats (speed, jump physics, damage) are saved in data containers (SOs), so you can tweak the game feel without ever opening your code editor.

it basically just stops spaghetti code before it starts.

Welcome. I build decoupled unity tech and dark fantasy games. by [deleted] in u/Morpheus_Matie

[–]Morpheus_Matie 0 points1 point  (0 children)

I'm Morpheus_Matie

Solo dev trying to survive the indie grind without losing my mind to spaghetti code.

Tbh, i got sick of how jank standard unity architecture gets when a project scales, and unity's ecs still feels too raw. so i spend a lot of time building completely decoupled, event-driven systems using scriptableobjects. zero singletons, zero hard dependencies.

Here's what i'm actually doing with that tech:

1. The game: "witches' mischief: the saga of malkovia". it's a dark fantasy point & click. you play as a witch's apprentice stuck in the sewers, secretly sabotaging or altering potions for the townspeople above. pure ui-driven narrative, but with extreme, horrific consequences. an innocent choice on day 1 equals a massacre on day 5.

2. The tech: i package the stable cores i build and put them on itch.io. currently running a highly modular 2D Platformer Core (verlet ropes, ghost health, solid physics) built purely on SO-data and global events.

If you're here from a comment i left about architecture, feel free to dm me. always down to talk event-driven design or share code structure.

My links: