0

We've stumbled at an inconvenient design in a game and wondering, whether there is nothing simpler. The background needs some explaining so I'm sorry for the wall of text.

The setting

We have a player class. The player knows how to walk around in the world. At certain occasions, we want to disable the player from being able to walk around.

At first, we just wanted to stop our player from walking when they open the inventory menu.

The player can also walk around the world and inspect objects. This also is supposed to disable walking for the player.

Here is the problem however: You can open the inventory while inspecting an item. There was a bug: the inventory code did not know about the inspection code. As such, if you closed the inventory during inspection, the inventory code allowed the player to walk again.

Our solution

Naturally, to fix this problem we came up with a layer of indirection. It is somewhat similar to semaphores. In a nutshell our indirection is this class:

class ActionBlocker:

void Block(EBlockableAction)
void Unblock(EBlockableAction)

Map<EBlockableAction, Array<Actions>>

The EBlockableAction describes the type in-game action. For example, we have EBlockableAction::Inventory and EBlockableAction::Inspection.

When the inventory is opened, its code simply calls Block(EBlockableAction::Inventory). Likewise, when the inventory is closed it calls Unblock(EBlockableAction::Inventory). It is similar for the inspection code.

Each enum is assigned to an array of actions: each action can be performed and reversed. In the example above, one action is you can enable walking (perform) and disable walking (reverse).

The Block function goes through each action assigned to the enum and performs the action. The Unblock function tells each action to reverse.

Internally, each action has a counter of how many times it was requested to be performed. The action is only performed initially, i.e. when that counter goes from 0 to 1. Likewise, the action is only reversed, once every code that requested for that action to be performed requests for it to be reversed, i.e. when the counter goes from 1 to 0.

I hope the way the system works is clear now (if not I can further explain it).

The problem with our solution

We expanded this system across the game. The number of enum entries has grown. Each enum also has multiple actions now, e.g. there is an action to toggle mouse cursor visibility, another action for ignoring certain types of keyboard inputs, and more.

What we won: all gameplay code is independent from each other. They do not need to talk to each other to sync blocking certain types of things in-game.

What we lost: It is difficult to debug debug the overall system's behaviour. When multiple features interact and block the same things, it is hard to know what is really blocking what.

The system is configured in a separate file. Sometimes it is not obvious that a certain gameplay action will block certain other features in the game.

It has happened on multiple occasions that we were left wondering why a certain input key was being ignored by our game. It was then often traced back to a Block call not getting made. This is difficult to debug.

My question(s)

We are looking for a more simple system than this. Do you have feedback / suggestions? Maybe you have encountered a similar scenario in the past and can enlighten your solution.

We're left wondering whether this solution really is the way to go and it is just the nature of inter-feature dependencies that makes the system so complex, and not the code design itself...

4
  • How are you dealing with input? Events, Polling? How does Player know a movement key was pressed?
    – Caleth
    Jun 26, 2019 at 11:53
  • It's event-based: You subscribe to the input system. An input-blocking action simply clears the subscriber list when they are performed. The list is restored when the action is reversed.
    – Peafowl
    Jun 26, 2019 at 13:27
  • One clarification on the current design: are you saying that you are just keeping a count of blocking events and if the count is above 0, the action is blocked?
    – JimmyJames
    Jun 27, 2019 at 16:43
  • Yes. Only the initial block call causes the block: thatbis when the count goes from 0 to 1. Subsequent calls only increment the counter. The action is reversed only when the counter goes from 1 to 0.
    – Peafowl
    Jun 28, 2019 at 6:26

2 Answers 2

2

Each enum also has multiple actions now, e.g. there is an action to toggle mouse cursor visibility, another action for ignoring certain types of keyboard inputs, and more. ... It has happened on multiple occasions that we were left wondering why a certain input key was being ignored by our game.

The issue here seems to be that your game logic is directly coupled to external input events. A better design would be to separate these layers:

  • Game objects simply model behavior such as moving the player or picking up an item, without knowledge of how that behavior is invoked.
  • There are different game states such as "default / walking", "looking at inventory", "inspecting an item". You can use a stack to model nested actions (for eg. default > inspect > inventory).
  • The UI layer interprets keyboard and mouse inputs based on the current game state and invokes the corresponding behavior on game objects.

With this design, the UI layer will never send a movement command to the player object when you are in a state that doesn't allow it. Debugging is also very easy, you can simply look at the current game state to understand what is going on (or look at the entire stack to see how you got there).

0

You needed to design your event system to be modal.

When a game object subscribes for an event, it also tells the event system what modes that it's interested in:

addEventListener(Keyboard.Left, ["menu", "map"], handler=function {})

If the UI enters a certain mode, it'll only call the event handlers for those modes.

1
  • Good suggestion! We will definitely look into this option for user input. However, I see difficulties integrating this solution to other actions, such as showing and hiding the mouse cursor. That would require some form of casablanca's solution.
    – Peafowl
    Jun 28, 2019 at 6:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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