More architecture than you can shake Apollodorus of Damascus at.
With its 1.3.0 release, LibGDX announced official support for Ashley: an Entity Component System (ECS). I've read much about the benefits of ECS-based architecture, but it immediately struck me that an ECS would be a natural opposite to LibGDX's Scene2D system. One monolithic, the other svelte. Could they be used in parallel or would I be forced to choose a favorite child? I posed this question to the LibGDX community and got some interesting suggestions. Still, the idea floated at the back of my mind until it was forced to surface, like impurities from iron, in the blast furnace that is Ludum Dare.
Rather than write a traditional After Action Report for my Ludum Dare game Interstellar, Inc., I will instead look back at the homogenous mixture of inspiration and madness that is my solution to the above question. Perhaps we can distill the inspiration without going blind.
Why worry so much about architecture, anyway? Well in part because this is one of my favorite past-times, but more importantly because architecture is about organizing your code to optimize the production of code. It's about synchronizing brain and machine. Decreasing the friction between your imagination and bringing your ideas to life.
If you're writing game logic and you have to pick through an entire scene graph to find all of the planets, that's friction.
If you're writing input handling and you have to iterate over every entity in the game to see which one's been clicked, that's friction.
Less friction means more game.
At some point in the middle of Ludum Dare's Saturday (the second of three calendar days in my timezone) I was struggling with this friction. I had started with Scene2D because I needed a clickable UI which I didn't want to build from scratch. But now I was tackling the "worker system" which allows you to perform actions like building connections and mining planets. This system doesn't care about UI and presentation; it's interested in the internal model of the game and the rules which govern it. Yet here I was wading through Actor objects just brimming with UI code.
I come from a web development background and this sort of thing triggers sirens and flashing lights in my brain. WARNING: Flagrant Violation of the Separation of Concerns!
Perhaps the Ludum Dare theme ("connected worlds") jogged my thinking into the right space, or perhaps I'd simply been waiting to arrive at the appropriate test grounds, but after much frowning and cursing I was struck with inspiration. Use Ashley for game logic. Use Scene2D for UI. Model and View. Parallel worlds. Like Spock and Evil Spock.
Model and View
I would use the two systems for their strengths. Scene2D would render things on the screen and deal with user input; Ashley would maintain a pure model of the game world. Ashley is well-suited to dealing with groups of entities (Families) differentiated by their properties (Components); if I just had Scene2D I'd have to store and maintain these lists myself. If I gave up Scene2D, I'd have to reinvent its functionality. The separation de-cluttered my UI layer and my model code.
At this point I had traded an organization problem for a communication problem. How do you mirror game state into the UI? And how do you impact the game state in response to user input?
The first tool for handling communication concerns are my static, singleton "Controller" objects. GameController talks to Entities to allow interaction with the game model. FieldController and HudController talk with Scene2D Stages to allow interaction with UI components. (Did I mention I use two separate Stages?)
If you want the user's Player model, or the Map of the world, you ask GameController. If you want to set the text rendered in the "tool tip", you ask HudController.
Another key element is that FieldController and HudController implement the EntityListener interface, which means they can be informed when an Entity is added to or removed from Ashley. I use this to create and add a corresponding Actor to the appropriate Stage. At the same time, the Actor saves a reference to its model Entity (if any). And this is how the Actors reflect the game state. Simply put, an Actor can poll its model Entity for state changes during its act() method and update itself accordingly.
When a Planet goes from neutral to being owned by a particular player, the game model is updated and on the next act() call, the Planet polls for the change and updates its color as well as which buttons are shown.
If you're anything like me, you've got sirens and flashing lights again: surely all that polling is a performance bottleneck, right? Maybe so. On the scale of this game it doesn't matter in the slightest, but it's a valid concern. It would certainly be more ideal if state changes happened in a "push" fashion, rather than a constant poll. This is, in my opinion, the biggest improvement to make on the architecture I'm sketching out. Some kind of unifying message-passing system would be great here, but it's not something I've tried to implement as of yet.
Of course all of this code is up for perusal on Github, so feel free to go look for yourself. Keep in mind that this is Holy-Crap-Get-It-Done-in-48-Hours code and I didn't sleep particularly well that weekend, so it's not a great exemplar of all things. In fact I committed some egregious crimes against code that The Hague will no doubt sentence me for one day. This I do not deny. But I think if you squint a little and cross your eyes, you can sort of start to see some good code in there. Or if nothing else, a sail boat.