Entity Systems in Unity… some examples of the problems
This is a new series of blog posts, where I’m going to document specific ways + concrete examples in which Unity fails (sometimes spectacularly) as an “ECS” game engine.
I like Unity; but the core architecture (which is very old) is a half-assed ECS, and if we’re to upgrade it into a really good, modern, architecture … we first need to understand exactly where it’s failing, and why.
So, let’s start with Selecting Things…
A 2-player card game where each player normally draws a card each turn, and then plays one or more cards. Sometimes (e.g. when discarding because “too many cards in hand”) they have to select more than one card at once.
Initial version of game will be 1-player versus computer. Very soon: want to upgrade to OPTIONAL 2-players on-screen, with one using the mouse, other using gamepad. Ultimately, want to also add over-the-internet multiplayer (which ends up interfacing with the codebase in a very similar way to the original 1-player-vs-computer, so we can ignore for now).
Player taps card. Now … we must inform many other scripts and independent systems, so they can choose to eg.
- ignore (if invalid for current state of game)
- react (e.g. zoom to display the card more clearly)
- “select” internally (side-effects include: deselect other things)
- advance the game (if it was waiting-for-input)
What/where do you send the “player clicked on a card; can some piece(s) of code PLEASE deal with this??!!?” ?
In Unity, only the low-level “card” object can sensibly detect it has been clicked on.
Both Unity’s Physics (old) and EventSystem (new) effectively force this via their core design. Both require you to attach scripts to the physical objects that will be clicked.
In practice, this is bad for OOP (and bad for ECS too). When there’s e.g. 100 cards all of which must be separately clickable, your code is really in the wrong place. You don’t want cards (which sometimes are in a deck, sometimes on the table, sometimes “virtual” (perhaps in a virtual, unopened booster pack etc)) … to be containing all the GAME logic required to know what to do when they’re touched!
Classic Entity System / ECS solution
- Create a SelectedByPlayer component
- Add it to the card
- Sit back, and relax. Code that cares about input will scan “get me all SelectedByPlayer components” on each frame, and react accordingly
Everything works automatically; any System/Processor that’s “waiting for a selection”, or “making render changes when selections add/remove”, etc … will pick up what it needs, with no work.
You can add new input-handling routines simply by adding them. That’s all. No other changes needed.
Attempting to solve this in Unity
Maybe … NB: I’ve only just started using the new GUI/EventSystem in Unity 4.6+ .. create a custom Input event, and a custom InputModule that can understand that, and then put all the code for ALL affected systems/processors into one monolithic ugly, hard-to-maintain script from Hell.
I suspect that this code will be quite maintainable w.r.t. adding new Input hardware in future – e.g. allowing mouse vs gamepad. But it’s going to suck at the rest, all the business-logic and handling. Which is going to be > 95% of the maintenance cost.
You get one small benefit: you can separate-out different inputs (click versus drag). Sadly, in reality: 95% of game actions will be simple clicks. This is one of those “the code architecture sounded great in academic situation, but reality is so unbalanced, it works out less well in practice” situations.
This is a classic OOP solution, and has the downsides. The only significant benefit I can think of is that it’s a “known” Hell: if you’ve done a lot of OOP game coding, you’ll be familiar with the pain you’re going to run into.
Make a new class “CardClickManager” whose sole purpose is to reference all the possible bits of OOP code that “might” need to react, and which has to be updated by hand EVERY TIME you modify, add, or remove some input-handling code ANYWHERE else in the codebase.
Pretty much the same as above, except it:
- … is even more simplistic (no event-dispatch systems)
- … making it even harder to maintain + debug
- … is slightly more proprietary
Conclusions / Improving Unity
So far, I cannot think of any sane, maintainable solution here other than “suck it down and use OOP, and suffer forever”, or “throw away Unity GameObject/Component, and implement a proper ECS”.
That’s fine, though. That’s the point of these posts – to hilight situations where there’s no good middle-ground, where we must create the data-centric, cleanly-separated architecture of a modern ECS.
Counter-ideas very welcome! Comment away, guys…