Wizard Fu's Cocos2dx Platformer Game Engine v1.0.1

nat@wizardfu.com

See the file LICENSE for the license governing this code.

When making a game with networked multiplayer, it’s important to understand the implementation. Here’s a primer on developing multiplayer games. It all starts with the tick.

The Tick Method

A tick is one iteration of the game’s world (also the “game state”) with a fixed time step. In the game’s main loop, an accumulator is used to guarantee that each call to the tick method is a constant duration. If the game is set for 10 ticks per second, each tick is guaranteed to last 0.1 seconds.

This trick called the tick works wonders in promoting determinism — ensuring the game plays the same on any device at any speed — especially when physics are involved.

The tick is also used to keep networked players in sync. When game clients send gameplay-related packets to each other (or the server), they send the current tick along with the other data. This allows the other game clients or the server to apply the data to the correct tick.

Determinism

Determinism means that given a certain set of inputs, the game will always play out the same — deterministically. Being deterministic means that no matter the character of the device (its platform, processing speed, graphics power, etc.) the game will play the same.

Coding a game with determinism in mind from the beginning is important for realtime multiplayer games. Use only integers for shared game world math, as floating points don’t always add the same on different devices.

Use a deterministic random number generator so that even though the game uses random numbers, they are guaranteed to play out the same on two different machines given the same seed.

Ensure that all game world variables that affect determinism are part of a sharable game world that can be verified through hashing. The current random number seed and game world hash can be sent along with the current tick data to help ensure that all players are in sync.

Client-server vs Peer-to-peer

Games implement multiplayer mostly in two fashions:

  1. Client Server. A server is used to store the game world. It receives input packets from players, ticks the game world and sends updated world data back to each game client. The server is solely responsible for changing the game world, so this can deter cheating. It also makes gameplay more fair, as no single player is a designated host with essentially zero latency.

  2. Peer to Peer. A server might be used for matchmaking, but other than that the networked game clients communicate directly with each other. It’s possible to implement P2P in many different ways. Here’s a couple examples:

    • All game clients send small input packets to all other game clients every tick. In this case, one of the game clients is designated the host and has authority over the game world. This means that the host player actually has an advantage because all local input is guaranteed, whereas network input may lag and fail to arrive in time.
    • Each game client sends updated world data for the local player to all other clients every tick. In this case, there is distributed authority, however it can enable cheaters.
    • There’s also the easy path of not caring whether players are exactly in sync. Simply send input or player-centric world updates every tick and hope they arrive. This is a nice and simple way to go, especially when beginning with multiplayer development.

Latency

It currently takes a packet of data about 250 ms to travel halfway around the planet in a single direction. Travelling a few hundred miles, a packet can arrive (oneway) in a mere 50 ms. Above 400 ms for a oneway packet and the latency is getting too high for realtime play.

Realtime multiplayer games may plan for an input delay (usually measured in ticks). The local client receives the player’s local input and immediately sends it to the server (or the other game clients in the case of P2P). The input is not processed until a few ticks later, however. This gives the other clients (or server) time to receive the packet and process it on the same tick as the sending client. If there are 10 ticks per second, 100 ms per tick and an input delay of 4 ticks, that’s an overall input delay buffer of 400ms. Games will typically play sound effects or start animations during this time so it feels like the game is instantly responsive, even though the actual input may take nearly half a second to process.

The other way to think of a 400ms input delay is like showing the player the world state from 400ms ago. It’s the same concept, just thought of in a different way.

Another way to handle latency is to send input immediately, process input immediately and correct course later if a packet didn’t arrive or arrived late. Smoothing can be applied to these course corrections. However, because there is no guaranteed input delay this method can lead to glitchy gameplay when there is high latency or a high packet drop rate, as there is no period of time that course corrections can be applied without affecting gameplay as with the input delay method.

Handling lag and dropped packets

Packets can arrive late, arrive in the wrong order or not arrive at all (dropped packets). It’s important in developing a realtime multiplayer game to ensure that these cases are handled in the smoothest way possible.

Typical packet drop rates can be around 5-20%. One can even simulate packet drops (highly recommended) by randomly not processing a percentage of network data packets. A good test of a realtime multiplayer game is to set the simulated packet drop rate up to 60% or more.

Unfortunately, lag is a reality of networked multiplayer games and there’s simply no way to perfectly accomodate for it. However, there’s two ways to minimize the effects of lag: interpolation and extrapolation.

Interpolation

Interpolation is performed to smooth data between two ticks. At tick 100, player 1 might be at 10,10. At tick 101, the new position is 10,15. In between ticks 100 and 101 the position is interpolated: 10.5, 11.0, 11.5, etc.

Interpolation can also be used to smooth out corrections. If a packet arrives late, the game can rewind, apply the new data, playback the game world to the current tick and use interpolation to make it smooth.

Extrapolation

Extrapolation means the game predicts data for players given existing circumstances. A player might be moving with a certain velocity at a given tick. Even if no input is received from that player, the game extrapolates a new position for the player based on the current velocity.

Extrapolation is often used by the server in the client-server model. If using no input delay, the server can extrapolate on the input it receives from players using the measured latency. This is called lag compensation. Here’s an example: at tick 100 player 0 fires a perfect shot at player 1. This input is sent to the server and arrives 150 ms later. The server realizes that player 0 fired a shot 150 ms ago, rewinds the world a bit, replays the shot, realizes it was successful and awards a hit.

Packet acknowledgement

Data packets in networked multiplayer games may contain information on which input has been acknoledged and processed. A game client continues to send input for a given tick until it has been acknowledged. Imagine a client sending tick 100. At 102, the other client receives the data and sends back acknowledgement of tick 100 along with current tick data. The original client receives the acknowledgement at tick 104. During ticks 101, 102 and 103, the original client continues to send tick 100’s data along with the current packet because it has yet to be acknowledged. If packet number 100 fails to arrive, then perhaps 101 will successfully arrive before the input delay is up.

Nextpeer

The implemenation of networked multiplayer in this platformer game engine uses Nextpeer. Nextpeer uses the peer-to-peer method and greatly simplifies the process of matchmaking. After launching the dashboard and joining a game, Nextpeer handles all the intricacies of linking the two players together.

Once two or more players are connected, Nextpeer provides a method to send raw data between players. Here is where the Multiplayer class comes in. It defines the message types that can be sent between players, providing a protocol. This protocol is used to package up input or other player data and send it to the the other peers. The other peers then use the protocol to unpack the data and apply it to their local game world.

Nextpeer currently works on iOS and Android.

#pragma once
#include "Headers.h"

Message types

The message types used by this game. Since we are using a P2P implementation where players only send input and Nextpeer handles all the matchmaking, we only need to define a message type for sending input.

enum
{
	kMsgPlayerInput,
	kNumberOfMessageTypes
};

The Multiplayer class

class Multiplayer : public Ref
{
	public:
		Multiplayer();
		virtual ~Multiplayer();

Initialize the multiplayer object with the given appKey.

		void init(const string& appKey);

Launch the multiplayer dashboard (used to connect players into a match).

		void launchDashboard();

Called when a tournament / match is started.

		void didStartTournament(Ref* startData);

Called when a tournament / match is ended.

		void didEndTournament(Ref* endData);

Called when a packet of data arrives.

		void incomingDataPacket(Ref* packet);

Send the local player’s input to the other players.

		static void sendAllMyPlayerInput();

End the current tournament, given the winner’s player index.

		static void endTournament(int winningPlayerIndex);
};
 
h