Earlier, I already wrote about my experience in developing a mobile
word game on Android and iOS, which enjoys a certain popularity, and I decided to tie the multiplayer mode to it, when two players compete with each other, making up words one by one as the final round of the TV show Sergei Suponev Star. hour".

It took me a month and a half to study and implement multiplayer, in the article I will try to describe the concept without source code examples, making a squeeze from the amount of work done.
A bit of history
The application was written in C ++ using the Marmalade SDK. Since then, the vendor has stopped supporting this platform, selling sorts to the Japanese, and the future of this development environment has become very vague.
')

The question arose of what to port the current projects for their further support.
Why not cocos2d-x
Cocos2d-x is one of the most common engines for developing cross-platform mobile games in C ++. Apparently, due to its free and open source. The engine is poorly documented. The description covers a scanty part of the engine and most of the material is long outdated.
According to the results of a certain period, I still managed to create a prototype of my application. But the impressions were very bad: the feeling that cocos2d-x is assembled on the knee. The levels of abstraction Scene, Sprite, Application Delegate seemed to me very inconvenient, and the need to look for answers to questions on the coconut forum more often lead you to think that you are doing something wrong. Probably my hands are not growing from that place.
My choice fell on SDL
SDL , as well as Marmalade SDL, is not an engine, it is a platform. It provides a low-level API, from which I then build convenient levels of abstraction for me. All this is written in C, the source code is open.
In a nutshell, SDL is a free cross-platform library for working with graphics, sound, and processing messages from the operating system. It is very convenient to do a win32 build and debug the game logic on Windows, leaving only OS debugging for mobile emulators and physical devices.
Fortunately or unfortunately, the SDL does not provide tools for such a narrow task as developing multiplayer for iOS and Android, so I had to integrate with the corresponding services myself.
Multithreaded application architecture
The application logic and all the work with graphics is implemented in the main thread, which is the message processing cycle and starts in the main function. Let's call this thread SDL Thread. In turn, other threads throw events (SDL_PushEvent) to it for processing into a queue, and that one reads them from it using SDL_WaitEvent and SDL_PollEvent. These are either system events thrown by the system and whose support is already implemented in the SDL, or calls to Callbacks and Listeners that we implement above SDL functionality.

The whole logic of the game is written in C ++. The project directory contains a set of * .cpp files that can be divided into three groups:
- cross-platform - those files that are included in the assembly of all platforms (the logic of the game);
- monoplatform, i.e. included in the application of a single platform for the implementation of its features.
Accordingly, there are three separate directories for the project of each platform:
- proj.win32 - project VS2017 Community Edition;
- proj.android - Android project using Gradle;
- proj.ios - Xcode project for iOS.
Integration with multiplayer services
Now we need to paste a separate layer, which will be responsible for such functionality as:
- search for an opponent, connect to the game;
- messaging between rivals;
- exit from the game room;
- fixing player points in Leaderboards.
Both iOS and Android platforms support Real-time Multiplayer (RTMP). In the case of Android, we integrate with Google Play Services (GPS), in the case of iOS - Game Center. Previously, Google supported and integration with iOS, but this year decided to abandon it.
In this article I will not describe the actions that need to be performed in the Google Play Console and AppStoreConnect to configure multiplayer, I will not describe the specification of classes and integration methods - all this is described on vendor sites.
Next, I briefly describe what changes need to be made in the project for each of the platforms.
Android
How? I have not said this yet? To compile C ++ code,
Android NDK is used . Although, if you are an Android developer, then you already know.
General instructions for integrating Google Play Services into an Android project are described on the website for Android developers. In my project I use the following dependencies:
implementation 'com.google.android.gms:play-services-games:16.0.0' implementation 'com.google.android.gms:play-services-nearby:16.0.0' implementation 'com.google.android.gms:play-services-auth:16.0.1'
Initially, the idea was to use
C ++ api , which comes in the form of compiled static libraries without source codes. Due to the fact that there is no build for the x86_64 platform in the list of libraries, I decided that the guys from Google didn’t really follow the relevance of this SDK and decided to
invent my bike to write this layer in Java, wrapping it with
JNI wrappers. And then, why do I need an extra dependency in the form of a lib without source code, which is still jerking Java inside itself? In addition to the relevance of Java classes, it will also be necessary to monitor the relevance of these libs.
As a guide used a good example from
Google Samples . Thank you google for this. Apple, take a cue from Google!
iOS
To integrate with the Game Center you need to connect the GameKit framework. We describe the entire layer of integration with the Game Center in one * .m-file and provide the interface to it through a separate * .h file. Since C ++ is a subset of the language objective-C, then with the assembly of * .cpp and * .m files in one project there will be no problems.
In addition to the
official documentation, I guided this project:
GameCenterManager . True, some things from the example are already outdated, XCode 10 will indicate this to you and you will replace the outdated functionality with a new one.
The principle of working with a multiplayer layer
Single entry point
Having studied the features of working with multiplayer on both platforms, I created a single C ++ asbestos for my application and, at the time of compilation, the corresponding implementation is “attached” to it depending on the specific platform. That is, my application does not know about any Google Play Services, Game Center and their features. It knows only the C ++ api provided to it, where, for example, there are such methods as:
SignIn()
Search for an opponent
A player can invite a friend from his list of contacts, or start a game with a random opponent. The player who received the invitation can accept it, or reject it. For all these scenarios, I use the standard interface of the service used. It should be noted that google muzzles look much nicer than iOS-ovsky. Maybe someday my hands will get there and I will write my interface with dominoes and young ladies.
Connect to the game room
When two players have connected to the virtual gaming room, they receive the appropriate callback. Now you need to choose who will be the host.
Host selection
Among the players, you need to select a host so that it determines the initial state of the game.
Consider possible ways to route messages between players in the general case. Please note that in the second version the host also has the role of the router.

Since I always have only two players in the game, it turns out that I have a special case of peer-to-peer connections. And therefore, only the definition of the initial state drops to the role of the host, namely, the choice of the word from which the words will be composed.
So, after the players connected to the game room, each of the players knows the list of identifiers of the participants in the game that has begun. We will call it a list of
participantID . A participantID is a kind of unique string identifier for a participant in a game, which is assigned by the service. It is necessary to choose which of them will be the host, bring it to the host itself and inform the other that its opponent is selected as the host. How to do it?
Choosing a host on Android
In the Google dock, I did not find tips on choosing a host. Silent, partisans. But kind people at
stackoverflow.com threw a link to the
video , which explains in detail the following principle:
- each participant sorts the participantID list (ascending or descending - it does not matter, as long as everything is done in the same order);
- each participant compares his participantID with the first participantID from the list;
- if they match, then the current player is given the right to choose who will be the host. He
throws a coin jerks random (), thereby selecting a host from existing members, and informs everyone who is a host.
IOS Host Selection
For iOS, there is a method
selectBestHostPlayerWithCompletionHandler , which greatly simplifies the scenario of choosing a host compared to what I described for Android. But, judging by the noticeable delays during the call of this method, it evaluates the parameters of the network response, measures the ping and, on the basis of these statistics, decides who will be the host. It is more suitable for the above client-server architecture, where the host serves as a router. In my own variant of a private peer-to-peer connection, this does not make sense, and in order to save time, I use a principle similar to what I did for Android.
Messaging between players
What is a message? The message is an array of bytes.
- In java, this is the type:
byte[]
- in objective-C it is:
NSData *
- in C ++, I drop everything above
std::vector<Uint8>
There are 2 types of sending messages:
- Reliable - guaranteed delivery through the queue. Used to deliver critical messages.
- Unreliable - unwarranted delivery. Used messages, the delivery of which can be neglected.
Unreliable is usually delivered faster than Reliable. More information can be found on the vendors website:
How will we use this array? Very simple:
- in the first byte we will write the message type.
- if the message has any parameters, we will put them in the next bytes. For each type of message that has an additional. parameters, we implement our function of serialization and deserialization.
- We’ll put a checksum at the end of the message to check the integrity.
So, we define the
enum with the types of messages that players will exchange with each other during the game:
- I am selected as a host. I pass the initial state. Now my turn (parameters: the version number of the messaging protocol, the original word);
- You are the selected host. I am waiting for you to start;
- I open (call) a word. Now it's your turn (parameter: named word);
- I give up. You won;
- I could not make a word during the course. You won;
- I agree to a rematch;
- I am leaving the game;
- Error parsing message. Disconnect;
- Your version of the messaging protocol is outdated. Check the application update. Disconnect;
- My version of the messaging protocol is outdated. Need to check the update. Disconnect;
- Ping (system message);
When an application receives an incoming message from an opponent, the corresponding Callback is called, which in turn sends it to the main SDL Thread for processing.
Connection monitoring
Gaming services (like Google’s, Apple’s) have listener functions that, in one form or another, are designed to alert us to breaking the connection with an opponent. But, I noticed that if one of the players is disconnected from the Internet, the second one does not immediately recognize that the first one has disconnected and there is no one to play with. Callbacks are not called in such cases, or they are called after a rather long time. So that in this case the second player would not have to wait for the cancer on the mountain to whistle, I had to do my own monitoring of the compound, working on the principle:
- Each player sends a ping message to the opponent every second;
- Each player checks: if there was no message from the opponent for more than 5 seconds, then the connection is lost, we exit the game.
Result
As a result of the work done, I got a game that I play with my friends and family. I play both on iOS, and on Android.
True, there is a nuance on iOS - for some reason, points in Leaderboards are not fixed, which I am currently in correspondence with Apple support.
I hope this article will be useful as the members of my team, and those who are interested in developing mobile applications. Thanks for attention.