
Rollback Game
Game SAE Network C++
Introduction
This rollback game has been developed during my studies at SAE Institute Geneva in the Network Programming module. I had to develop a game with rollback and real-time network synchronization using my own physic engine. The time given was 1 month to develop the game and the rollback system.
We were given the choice to use Photon, but I choose to use my own network library based on SFML to implement the network part of the game.
The game is available on my GitHub repository here and its technical documentation here.
In this post, I will explain how I implemented different parts of the game and the problems I encountered:
- Architecture
- Rollback
- Network
- Problems
Game
Here is a gameplay video of the game in split screen:
The game is a 2D platform game where one player is a ghost who launch bricks on the other player to crush him, making him “Splotch”. The other player is a human who has to avoid the bricks.
When the human is hit by a brick, they become a ghost and the other player becomes a human. The bricks are not removed when switching players.
The game is over when the ghost has launched 50% of the bricks available.
Architecture
Client
It has a fixed frame rate of 30fps for physics and has VSync for rendering. This low frame rate is helpful for the rollback system. The render simulates the next position of player to have a high frame rate despite the low one of physics.
So if elapsed time is equal or more than the fixed time step, it will do a physics step.
The client starts his fixed update by getting the input from the player and sending it to the server.
Then, it processes a packet received from the server, rollback if needed, do fixed physics steps.
After that, it renders it every frame.
Server
The server is much simpler than the client. It contains a lobby and game rooms system that can handle multiple games at the same time.
When receiving a packet from a client, it will process it and send it to the other player in the same room.
If it has both player inputs, it will simulate the game state to the frame where the input was sent and send the confirmed frame to both players.
The confirmed frame contains the frame number, both player inputs, and a checksum of the game state.
Rollback
Rollback is a technique used in fighting games to ensure that the game state is consistent between both players. This is done by rewinding the game state to a previous point in time and replaying the game from that point.
When player 1 sends his input to the server, the server sends the input to player 2, but it receives it after some frames, so it needs to simulate the game state to the frame where the input was sent.
Why use rollback?
I used rollback to ensure that the game state is consistent between both players, because it’s funnier to play a game when it looks like the game is in real-time.
How I implemented rollback
I implemented rollback by using a fixed time step and a fixed frame rate.
I store all data from the game in an object called GameData
which is passed to RollbackManager
to store the current game state and the previous game states.
I make a checksum of the game state to ensure that the game state is consistent between both players.
A checksum is a value that is computed from a data set and is used to check the integrity of the data set.
When the server receives an input from a player, it simulates the game state to the frame where the input was sent and sends the input confirmed to the other player with a checksum of the current game state.
When the client receives the confirmed frame, it checks if it needs to roll back to the confirmed frame and simulates the game state to the confirmed frame.
The client checks the checksum on the confirmed frame to ensure that the game state is consistent between both players.
Here the loop of the game with rollback, we can see the multiple physics steps with rollback. The frame is 2.5ms long and the rollback is four frames long.
Network
How I implemented the network using SFML
I implemented the network using SFML by using the sf::TcpSocket
and sf::UdpSocket
classes and multithreading.
I created a network interface that needs to be inherited by the client and the server.
I created my own packet class to send data between the server and the client.
Here are more details about the packet class.
Network model
Network model is based on the client-server model.
The server is the host of the game, and the clients are the players.
It sends inputs between them and simulates the game when receiving both inputs from the clients.
Network protocol
The network protocol is used in TCP and UDP.
TCP
At the start, the network launches a thread that waits until he receives a connection of a new client. When a client is connected, the server will create a new thread to receive packets from the client.
At the start, it will also launch a thread that will send packets to the client.
When sending or receiving a packet, it will add them to a queue to avoid data corruption.
To receive a packet, it can be got with a method that will return the first packet of the queue to treat them in the main thread.
UDP
For UDP, the client need to send a UDPAcknowledge
packet to the server to link TCP client and UDP client.
A single thread is allowed to receive packets in UDP.
Problems
Un-sync
I got un-sync when I was switching players because of the different inputs that were sent to the server.
The main problem was that I was storing who is player and ghost outside game data. So when I was switching players, the server was switching player roles but at an old frame and the client was switching player roles at the current frame. It caused big problems because the server was simulating the game with the wrong player roles.
I resolved this by refactoring the code to store the player and ghost roles in the game data.
I make that the client and server inherit from the GameData
class
to store the player and ghost roles in their own way and associate input from player 1 and 2 to the correct player.
Conclusion
I was given the task to develop a game with rollback and real-time network synchronization using my own physic engine.
It was hard to combine all these parts. I saw that my physics engine was not correctly implemented, colliders were not working correctly, so a lot of features were abandoned to be replaced by a much simpler version.
The rollback system is hard to implement at first, but when you have it and a game state, you can implement what you want, but it absolutely needs to be inside the game state object.
The network part was the easiest part to implement, because I already had a network library that I used in other projects.