Kotlin Game Development: Game Loop

Gamedev |

Updated on

Every game has a game loop. It is a simple loop that should be familiar to every programmer. We can think of a game as a sequence of static images changing fast enough to create an illusion of motion. The purpose of the game loop is to keep generating new frames as long as the loop lasts and, as with any loop, it should not last forever so we must have some way of breaking the loop, usually when a player decides to quit the game.

Thumbnail

This is the forth part of the “Kotlin Game Development” series and it’s better if you read it in chronological order:

  1. Part 1 - Introduction
  2. Part 2 - Creating a Scene
  3. Part 3 - Controller Input
  4. Part 4 - Game Loop (you are here)
  5. Part 5 - Game Factory
  6. Part 6 - Main Menu
  7. Part 7 - Model
  8. Part 8 - Game Scene
  9. Part 9 - Finalizing The Game

Implementation

We’re going to create a class called Game which will wrap the game loop and it will also contain all of the crucial game components such as an input handling module and the current scene.

This class will have two methods:

  • play - this method will activate the game loop so it can start producing new frames

  • pause - this method will move the game to the inactive state. The game loop should be stopped which means the game should stop producing new frames

Here is the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.bubelov.snake.engine

import kotlinx.coroutines.experimental.*
import java.awt.Canvas
import java.awt.Dimension
import java.awt.Graphics2D

class Game(val screenSize: Dimension) : Canvas() {
    var scene: Scene? = null

    val input = Input()

    private var gameLoop: Job? = null

    init {
        size = screenSize
        addKeyListener(input)
    }

    fun play() {
        if (gameLoop != null) return

        gameLoop = GlobalScope.launch {
            var previousIterationTime = System.nanoTime()

            while (isActive) {
                val timePassed = System.nanoTime() - previousIterationTime
                previousIterationTime = System.nanoTime()
                scene?.updateAndDraw(timePassed, bufferStrategy.drawGraphics as Graphics2D)
                bufferStrategy.show()
            }
        }
    }

    fun pause() = runBlocking {
        gameLoop?.cancel()
        gameLoop?.join()
        gameLoop = null
    }
}

How Does It Work?

As you can see, the Game class extends the Canvas which is the part of the AWT (Abstract Window Toolkit) library. We can think of the Canvas as the empty drawing space that we’ll be using to draw our game scenes on and because our Game class is also a Canvas we can easily place it inside any AWT window.

The Game constructor takes a single parameter: screenSize, it should state how much space (in pixels) our game wants to occupy. The Game class also has references to the current scene, the input module and the game loop. The play and pause methods are used to control the game lifecycle.

The most interesting part of this class is the content of the game loop, so let’s examine it in more detail:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gameLoop = GlobalScope.launch {
    var previousIterationTime = System.nanoTime()

    while (isActive) {
        val timePassed = System.nanoTime() - previousIterationTime
        previousIterationTime = System.nanoTime()
        scene?.updateAndDraw(timePassed, bufferStrategy.drawGraphics as Graphics2D)
        bufferStrategy.show()
    }
}

Before we go into the while loop, we have to initialize the variable called previousUpdateTime. This variable holds the time of the previous iteration and we need to know it in order to find out how much time has passed since the last frame was rendered.

The loop itself will run until the enclosing coroutine is active. Calling the pause method will make this coroutine inactive so it will stop the loop so the code inside it will stop repeating.

In The Loop

The first thing that the loop does is calculating how much time has passed since the last iteration and it can vary from computer to computer. The timePassed value will be larger on slower PCs and lower on the fastest ones. Obviously, the lower, the better but players would hardly notice any difference if the game loop can do at least 30 iterations per second. It would even make sense to cap the maximum amount of frames at 60 per second to make sure we’re not wasting more processing power than we actually need to make sure the game runs smoothly.

Now that we know how much time has passed since the previous iteration we can call the updateAndDraw method to update the game state and draw that state on the screen. We can also obtain a Graphics2D object from our Canvas which can be used by our scenes to perform various drawing operations.

And the last step our loop is supposed to do is to call the BufferStrategy.show method to notify other UI components that the frame is ready to be displayed.

What’s next

Now we have the game loop, the input module and the Scene class to display our frames. It’s all tied together and managed by the Game class. The only thing our little framework is missing is an actual window. The game needs to live inside a window and that’s what we’re going to implement next.

This site doesn't have ads and the reasons are simple:

  • Most people don't want to see ads, that's not what they look for when they open web pages.
  • Ad scripts can track visitors, exposing private data to third parties.

If you found this post valuable and you wish to leave a tip, you can do it with Bitcoin:

34CXtg7c4Vbw8DZjAwFQVsrbu9eDEbTzbA