Current social networks like Facebook, Twitter or Instagram mostly have a centralized approach ([1], [2], [6]). They are centralized in the sense, that all data is processed in data centers that are under a corporation’s control. It is hard to beat the economies of scale that can be achieved by having gigantic server farms which process the huge amounts of data that are being created. But there is a lot of merit in a more decentralized approach. Especially if that approach serves a purpose other than making money by selling user data or entrapping people’s brains in a loop of distraction and dopamine release.
Of course, decentralization alone is not the sole solution to this problem. But in centralized systems there is always the possibility of data being collected and sold. The cost of operating server farms also creates the need of making a profit. That’s why social networks nowadays are often heavily reliant on ad revenue which creates a need to make users as dependent as possible on the platform, so they spend more time on it.
Society could really benefit from a social network with the sole purpose of connecting people and without the need for psychological tricks or selling data to maximize profits. Social media platforms purposefully create echo chambers to keep engagement high which nurture more extreme opinions and further cement the divide between political camps [9]. Additionally, platforms like TikTok use algorithms to take advantage of the way people’s brains are wired to maximize their time spent on the platform. All while damaging people’s attention span in the process [4].
An ideal social media platform would therefore either need a different kind of monetarization like a monthly fee or it needs to be decentralized and work with a technology like peer-to-peer (P2P) to save on infrastructural costs. That way the load which is normally taken on by data centers could be moved to the clients.
Gaming is fun. Strategy games are fun. Multiplayer is fun. That’s the idea behind this project.
In the past I developed some games with the Unity engine – mainly 2D strategy games – and so I thought it is now time for an awesome 3D multiplayer game; or more like a prototype.
The focus of this blog post is not on the game development though, but rather on the multiplayer part with the help of Cloud Computing.
However where to start? There are many ways one can implement a multiplayer game. I chose a quite simple, yet most of the time very effective and cheap approach: Peer-to-Peer (P2P).
But first, let us dive in the gameplay of Admiral: WW2 (working title).
2 Game Demo
2.1 Gameplay
Admiral: WW2 is basically like the classic board game “Battleships”. You’ve got a fleet and the enemy player has got a fleet. Destroy the enemy’s fleet before your own fleet is sunk. The big difference is that Admiral: WW2 is a real-time strategy game. So the gameplay is more like a real-life simulation where you as the admiral can command your ships via direct orders:
Set speed of a ship (stop, slow ahead, full ahead, …)
Set course of a ship
Set the target of the ship (select a ship in the enemy fleet)
Currently there is only one ship class (the German cruiser Admiral Hipper), so the tactical options are limited. Other classes like battleships, destroyers or even aircraft carriers would greatly improve replayability; on the other hand they would need many other game mechanics to be implemented first.
Ships have multiple damage zones:
Hull (decreases the ship’s hitpoints or triggers a water ingress [water level of the ship increases and reduces the hitpoints based on the amount of water in the hull])
Turrets (disables the gun turrets)
Rudder (rudder cannot change direction anymore)
Engine/Propeller (ship cannot accelerate anymore)
If a ship loses all hitpoints the ship will sink and is not controllable.
2.2 The Lobby Menu
Before entering the gameplay action the player needs to connect to another player to play against. This is done via the lobby menu.
Here is the place where games are hosted and all available matches are listed.
On the right hand side is the host panel. To create a game the host must enter a unique name and a port. If the IP & Port combination of the host already exists, hosting is blocked.
After entering valid infos the public IP of the host is obtained via an external service (e.g. icanhazip.com). Then the match is registered on a server and the host waits for incoming connections from other players.
On the left hand side there is the join panel. The player must enter a port before viewing the match list. After clicking “Join”, a Peer-to-Peer connection to the host is established. Currently the game only supports two players, so after both peers (host and player) are connected the game will launch.
More on the connection process later.
3 Multiplayer Communication with Peer2Peer
3.1 Peer-to-Peer
P2P allows a direct connection between the peers with UDP packets – in this case the game host and player.
So in between no dedicated server handling all the game traffic data is needed, thus reducing hosting costs immensely.
Because most peers are behind a NAT and therefore connection requests between peers are blocked, one can make use of the NAT-Traversal method Hole-Punching.
3.1.1 P2P Connection with Hole-Punching
Given peer A and peer B. A direct connection between A and B is possible if:
A knows the public IP of B
A knows the UDP port B will open
B knows the public IP of A
B knows the UDP port A will open
A and B initiate the connection simultaneously
This works without port-forwarding, because each peer keeps the port open as if they would contact a simple web server and wait for the response.
To exchange the public IPs and ports of each peer a Rendezvous-Server behind no NAT is required.
3.1.2 Rendezvous-Server
The Rendezvous-Server needs to be hosted in the public web, so behind no NAT. Both peers now can send simple web requests as if the users would browse the internet.
If peer A tells the server he wants to host a game, the server saves the public IP and port of A.
If B now decides to join A’s game the server informs B of the IP and port of A.
A is informed of B’s public IP and port as well.
After this process A and B can now hole-punch through their NATs and establish a P2P connection to each other.
A Rendezvous-Server can be very cheap, because the workload is quite small.
But there are some cases where Hole-Punching does not succeed (“…we find that about 82% of the NATs tested support hole punching for UDP…”, https://bford.info/pub/net/p2pnat/).
In those cases a Relay-Server is needed.
3.1.3 Relay-Server
The Relay-Server is only used as a backup in case P2P fails. It has to be hosted in the public internet, so behind no NAT.
Its only task is the transfer of all game data from one origin peer to all other peers. So the game data just takes a little detour to the Relay-Server before continuing it’s usual way to the peers.
This comes at a price though. Since all of the game traffic is now travelling through this server the workload can be quite tough depending on the amount of information the game needs to exchange. Naturally the ping or RTT (Round Trip Time: the time it takes to send a packet from peer to peer) of a packet is increased resulting in lags. And finally multiple Relay-Servers would be required in each region (Europe, America, Asia, …). Otherwise players far away from the Relay-Server suffer heavy lags. All of these lead to high demands on the server hardware. To be clear: a proper Relay-Server architecture can be expensive in time and money.
Because of that in this project I ignored the worst-case and focused on the default Peer-to-Peer mechanism.
3.1.4 Peer2Peer Conclusion
The big advantage of this method: it’s mainly serverless, so the operation costs of the multiplayer is very low. Because of that, P2P is a very viable multiplayer solution for small projects and indie games. The only thing that is needed is a cheap Rendezvous-Server (of course only if no Relay-Server is used). P2P also does not require to port-forward, which can be a difficult and/or time consuming task depending on the player’s knowledge.
But there are disadvantages:
A home network bandwidth may not be enough to host larger games with much traffic; a server hosted at a server farm has much more bandwidth
The game stops if a P2P host leaves the game
No server authority
every player has a slightly different game state that needs to be synchronized often; a dedicated server has only one state and distributes it to the players; players only send inputs to the server
anti-cheat has to be performed by every peer and not just the server
random is handled better if only the server generates random values, otherwise seeds have to be used
game states may need to be interpolated between peers, which is not the case if only the server owns the game state
A dedicated server would solve these disadvantages but in return the hardware requirements are much higher making this approach more expensive. Also multiple servers would be needed in all regions of the world to reduce ping/RTT.
3.2 Game Connection Process
After starting the game the player sees the multiplayer games lobby. As described previously the player can host or join a game from the list.
3.2.1 Hosting a game
The host needs to input a unique game name and the port he will open for the connection. When the host button is clicked the following procedure is triggered:
Obtain public IP address
Originally this should be handled by the Rendezvous-Server, because it is hosted behind no NAT and can see the public IP of requests, but limitations of the chosen hosting service prevented this approach (more on that later)
Instead I used a web request to free services like icanhazip.com or bot.whatismyipaddress.com as a second backup in case the first service is down; these websites respond with a plain text containing the ipv6 or ipv4 of client/request
The Rendezvous-Server is notified of the new multiplayer game entry and saves the game with public IP and port, both sent to the server by the host
Host sends GET-Request to the server (web server) containing all the information needed /registermpgame?name=GameOne&hostIP=1.1.1.1&hostPort=4141
On success the game is registered and a token is returned to the host; the token is needed for further actions affecting the created multiplayer game
The host now waits for incoming connections from other players/peers
The host sends another GET-Request to the Rendezvous-Server /listenforjoin?token=XYZ123
This is a long-polling request (websocket alternative): the connection is held open by the server until a player joined the multiplayer game
If that is the case the GET-Request is resolved with the public IP and port of the joined player, so that hole-punching is possible
If no player joins until the timeout is reached (I’ve set the timeout to 15 seconds), the request is resolved with http status code 204 No content and no body
In that case the GET-Request has to be sent again and again until a player joins
On player join both peers init a connection and punch through NAT
If successful the game starts
(Otherwise a Relay-Server is needed; explained previously)
The host closes the game with another GET /startorremovempgame?token=XYZ123
3.2.2 Joining a game
The player first needs to input a valid port. After that he is presented with a list of multiplayer games by retrieving the information from the Rendezvous-Server with a GET-Request to the endpoint /mpgameslist. This returns a JSON list with game data objects containing the following infos:
name: multiplayer game name
hostIP: public IP of the host
hostPort: port the host will open for the connection
If the player clicks “Join” on a specific game list item the following process handles the connection with the host:
Obtain public IP address
Originally this should be handled by the Rendezvous-Server, because it is hosted behind no NAT and can see the public IP of requests, but limitations of the chosen hosting service prevented this approach (more on that later)
Instead I used a web request to free services like icanhazip.com or bot.whatismyipaddress.com as a second backup in case the first service is down; these websites respond with a plain text containing the ipv6 or ipv4 of the client/request
Inform the Rendezvous-Server of the join
Send a GET-Request with all the information needed /joinmpgame?name=GameOne&ownIP=2.2.2.2&hostPort=2222
Now the host is informed by the server if the host was listening
The server resolves the request with the public IP and port of the host
Now the player and the host try to establish a P2P connection with hole-punching
If successful the game starts
(Otherwise a Relay-Server is needed; explained previously)
3.3 Game Synchronization
Real-time synchronization of game states is a big challenge. Unlike turn-based games the game does not wait until all infos are received from the other players. The game always goes on with a desirably minimal amount of lag.
Of course the whole game state could be serialized and sent to all players, but this would have to happen very frequently and the package size would be very large. Thus resulting in very high bandwidth demand.
Another approach is to only send user inputs/orders, which yields far less network traffic. I used this lightweight idea, so when the player issues an order the order is immediately transmitted to the other player. There the order is executed as well.
The following game events are synchronized:
GameStart: After the game scene is loaded the game is paused and the peer sends this message to the other player periodically until he receives the same message from the other peer; then the game is started
RandomSeed: Per game a “random seed master” (the host) periodically generates a random seed and distributes that seed to the other player; this seed is then used for all random calculations
All 3 ship orders:
ShipCourse
ShipSpeed
ShipTarget
GameSync: All of the previous messages still led to diverging game states, so a complete game serialization and synchronization is scheduled to happen every 30 seconds
Projectile positions, rotations, velocities are synched
The whole ship state is synched
Both game states (the received one and the own one) are interpolated, because I don’t use an authoritative server model and so both game states are “valid”
The following game events should have a positive impact on game sync, but are not implemented yet:
ProjectileFire: Syncs projectiles being fired
Waves: Because the waves have a small impact on the position where projectiles are fired and hit the ship the waves should be in-sync as well
3.3.1 IDs
In game development you mostly work with references. So for example a ship has a reference to another ship as the firing target. In code this has the benefit of easy access to the target ship’s properties, fields and methods.
The problem is with networking these references do not work. Every machine has different references although it may represent the same ship. So if we want to transfer the order “Ship1 course 180” we cannot use the local reference value to Ship1.
Ship1 needs an unique ID that is exactly the same on all machines. Now we can send “ShipWithID1234 course 180” and every machine knows which ship to address.
In code this is a bit more tedious, because the received ID has to be resolved to the appropriate ship reference.
The most difficult part is finding unique IDs for all gameobjects.
Ships can obtain an ID easily on game start by the host. Projectiles are a bit more tricky, because they are spawned later on. I solved this by counting the shots fired by a gun turret and combining the gun turret’s ID with the shot number to generate a guaranteed unique ID, provided the gun turret ID is unique. Gun turret IDs are combined as well: Ship ID + gun turret location (sternA, sternB, bowA, bowB, …).
Of course with an authoritative server this gets easier as only the server generates IDs and distributes them to all clients.
3.3.2 Lockstep
Additionally there is an interesting and promising approach to discretize the continuous game time called Lockstep. It is used in prominent real-time strategy games like Age of Empires (https://www.gamasutra.com/view/feature/131503/1500_archers_on_a_288_network_.php). The basic idea is to split up the time in small time chunks, for example 200ms intervals. In this time frame every player can do exactly one action that gets transferred to all the other players. Of course this action can also be “no action”. The action is then executed in the next interval almost simultaneously for all players. This way the real-time game is transformed into a turn-based game. It is important to adjust the interval based on the connection speeds between the players, so that no player lags behind. For the players the small order input delay is usually unnoticed, if the interval is small enough.
An important requirement is that the game is deterministic and orders issued by players have the same outcome on all machines. Sure there are ways to handle random game actions, but because AdmiralWW:2 uses random for many important calculations and my development time frame was limited I unfortunately did not implement this technique.
4 Rendezvous-Server Hosting
There are almost unlimited hosting options on the internet. Usually the selection shrinks after a specific programming language is picked. But because I used NodeJS with Typescript, which transpiles the code to default Javascript, there were still plenty of hosting options. If I decided to write the server in C# and therefore run a .NET Core application like the game is written with (Unity uses C# or some exotic programmers use Javascript) many hosting providers drop out.
4.1 Alternatives
Of course there is the option of renting an own dedicated server: very expensive for a simple Rendezvous-Server and maintenance heavy, but powerful and flexible (.NET ok).
There’s the option of a managed server: little maintenance but very, very expensive.
We have VPS (Virtual Private Servers): dedicated servers that are used by many customers and the hardware is distributed among them, cheaper.
Then there are the big players like AWS, Google Cloud Platform, IBM Cloud and Microsoft Azure: they can get very expensive, but in return they offer vast opportunities and flexibility; it is easy to scale and monitor your whole infrastructure and a load-balancer can increase availability and efficiency of your server(s); on the other hand the learning-curve is steeper and setting up a project needs more time.
4.2 Heroku
Heroku is a cloud based Platform-as-a-service (PaaS) offering hosting of many common programming languages like Javascript/NodeJS (which I used), Python and Ruby. It does not offer as many possibilities as AWS and co, but it is way simpler to learn and set up.
Also it does have a completely free plan, which grants over 500 hours uptime per month. This is not enough to run the whole month with 30 * 24 = 720 hours, but the application sleeps after 1 hour with no actions and automatically wakes up again if needed. This is perfectly fine for a Rendezvous-Server, because it is not used all the time. The wake up time is not that bad as well (around 4-8 seconds).
Of course Heroku offers scaling so that the performance is massively increased and the app will never sleep, but this comes with a price tag.
In a paid plan Heroku also has a solid monitoring page with events, up- and downtimes, traffic and so on.
Server logs are easily accessible as well.
For setup you just need to create a “Procfile” in your project folder that defines what to execute after the build is completed: web: npm run start will run the npm script called start as a web service. The application is then publicly reachable on your-app-name.herokuapp.com. The NodeJS web server can then listen on the port that is provided by Heroku in the environment variable process.env.PORT.
Deployment is automated: just push to your github master branch (or the branch you specified in Heroku); after that a github webhook triggers the build of your app in Heroku.
But during development I discovered a big disadvantage: Heroku does not support ipv6.
This is a problem, because I wanted to use the Rendezvous-Server as a STUN-Server as well, which can determine and save the public IPs of client requests. But if a client like me only has Dual-Stack lite (unique ipv6 but the ipv4 address is shared among multiple customers) Peer2Peer is not possible with the shared ipv4.
As a workaround the clients obtain their public ipv4 or ipv6 via GET-Request from icanhazip.com or as a backup from bot.whatismyipaddress.com. These websites return a plain text body containing the public IP. After that the peers send their public IP to the Rendezvous-Server as explained previously.
5 Architecture Overview
Typescript usually is a very good choice for larger projects, simply because of the type-safety and development-time error checking. This guarantees no more searching for bugs like typos as it is the case in plain Javascript.
To realize the web server I used the very popular ExpressJS, which does not need any introduction and should be well-known by this time.
6 Conclusion
Real-time multiplayer games are tricky. The game states quickly diverge and much effort has to be done to counteract this. Game time differences and lag drastically compound this. But methods such as Lockstep can help to synchronize the time across multiple players.
While developing, try to keep the game as deterministic as possible, so that player actions yield the same result on every machine. Random is usually problematic, but can be handled via a dedicated game server or seeds.
Peer-to-Peer is a simple and great solution for smaller indie multiplayer games, but comes with some disadvantages. For larger projects dedicated/authoritative servers are favourable.
Heroku offers a fast and simple setup for hosting cloud applications and the free plan is great for smaller projects. If demand increases scaling is no problem and the deployment is automated. But be aware of the missing ipv6 support of Heroku.
All in all: Gaming is fun. Strategy games are fun. Multiplayer is fun – for the player and an exciting challenge for developers.
Welcome to our journey through the blockchain, since the emergence of Bitcoin, one of the most trending topics of the global digital village. After reading this blog post, you’ll have a basic understanding of the technology, a wide overview of future use cases and are able to differentiate between realistic potential and hype.
In the first part we explain the blockchain technology, for everybody who’s not that deep in the topic or needs a quick recap:
What is an transaction?
What’s inside a block?
How does the merkle tree work?
Why do we need a proof of work?
In the second part we take a closer look on the future of the blockchain:
What are Proof-of-Stake, Casper and Algorand?
What are the risks and opportunities of the technology?
Where are the future uses cases of the blockchain?
Is there a blockchain revolution or just another hype? Continue reading →
You must be logged in to post a comment.