Escape Code Chaos: Embrace Logic-Rendering Separation!


Separating the game world logic from its rendering

I do not possess the title of game developer, for my main focus is on creating enterprise software that remains maintainable and scalable. There's a number of principles that are very logical in that realm. Not abiding by those principles makes me feel naughty, so I tried to apply some of them during my first game development experience.

In this devlog I'll share the approach I used to separate game logic from game rendering.

The game world

I'll be using the simple game "deep dive" as an example, let's get familiar with how it works (or give it a quick try here) The player controls a diver that needs to reach the bottom of the sea. The diver has two challenges:

  • every second underwater costs some oxygen
  • fish are swimming around that cost oxygen on collision

The game uses a level system, each level the diver needs to go deeper and spawns more and faster fish

The two concerns

We'll be separating the project into two concerns: the domain and the rendering. The separation happens by stating that each class that we create, will only belong to one of those concerns. This is helped by having a separate package for each.

Concern 1: the domain

The classes that belong to the domain encapsulate the rules and mechanics of the diver and his oxygen consumption, the fish and their movement, ... The domain doesn't know anything about rendering, it doesn't even know that it is being rendered. It could be used in a headless way.

I chose for a world that changes state using a ticking system. Each tick, all actors (diver and fish) are asked what they want to do, and their desired action gets taken into account for creating the next state.

Concern 2: the rendering

The rendering concern focuses on visualizing the game state without delving into the underlying logic. This separation ensures that rendering classes do not influence the domain's behavior.

Communication between the two

Obviously the rendering has to know SOMETHING about what's happening in the domain, how else could it render what's going on? For the rendering to know where to draw the diver, it needs to know where the diver is located.

The strategy we will look at is a listener based approach (not that different from an event driven approach). We will wire up the classes so that the rendering model is a listener of the domain, and the domain will tell the listener what is happening.

We define a Listener interface in the domain as follows:


    interface WorldListener {
        fun onDiverSpawn(location: Vector3)
        fun diverMoved(xDelta: Int, yDelta: Int)
    }

The implementation of this interface, will reside in a rendering class.

    class WorldRenderModel : WorldListener {
        override fun onDiverSpawn(spawnLocation: Vector3) {
            diver = DiverRenderModel(spawnLocation.x, spawnLocation.y)
        }
        override fun diverMoved(xDelta: Int, yDelta: Int) {
            diver.x += xDelta
            diver.y += yDelta
        }
    }

When creating a World Object, we pass the WorldRenderModel as WorldListener.

    val worldRenderModel = WorldRenderModel()
    val world = World(worldRenderModel)
    

When in the state of the domain, the diver moved position, the render model will be told by the domain through this listener. We stay true to the desire of the domain not knowing about the rendering, and being able to function without rendering: the domain only feeds information in the listener, and doesn't care whether something happens with that info.

User interaction

For out game to be a game, the user has to be able to interact with it. It's not the domains concern to handle user input, we want to keep the domain as pure as possible. We should be able to protect the domain from knowing whether the diver is being controlled by keyboard (and which keys exactly), or mouse.

Doing this allowed me to add different controller options without having to change any code in the domain.

Open source

The entire codebase of the game can be found here. This codebase is far from perfect, it was made during the first gamejam I joined, and in the eye of a deadline shortcuts were made.

Shoutouts

Thanks to my wife for drawing the game art in a matter of minutes.
Thanks to the community of libGdx for creating a game library that doesn't force anything, but does make rendering easier.
Thanks to the creators of liftoff for helping me get up and running with a Kotlin project that is deployable as a HTML+js webpage.

Files

testupload-win.zip Play in browser
Version 19 Oct 29, 2023

Leave a comment

Log in with itch.io to leave a comment.