One of the most requested features for Defend the Highlands was multiplayer. I'd decided at the beginning of that project, in the interest of keeping my goals realistic, to only do single player. For Defend the Highlands World Tour however, I've decided to make my first foray into the world of multiplayer programming.
World Tour will have two multiplayer game modes, co-op and vs. In co-op, the players each have a separate group of Scotsmen (distinguished by kilt color). They share resources, and can use each others towers, but cannot control each others Scotsmen. As in a single player game, the goal is to construct the haggis factory and defend it from the waves of enemies.
In Vs. mode, one player will take control of the Scotsmen, while the other will take control of the enemies. The player controlling the Scotsmen will start with a large amount of money and Scotsmen, and a completed haggis factory. They will get a short head start to set up their defences. The player playing as the enemy will have a gradual income of “recruitment points” as well as earning extra recruitment points for defeating Scotsmen. They can recruit whatever enemies they like at any of the enemy spawn zones, and control them the same way as they'd control their Scotsmen in single player. Their goal is to defeat the Scotsmen in as short a time as possible. After they have defeated them, the players switch roles. This goes on for as many rounds as the players chose when configuring the game. At the end, the player who cumulatively held out for the longest amount of time when playing as the Scotsmen wins.
Setting up the multiplayer system has been quite a wrestle so far, but this week I've managed to get it running error free for the first time. I've had to devise a way of keeping the bandwidth requirements reasonable, without having to scale back the gameplay, which allows hundreds of units, and hundreds more physics based projectiles in play at once. The normal way of handling this in strategy games is to use a lockstep simulation, where only the players inputs are sent over the network, relying on the deterministic nature of the game to ensure that each player's instance of the game will remain in sync when given the exact same inputs. Unfortunately, Unity's physics system is non-deterministic. In my research I've not been able to find anyone who's succeeded in implementing a lockstep simulation in unity (although many have tried).
I've managed to come up with a solution that in my calculations should keep the bandwidth requirement below 256 Kbit/sec.
One player will be authoritative (the master), running the AI, damage system, commands etc. The other player's simulation (the client) simply provides a visual representation of the game, and relays input to the master (there will only be 2 players per game)
Each simulation will keep a register of all units. 10X per second, the master will send the position of all units in the register to the client. Because the register is an exact copy, only the positions need to be sent, without reference to the unit. The client can rely on the order of the received positions being the same as the order of the units in the register, to know which unit each position refers to. The client then uses interpolation to smooth unit movement.
Positions will be sent as 2 short (16 bit) integers, representing the X and Z co-ordinate of the unit. The gameplay area will be 256x 256 metres. Because a short integer has 65, 535 possible values, this allows the position to be represented in increments of approximately 4mm. The Y co-ordinate doesn’t need to be sent, as it can be calculated on the client by raycasting to find the ground. These optimisations mean each position sync takes only 32 bits, as opposed to 96 bits if using plain Vector3s.
Unit rotations will not be sent, but calculated on the client based on a unit's movement direction and target. Because the rotations don't have a significant effect on gameplay, it doesn't matter if they aren't exact.
Commands such as launching a projectile or applying damage will only be initiated by the master.
When a projectile is launched, the master will send the launch location and velocity to the client. The client can then mirror the projectile's simulation without needing to constantly send the projectile's position. To get around the non-determinism of Unity's physics, the projectile will not apply damage on the client. Instead, when it collides on the master, the damage message will be sent to the client. This way if the physics simulation differs on the client, it will be purely a visual difference, and won't desync the game.
When the client gives a unit a command, the command will be sent to the master to execute.
The register of units will allow up to 256 units. This limitation allows units to be referenced over the network with an 8-bit integer (byte), instead of a standard 32 bit int.
The system is working well so far. The main problems I've encountered so far have been the result of the unit registers becoming out of sync, resulting in the wrong units getting the wrong positions and commands, or commands not finding a unit to apply to. After days of work, I've managed to eliminate all of the registry desyncs in the current build. I can now run a playable co-op multiplayer game without errors, which has been a huge relief. Here's hoping I can keep it that way.
PS: Sorry for the long, boring, technical post. Pretty pictures of Joan of Arc next week :)
PPS: I'm using Photon Unity Networking