Game Systems Interaction Design

  softwareengineering

Intro

I’m writing an FPS game in c++. There is a timed game mode, players run around a map shoot from a variety of weapons which are either hitscan or projectile based, when a shot connects, based on information about the game state players are assigned points, when the time is up the player with the most points wins.

I am currently working on the client side, where we use opengl for graphics, glfw for input and window, and other libraries for physics and sound. In each of the designs I’ve thought about there are always systems: Graphics, Input, Physics, Sound, Gameplay which store their own state and can be interacted with through methods they expose.

Design 1

In this approach in int main() { ... } we initialize the systems and store them in variables which reference the respective systems, whenever a function is called which needs a certain system we pass it in as an argument.

For example, we have character.update_velocity(delta_time, Input, Sound) and then inside we have the input object is needed to be passed into this call because we check Input.forward_pressed etc… in order to change their velocity depending on what keys are pressed. if (Input.jump_pressed) { Sound.play_sound("jump") ... }.

Apparent Benefit: You’re focusing on telling the computer on what to do to make the game happen rather than other things.

Apparent Downside: Your function signatures get cluttered with systems. You need to work around callbacks with fixed signatures because you can’t pass systems directly as parameters into them.

Design 2

We use the same approach, but instead of passing the systems that are needed to specific function calls, we instead make each of the systems global which means that the functions don’t need to take them in as arguments, but the code inside these functions stays the same as design 1.

Apparent Benefit: The signature of functions is not cluttered by a bunch of systems. Fixes the callback thing mentioned in Design 1.

Apparent Downside: It’s not immediately obvious what systems a function call will use by looking at its signature

Design 3

In this design we use an event system to separate concerns. Now instead of passing in the systems into the respective function calls that need them, the function calls emit signals which then call the code. There is a class called SignalMediator which contains all of the systems and then sends signals to respective systems whenever certain events get triggered.

For example, we have character.update_velocity(delta_time, Input) when a jump occurs we call SignalMediator.emit_signal("jump_occurred") now the Sound object subscribes to the SignalMediator‘s jump_occurred signal and defines what should occur when this happens.

Apparent Benefits: Our code becomes easier to read because separate functions only interact with a single system at once.

Apparent Downside: The code that makes the game happen is now spread out over more files so it’s harder to see all of the functionality at once

Question

I’ve started writing the game following design 1, but I’m heavily considering design 3 and I’ve frozen myself up because I’m not sure how design 1 will scale over time as opposed to design 3. I’m also curious if any of the designs I’ve listed have performance overheads that I’m not considering.

If anyone has insight into these designs or other designs that keep performance, code complexity and maintainability in mind could you share your experiences?

LEAVE A COMMENT