Bannermen, a Classic RTS Game Using Lockstep with Photon and Unreal Engine
This guest post was written by Christoffer Andersson, co-founder and software architect at Pathos Interactive. Pathos Interactive is a game studio headquartered in Mölndal, Sweden. The studio was founded in 2015 and are currently developing Bannermen.
About the Game
Bannermen is an RTS game with the goal to refresh the classic RTS genre. Bannermen will contain a singleplayer campaign and several multiplayer modes, where the main tasks consist of base building, resource management and battling enemy armies. The maps and missions vary in both length and shape. The game also contains something we call dynamic environments which allows players to interact with the environment in several ways. One example of this is players being able to control different nature powers by controlling religious spots on the map. Depending on the level’s environment, different dynamic environments will be available. Our goal is to create more interesting combats with greater strategic depth while allowing the player to feel badass and in control. If you are interested in our work please check out our pre-alpha demo.
Choosing Lockstep
One big requirement for Bannermen was to support a modern networking solution. Nowadays most players would expect a seamless experience without having to worry about hosting, port forwarding, network bandwidth and you name it. Photon’s networking model was the perfect fit for those requirements. Since we also wanted to support a larger number of units while keeping the network bandwidth low, we chose to implement a lockstep model in Bannermen. If you not familiar with “lockstep”, it basically means that every frame is synced across all clients. It’s then enough to only send player input, keeping the network bandwidth usage minimal.
Bannermen is created in Unreal Engine 4 which is a very great engine. The only problem is that unlike Unity there is no support out of the box for lockstep at the moment. We implemented lockstep with the C++ SDK directly.
Keeping it synced
Implementing lockstep in your game will affect the whole architecture of the game and must be taken in consideration for every step in the designing process. Since every frame must be synced across all clients, input must be synced and each frame must be deterministic. Everything game logic related must be deterministic. If clients receive input a certain frame, the state must remain synced an arbitrary number of frames later. If not done correctly a big problem can arise which is desynchronization, meaning that the game must either be synced or terminated. If implemented correctly however this should never arise.
Unfortunately, desynchronization can arise in a lot ways so you have to be careful. E.g. using floats or doubles may not be deterministic across different platforms or even computers. With multithreading, it gets even harder. Multithreaded code must always result in the same state across all clients.
Input Evaluation
One problem we had in Bannermen was when two workers wanted to enter a resource at the same frame, if the resource only had room for one more worker we had to choose one worker in a deterministic way across all clients. It may sound like a small issue and it happens very rarely, but if it isn’t deterministic the game will suffer from desynchronization when it happens. It helps to always evaluate input in the same order across all clients but may not be enough. Also, we don’t want to give any player priority or an unfair advantage. This time we ended up breaking up the “enter resource” action into two phases. First allow all workers to register as candidates to enter. Then we use a deterministic random algorithm based on workers’ unique network ID that will be fair over the course of time. However, this is nothing a player can notice in practice.
Fixed Point Math
In Bannermen we wrote our own math library for fixed point math to avoid the floating-point issues. Using fixed point math, we then implemented our own navigation mesh, pathfinding, collision system, etc., all based on fixed point math. We run a separate simulation thread in parallel with Unreal Engine’s main thread. This thread simulates all the actual game logic. Then we sync the game state every frame with Unreal Engine’s main thread. This allow us to use a lot of graphical effect while still having great FPS, almost like running Unreal Engine without game logic or at least very light weight logic.
To get started implementing lockstep support in Unreal Engine I would recommend starting with implementing or using some existing fixed-point math library. You can play around with the precision or even support different amount of precision depending on your needs. Here is a very basic implementation to get started:
#pragma once
#include "FixedPoint.generated.h"
USTRUCT()
struct FFixedPoint
{
GENERATED_USTRUCT_BODY()
FFixedPoint();
FFixedPoint(uint32 inValue);
FFixedPoint(const FFixedPoint &otherFixedPoint);
FFixedPoint& operator=(int32 intValue);
FFixedPoint& operator=(const FFixedPoint &otherFixedPoint);
FFixedPoint operator+(const FFixedPoint &otherFixedPoint) const;
FFixedPoint operator-(const FFixedPoint &otherFixedPoint) const;
FFixedPoint operator*(const FFixedPoint &otherFixedPoint) const;
FFixedPoint operator/(const FFixedPoint &otherFixedPoint) const;
static FFixedPoint createFromInt(int32 value);
static FFixedPoint createFromFloat(float value);
// use UPROPERTY so unreal engine can save the value!
UPROPERTY()
uint32 value;
static const int32 FractionBits = 8;
static const int32 FirstIntegerBitSet = 1 << FractionBits;
};
#include "YourHeader.h"
#include "FixedPoint.h"
FFixedPoint::FFixedPoint()
: value { 0 }
{
}
FFixedPoint::FFixedPoint(uint32 inValue)
: value { inValue }
{
}
FFixedPoint::FFixedPoint(const FFixedPoint &otherFixedPoint)
{
value = otherFixedPoint.value;
}
FFixedPoint& FFixedPoint::operator=(const FFixedPoint &otherFixedPoint)
{
value = otherFixedPoint.value;
return *this;
}
FFixedPoint& FFixedPoint::operator=(int32 intValue)
{
value = intValue << FractionBits;
return *this;
}
FFixedPoint FFixedPoint::operator+(const FFixedPoint &otherFixedPoint) const
{
int32 result = value + otherFixedPoint.value;
return FFixedPoint(result);
}
FFixedPoint FFixedPoint::operator-(const FFixedPoint &otherFixedPoint) const
{
int32 result = value - otherFixedPoint.value;
return FFixedPoint(result);
}
FFixedPoint FFixedPoint::operator*(const FFixedPoint &otherFixedPoint) const
{
int32 result = value * otherFixedPoint.value;
// rounding of last bit, can be removed for performance
result = result + ((result & 1 << (FractionBits - 1)) << 1);
result = result >> FractionBits;
return FFixedPoint(result);
}
FFixedPoint FFixedPoint::operator/(const FFixedPoint &otherFixedPoint) const
{
int32 result = (value << FractionBits) / otherFixedPoint.value;
return FFixedPoint(result);
}
FFixedPoint FFixedPoint::createFromInt(int32 value)
{
int32 newValue = value << FractionBits;
return FFixedPoint(newValue);
}
FFixedPoint FFixedPoint::createFromFloat(float value)
{
int32 newValue = value * FirstIntegerBitSet;
return FFixedPoint(newValue);
}
The only parts that may not be trivial are multiplication and division. Basically, if we multiply two 24.8 fixed point (i.e. 24 bits for integer part, 8 bits for fraction part) we will end up with a result that looks like 48.16. If we want the result to be in the form of 24.8, we simply ignore the upper bits of the integer part like an overflow. Then we shift right 8 bits in this case, resulting in a truncation of the 8 lowest fraction bits. There exist good articles regarding this subject for those who are interested in reading more. E.g. https://www.codeproject.com/Articles/37636/Fixed-Point-Class or http://x86asm.net/articles/fixed-point-arithmetic-and-tricks/.
I don’t recommend using the create from float method in game logic but it can be handy to initiate values from the editor that then can be saved. More operators and math function can of course be implemented and even exposed to blueprints easily (Unreal Engine’s visual scripting).
Last thing I must mention is how one can see the fixed-point value in an understandable format when debugging. I recommend using a “natvis” file to visualize the value as a float or double to make life easier. Here is an example with and without a “natvis” file.
Without “natvis” file:
With “natvis” file:
“natvis” file:
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="FFixedPoint">
<DisplayString>{{{(double)value / FirstIntegerBitSet}}}</DisplayString>
</Type>
</AutoVisualizer>
In Visual Studio 2017, the location to place a “natvis” file is something like “C:\Users\<username>\Documents\Visual Studio 2017\Visualizers”, depending on your setup.
Finally, it was a lot of work to implement lockstep for Bannermen in Unreal Engine. However, now when it’s done we can use the full power of Photon. Also, our design has drastically improved the performance compared to an early prototype we had running purely in Unreal Engine. The network bandwidth has been drastically reduced and since our simulation tick is running in parallel with Unreal Engine’s main thread, we have freed-up resources to do more cool stuff. Overall, we find the Photon integration combined with lockstep model to be a perfect fit for Bannermen and Unreal Engine.
Happy hacking!
Christoffer Andersson
Co-founder & Software architect, Pathos Interactive
http://www.bannermen.net/