I was staring at my SelectionSystem class a few days ago and realized that the public interface was completely stateless, something that immediately raised a design question I hadn't considered.
It was just a collection of logic handling lists. It didn't care about the frame rate, it didn't care about the scene hierarchy, and it didn't actually "live" anywhere. It just took a unit to select and that was it.
At that moment, I hit a wall. If the system is stateless, where the hell should it actually reside?
Now, for a bit of context: I’m building this system in a separate repository as a "test of skill" to master ScriptableObject (SO) architecture. My long-term goal is to package this up and sell it for cheap to help other developers, especially juniors, get a robust selection system without the typical "I just put everything in a singleton" headache. But to make it as easy to use as I wanted, I had to figure out the "entry point" problem.
Here is the mess I went through trying to decide where the SelectionSystem should live.
The "Comfort Zone": The MonoBehaviour Route
The instinctive move in Unity — the one we all do when we're tired or in a rush — is to just slap a MonoBehaviour on a GameObject, call it SelectionManager, and call it a day.
The Appeal: It’s comfy. You can drag and drop references in the Inspector, and you can see the system sitting right there in your Hierarchy. If your selection system is something like an RTS-style box selection, it feels natural for it to be "scene-owned" because it needs the active camera and Physics.Raycast to figure out what the player is clicking on.
The Problem: The moment you do this, you’ve basically built a prison for your logic. The system is now coupled to the scene. If you want to use this to a different scene, you have to set this up as a prefab. Also, other systems now have to find this specific instance (hello, FindObjectOfType<T>() or the dreaded Singleton pattern), and your logic is now tangled up with Unity's scene lifecycle despite not using any of its methods.
It’s great for a weekend prototype, but for a reusable package? Maybe not my cup of tea.
The "Architect's Dream": The ScriptableObject Route
Since I'm leaning heavily into SO architecture, the next logical step was to make the SelectionSystem an injectable ScriptableObject service.
The Appeal: This is where things start feeling elegant. The selection rules live in an asset. You can inject that asset into whatever needs it. The logic becomes scene-independent; you can call SelectUnit(unit) from a UI button, a custom input handler, or a debug tool without needing a reference to a specific GameObject in the scene. It plays beautifully with modular asmdef designs, keeping the orchestration separate from the scene components.
The Problem: It's a potential "Shared State" Trap. Here is where it can get weird for some junior developers. ScriptableObjects are assets. If you store runtime state — like who is currently selected — directly inside the SO asset, that state persists across scenes. You stop the game, and the unit is still "selected" in the editor. Unless it was destroyed, of course. In that case, your list now contains a null element, ready to bite back at runtime.
And then there's the lifecycle. SOs don't have an Update loop. If your selection system needs to do something, even for a single frame, you're back to needing a MonoBehaviour .
The Epiphany: "But wait... what about Split-Screen?"
While debating MB vs. SO, I hit the real wall: Instance Scalability.
I started asking myself:
What if the game is split-screen?
Suddenly, one global SelectionSystem asset isn't enough. You need two. One for Player 1, one for Player 2. If the system is a single SO asset, Player 1 selecting a unit would accidentally select it for Player 2 as well. Total chaos.
What if the AI uses the selection system to target units?
Does every single AI agent need their own SelectionSystem asset? If you have 100 of them, are you really going to create 100 SO assets in your project folder? No way.
Also, this all assumes we know the number of players/AIs ahead of time, and that's not always the case. So the question isn't just "how many SelectionSystem do I need?" anymore. It's "how do I create one on demand?"
Now, someone will (rightly) point out: "You can just call ScriptableObject.CreateInstance<SelectionSystem>() at runtime. Problem solved."
True. That API exists precisely for this. But ask yourself what you're actually getting when you do that.
The entire reason to reach for a ScriptableObject in the first place is because it's an asset. If you're using that method, you might as well use a plain C# class instead of inheriting all the overhead of aUnityEngine.Object.
The Pure C# Detour
I briefly considered going full "Clean Architecture" and making the system a pure C# domain service.
The Appeal: Maximum flexibility and testability. In theory, I don't even need the Unity Editor. No dependencies, no scene overhead, just pure logic.
The Problem: Integration friction. Pure C# might be a bit of a pain for junior developers to integrate because there's no "drag and drop" in the Inspector. You need some manual bootstrapping code to get it running in Unity.
The Solution: The Hybrid Approach
I realized that the "entry point" shouldn't be the "system" itself. The system should be the logic, and the entry point should be an adapter.
That's why "which one is better?" doesn't really hold up as a question. It's not an either/or choice. You can offer all of them.
Why I'm sticking with SO (for now)
Going through this exercise, I realized the "correct" answer is probably the hybrid approach, and let each developer decide what's best for their project.
But here's the thing: this system is a "test of skill" to master ScriptableObject (SO) architecture. Sure, this approach has real problems, but they're not problems I have right now, and limitations can be documented. Building the fully decoupled version today would be solving for a scale I don't need yet.
So I'm keeping the SO-based version as-is and rather focus on flexibility and extensibility. If one of those use cases show up for real, which is very unlikely for my game, at least I know exactly where the seams are.
What about you? Do you prefer the "everything is a MonoBehaviour" simplicity, or have you fallen down the ScriptableObject rabbit hole too? Let me know in the comments!
My Game's Links:
The Weight of One - Official Channel
The Weight of One - Itch.io Page
My Socials:
My Linkedin
My Personal Channel
You can find my Dev Vlogs in both Youtube channels!
Top comments (0)