📜 ⬆️ ⬇️

Unity3D + Google Services: multiplayer for your project on Android and iOS


In this article I want to talk about the use of Google gaming services in your application on Unity. Writing this material led me to a fairly large number of problems encountered during the development of our application, as well as the absence of any materials in Russian on this topic. And actually, in English too. The description of the plugin on the githaba is very brief and does not give an answer to possible problems with the work of the services. I think it’s not worth explaining here that multiplayer and player ratings often increase user interest, and therefore your possible profit. And thanks to this article, novice developers will be able to start using these advantages.


Plugin


We used the free Play Games For Unity plugin. It contains libraries for working with Google services. The plugin includes a Google+ user authorization, the ability to use achievements and rankings for players, Google clouds to store data and organize multiplayer both in real time and step by step. Installing the plugin does not cause any difficulties: in Unity, you need to select an asset import (Assets-> ImportPackage-> CustomPackage) and in the opened window select an asset located in the folder "current-build".
Next, you need to enter the id of your application: open the drop-down list “Google Play Games” and select the item “Android Setup”. Id applications you can find in the Google Developer Console. It is issued after adding a new application to Game Services, you can see it next to the name of your game. After adding the id, you can go directly to the code.

To initialize the plugin, use the following code:
//    using GooglePlayGames; using UnityEngine.SocialPlatforms; //   Google Play Games PlayGamesPlatform.Activate(); 

PlayGamesPlatform.Activate (); it is enough to call only once after starting your application. After initialization, you can access the platform using Social.Active.
')
To implement user authentication, use the following code:
  ... //  : Social.localUser.Authenticate((bool success) => { // ,      . }); 

The success variable takes true values ​​on successful login and false, respectively, on failure. In our case, with a successful login, the method of downloading the required user from the Google cloud is called. And immediately the first problem encountered in our path: the method is called, but if an unsuccessful attempt fails, it does not return false, and therefore the authorization fails and there is no possibility to call the method again (! Success). I had to write a crutch, which calls the method at a certain interval until authorization passes (provided that the user has confirmed the authorization request before this).
After user authorization, we get the opportunity to use Google services.

Raise multiplayer


Some of this is described on a githaba, something on developers.google. Here I collected a useful squeeze.
The plugin has 4 multiplayer modes of operation:
  1. Creating a room with random players (quick play)
  2. Creating a room with an invitation screen (allows you to invite friends from Google+ circles to the game)
  3. Invitation overview (allows you to see which friends on Google+ want to invite you to the game)
  4. Invitations by id (we will not consider it, because we did not use this mode in our application; those who are interested can read about it by reference to Github)

To make it easier to work with the following functions, your class must inherit from the RealTimeMultiplayerListener interface.

Create a "quick game" / connect

A room is created where random opponents are recruited, or an automatic connection to an already created room takes place.
 const int MinOpponents = 1, MaxOpponents = 3; const int GameVariant = 0; PlayGamesPlatform.Instance.RealTime.CreateQuickGame(MinOpponents, MaxOpponents, GameVariant, listener); 

Obviously, the minimum and maximum number of players is determined by the variables MinOpponents, MaxOpponents. In our game MaxOpponents = 1, this means that in multiplayer you will have only one opponent.
If your class is inherited from RealTimeMultiplayerListener, then instead of the listener you need to write this.

Creating a room with an invitation screen

Almost identical to the previous one. A player can invite friends from Google+ or add random opponents.
 const int MinOpponents = 1, MaxOpponents = 3; const int GameVariant = 0; PlayGamesPlatform.Instance.RealTime.CreateWithInvitationScreen(MinOpponents, MaxOpponents, GameVariant, listener); 


Invitation Overview

 PlayGamesPlatform.Instance.RealTime.AcceptFromInbox(listener); 

A menu opens in which the player can see their invitations from Google+.

Room connection

The following method allows you to show the user a load while creating or connecting to a room:
 public void OnRoomSetupProgress(float progress) { // (    0.0  100.0) } 

In our case, we simply output the progress variable.

When the connection to the room is successful (or not), the following method is called:
 public void OnRoomConnected(bool success) { if (success) { //     // …  … } else { //     // …  … } } 

In our game for each online race, obstacles are generated randomly, and accordingly their position should be the same on both phones. If the connection is successful, the host is selected from the list of participants and obstacles are generated on its phone. After they are generated, the transmission of messages with the level parameters to another phone immediately begins, and as soon as the receiving phone has loaded the last obstacle, it sends a message that it is ready and the game begins. It also transmits the sequence number used by the spacecraft so that the correct model is displayed on the screen of another player. Moreover, various service variables are transmitted that are necessary to determine the readiness of all the parameters necessary for the game.

members id

To find out the id of all participants, including yours, you can use the following code (can be executed only after connecting to the room).
  using System.Collections.Generic; List<Participant> participants = PlayGamesPlatform.Instance.RealTime.GetConnectedParticipants(); 

For all participants, the list will be sorted in the same way.
To find out your Id, use the following method:

 Participant myself = PlayGamesPlatform.Instance.RealTime.GetSelf(); Debug.Log("My participant ID is " + myself.ParticipantId); 

Posting:

The plugin supports 2 types of messages, reliable and unreliable. Reliable communication is slower, but guarantees delivery, unreliable faster, but delivery verification is not performed.
To send a reliable message, use the following code:
 byte[] message = ....; //    byte[] bool reliable = true; PlayGamesPlatform.Instance.RealTime.SendMessageToAll(reliable, message); 

Accordingly, for the unreliable one, change the variable reliable to false. The message will be sent to all participants in the room, except for you.
You can also send a message to a specific participant:
 byte[] message = ...; bool reliable = true; string participantId = ...; PlayGamesPlatform.Instance.RealTime.SendMessage(reliable, participantId, message); 

In this way, you can send a message to yourself by specifying your id in the participantId.
The maximum length of one reliable message is 1400 bytes, an unreliable 1168 bytes.
There was also a problem: even if you send one message per frame, they are not sent. We have not figured out what is the reason, probably they just do not have time to form (maybe someone will correct me in the comments). Therefore, a frame counter was made and messages were sent at a specific interval, measured in frames. Then everything began to work great. During the game, our application implies the constant sending of messages (coordinates and rotation angle of the spacecraft), therefore unreliable messages are used, because the transmission speed comes to the fore, and if a couple of packets are lost, then that's okay, if the transmission is quite frequent, notice this. But when players join a room, all messages sent with parameters of participants and their ships are reliable, since they are sent only once and their values ​​are crucial for the start of the race.

We check receipt of all necessary messages to start the game:


Finally, after dozens of builds and tests, everything began to work:


Receive messages

When you receive a message, the following method is called:
 public void OnRealTimeMessageReceived(bool isReliable, string senderId, byte[] data) { //   } 

The received message is completely identical to the sent one, both in length and in content.
Since our game uses many different messages (whether the coordinates of the ship, or a message about winning one of the participants, etc.), in order to understand what message was received, we went to a rather simple and obvious step: the first byte was a number defining what kind of message and what method to call when it is received, and the transmitted data started from the second byte.

Connection events

If the user is disconnected from the room, the following method is called:
 public void OnLeftRoom() { //         //    PlayGamesPlatform.Instance.RealTime.LeaveRoom() //      } 

Important: When you minimize the game, the player disconnects from the room. Perhaps for some applications this will be a problem. But in our case, folding would inevitably lead to defeat, in view of the specifics of the game. Therefore, when one of the participants is disconnected from the room, the OnPeersDisconnected () method, described later, is called.

If someone connects or disconnects from the room, the following methods will be called.
 public void OnPeersConnected(string[] participantIds) { //      } public void OnPeersDisconnected(string[] participantIds) { //     } 

Participants can connect at any time during the game, if there are empty slots, and here it is necessary to ensure that the participant does not connect after the game has already started. You can make the wait until all the slots are full and only then start the game.
In our game, with early exit, one of the participants sends a message that determines the opponent's victory and the game ends, and therefore the following method should be called.

Exit the game

After your game is over, you need to leave the room:
 PlayGamesPlatform.Instance.RealTime.LeaveRoom(); 

After this, OnLeftRoom () will be called.

Conclusion


I hope our article will be useful for both beginners and more experienced Unity developers who have not met with the organization of multiplayer in their projects. If there is interest, I’ll write a continuation about using this plugin to work with the Google cloud, which also had difficulties in developing, and, of course, there were no answers in English / Russian.

Source: https://habr.com/ru/post/230377/


All Articles