Chronos Engine3 min read

If there’s one thing that sucks in game development, it’s writing yourself into a corner with your game engine. Imagine writing a singleplayer game, for instance, only to find out later on that you want to make it multiplayer? You’re going to have to rewrite a lot of code.

For instance, image two players are running on two different machines, each of which takes a small amount of time to communicate with each other… let’s say tens of milliseconds if we’re lucky. You could simply run the game on both machines and have each player update the other on what’s going on, right?

The Problem

Say player one fires a bullet directly at player two. Tens or hundreds of milliseconds later, player two gets that information. By that time, however, several frames of animation have passed. Player two might have moved out of the way. So now player one thinks there should be a hit and player two thinks there shouldn’t be.

The more lag there is in a network the worse this situation becomes.

The Solution

To solve this, an engine needs to correct discrepancies behind the scenes. One method is to keep a history of the last n milliseconds of gameplay. When a discrepancy is found, the engine rewinds to when it occurred, corrects the issue, fast forwards the simulation to the current time, and then gently nudges the players to where they should have been. An additional method is to constantly show players where they predict the other guy will be and what they will be doing. If an incorrect prediction is made, again, the engine gently nudges everything back to where they ought to be.

Deciding on an Engine

I decided that I might want an online multiplayer mode down the road, and that the ability to rewind my game could make for some interesting gameplay mechanics.

My first step in writing this kind of engine was, of course, to keep a running record of everything that happened in the game. There were two major constraints this imposed. First, every game variable now had to be sourced from a specific pool that could be recorded. Second, any state the game was in had to be completely recoverable by injecting historical data back into my game objects.

Chronos Engine

To solve the first case I created a Variable generic class that had logic to register itself automatically with an underlying historical data recorder. The variable memory itself is now stored there, and the generic itself is simply a pointer to a memory index. Every frames is serialized and recorded into a historical ledger.

To solve the second problem, I created a state machine for game objects that was capable of receiving this data and recreating the exact state it was in at a given point of time using per-tick update loops.

It turned out to be relatively straightforward to write, and the design has been only slightly more cumbersome to use than your typical lightweight engine. There are hidden benefits, though:

  • Everything is deterministic. Given a certain player input on the at a certain state, the exact same thing will happen every time.
  • I can record myself playing a character and use that recording for in-game cinematics, ghost races, and game preview screens.
  • Automated testing and AI training becomes easier. The computer can play the game until it gets hurt, rewind a bit, and try something else until it discovers a successful strategy.

I might not end up making full use of these capabilities, but if I do it will have been far easier to write into the engine now than to rewrite the whole game later.

Leave a Reply

Your email address will not be published. Required fields are marked *