4

I do a bit of solo game development and always get stuck on some part of the architecture where I can't decide what the best way to tackle a problem is. I've just run into a good architecture problem, and I'm hoping I'll be able to use lessons learned from it down the road. I'll abstract it down a bit to a theoretical problem:

Say my game has two abilities, "Heal Player" and "Stun Enemies". Each of the abilities is reusable with a cooldown. The game will need a separate button to activate each of the abilities.

The core class for gameplay is called GameplayCore. My GUI is all contained in an HUD class, and the enemies are all controlled by an EnemyManager. The Player class contains the player's health. GameplayCore creates and stores the HUD instance, EnemyManager instance, and Player instance.

Obviously I want to separate the UI code from the functionality code. So say on the GUI side I've got HealButton, StunButton, and HealthBar classes that define the appearance and behavior of those controls. On the functionality side, I've got the Player class with a health property, an AbstractAbility class that defines shared ability behavior including cooldown, and the HealAbility and StunAbility classes that extend it to define concrete implementations.

To keep GameplayCore from getting too bloated, I make an AbilitySet class to create and manage the HealAbility and StunAbility instances, and make an instance of AbilitySet in GameplayCore.


Here's where things start to fall apart. I want to be able to change the appearance of the buttons as their cooldowns elapse. I want the cooldown code to be self-contained in AbstractAbility, so HealAbility and StunAbility need references to their corresponding buttons. Now I need to pass those references back from HUD to GameplayCore and then into the constructor of AbilitySet so they can be passed into the constructors for the ability buttons. HealAbility needs references to the HealthBar (in HUD) and the Player (in GameplayCore). StunAbility needs a reference to EnemyManager so it can retrieve the collection of enemies to loop through and stun them. But HUD needs references to the HealAbility and StunAbility so it can call them when the buttons are pressed.

Everything has gotten pretty tangled; there is too much coupling and I'm honestly not sure where the best place to construct the ability classes is. I'm very tempted to merge all of the ability code into the ability buttons, which would greatly simplify everything, but then I'd have important functionality code mixed into a UI class.


What's a good organizational approach to this problem? What are some good architectural strategies I could use to keep the code from getting too tangled? Is my entire approach heavily flawed?

edit: I should note this particular project is in ActionScript 3, which does include an event system (with its own quirks and limitations).

  • 6
    Why don't you use the observer pattern to push updates from your ability classes to your UI classes? – Stephen Feb 10 '15 at 4:01
  • 1
    @Stephen, why don't you post that add an answer? – Pureferret Feb 10 '15 at 7:45
  • 1
    I want the cooldown code to be self-contained in AbstractAbility, so HealAbility and StunAbility need references to their corresponding buttons. Why? The UI can ask instances of AbstractAbility for the cooldown status, and the UI already has these references. – Doval Feb 10 '15 at 19:47
  • 1
    I didn't write it as an answer because I didn't have time to properly develop my thoughts. I figured that it might help the OP in the right direction and other answerers could write up more thorough responses. – Stephen Feb 10 '15 at 22:59
  • 1
    @Kevin On the contrary; the problem is when the game logic knows about the UI, because then UI changes can break the game logic and it becomes harder to run the game logic in isolation (e.g. for testing). There's generally going to be a dependency from the UI to the game logic because the UI needs information to present it to the user. – Doval Feb 11 '15 at 0:32
4

I have actually written a large scale game gui that implements (among many other things) precisely those actions that you are talking about. The way it worked is that the gui was completely separated from the game logic, so you could, in theory, play the game without the gui, or instantiate two guis on the same gamelogic. Nobody ever tried that, but it was theoretically possible, and the fact that it was possible reveals an important characteristic of the design:

The gui must know the gamelogic.

The gamelogic should know absolutely nothing about the gui.

The way this is accomplished is by making everything in the gamelogic observable, and making the gui register various observers to receive notifications from the gamelogic.

So, when the user clicks the button, the button tells the gamelogic to perform the corresponding action.

The action does whatever it is supposed to do, and then it sets its own time-to-unblock to 100%. Since the state of the action object has now changed, it issues a notification to that effect.

The gui has registered with the gamelogic to receive events about the action, so it receives this notification, it retrieves the new state of the action, and it paints the button as fully disabled.

Then on the next tick (the next iteration of the main loop of the game) the action reduces the time-to-unblock to, say, 95%, and issues another event about that change.

The gui receives this event and repaints the button to show it partially disabled. And so on, and so forth.

  • Reversing the flow so that the UI is listening to logic, instead of the logic knowing about and sending direct messages to the UI, really opened this up and helped me untangle things. Thanks Mike (and Doval as well, since he suggested the same thing above)! – user45623 Feb 11 '15 at 20:06
  • As far as the details - since the UI is listening to and sending commands to specific parts of the game logic, it needs access to those parts (e.g. HealAbility, etc). Should I have the logic pass those parts to the UI (which does require the logic to be aware of the UI once), put getters on the logic for those parts (which would expose some components that shouldn't normally be exposed, violating encapsulation), or something else? I've gone with the former for the moment but that does slightly violate your principle of separation here. – user45623 Feb 11 '15 at 20:12
  • I would have put getters in the logic. I do not think there is really any issue of encapsulation here. The game logic exposes the "model" of the game. If the model supports actions, it has to expose them so that they can be triggered and observed. If the model supports inventories, again it has to expose them so that they can be queried and observed. That's my thinking. – Mike Nakis Feb 11 '15 at 20:49
6

As Stephen suggested in his comment, you could use the observer Pattern to connect the different parts of your application.

However, this is a rather static approach, and you will have to implement interfaces everywhere and the dependencies are still strong. Since this is a game, your structure, logic and gameplay might will change over time.

I'd recommend using an event-based approach. It works similar to the Observer, with the difference that neither the sender nor the receiver know about each other (no explicit "notify").

You'd simply fire an "HealthChanged"-event, and every component that is interested will just act accordingly. You can achieve this by using an Event bus.

If you want to make the program really un-coupled and flexible, you might consider using the event mechanics together with an Entity system

It's a component-based approach: Instead of having a health attribute in the player class, the player would be defined by it's components : Health, Attack and Position (or whatever else).

Since I'm not sure if this might be too much overhead for a free-time-project like yours, I won't go into much detail here. But you can check the above link and more detail here

  • I'm using a combination of Unity3D's entity-component system and my own event system and I find it to be a very relaxing way to develop games. Good suggestion. – sydan Feb 10 '15 at 8:15
  • 2
    I agree. It's worthwhile to have an event bus class with a program as large as yours that deals purely with events, triggering and informing listeners. Rather than have many different interfaces as Don suggests, I would recommend you create an EventContext class which provides additional event information depending on the type of event (the part triggering the event in your program is responsible for passing an EventContext object as well). Since you know what event type corresponds to what EventContext, you can simply cast it to say HealEventContext and grab the heal button for instance. – Neil Feb 10 '15 at 8:32
  • I should have clarified I am working with ActionScript, which does have a built-in event system. The event system does have some quirks, namely that for object A to pick up events from object B, A either needs to have a reference to B or be a parent of B on the display. Since HealAbility and StunAbility aren't display classes at all, they would need direct references to the buttons to listen for events from them, and the buttons would likewise need references to the abilities to listen to events from them. Or GameplayCore could listen for the events and redirect them, but that adds overhead. – user45623 Feb 11 '15 at 0:33
  • Another problem with the AS3 event system is it adds a lot of overhead, and I try to avoid using it for things that will change every frame, like how much is left on a cooldown. Maybe I would be better off using a custom event system rather than the built-in one. I actually work freelance so unfortunately it's not just a free-time project. I hope I don't seem too incompetent :P It's not that I struggle with getting things done, but rather sometimes in deciding the best way to do them. – user45623 Feb 11 '15 at 0:36
  • @Kevin I have almost no knowledge of AS, but using your own eventbus seems the way to go. Also, the event could just trigger the beginning of the cool down and provide the duration instead of using one updateevent per frame. – Lovis Feb 11 '15 at 6:58
0

Obviously I want to separate the UI code from the functionality code.

In my opinion games are a category where it is very forgivable to abandon this requirement and just couple game logic to UI unless you are aiming for the type of game where such decoupling is beneficial, like one that could run in a completely different GUI environment with a completely different set of graphics and maybe even a completely different set of controls.

That said, if you want to keep it decoupled, you could simply "map/associate" the GUI code to a given ability type, like "StunWidget" to "typeof(StunAbility)", like so:

MapWidget(typeof(StunAbility), StunWidget)

The mapped widget automatically gets passed the ability instance when it is constructed for a given ability type.

Then when you have a unit selected, you can loop through the abilities available using something like reflection for that unit and create/insert/display the associated GUI controls (given its ability type as key to find the associated ability type widget) for each ability. Then the widget becomes responsible for activating the ability, targeting it if it requires targeting, displaying the cooldown, etc, maybe with a matrix-like display as in Starcraft showing what you can do with the unit:

enter image description here

I love that 3x3 matrix design for the GUI in Starcraft 1, never presenting more than 9 hotkeyable controls to the user at once. I think it's one of the most optimal workflows for a game that allows selecting multiple units, like an RTS or RPG where you have multiple party members you can select individually (including TRPGs). I think the effectiveness of such a design reveals itself through the players who can perform hundreds of actions per minute under such a workflow.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.