How to save/load with Google Play Services

Hi all! I-m trying yo implement the Google Play Services in my Android game.

I have been able to implement achievments without problems and are working good, but I have been some days trying to get the Saved Services working and I can´t.

The main problem is the code of the plugin page, I don´t understand how it works, only I have been able to showUI with a button but I dont pass from that point.

I need the new Saved Service, not the old one Cloud Services, thats the main problem, for the old Cloud Service are a lot of tutorial, but for the new one I can´t fine anything.

I have enable Saved Games in Google Developers Console and it is publishied more than 24 hours ago (2 days exactly).

I have enabled Saved Games in the code:

    PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
        // enables saving game progress.
        .EnableSavedGames()
        .Build();
    PlayGamesPlatform.InitializeInstance(config);

This is the code provided with the plugin:

To show the default UI:

    void ShowSelectUI() {
        int maxNumToDisplay = 5;
        bool allowCreateNew = false;
        bool allowDelete = true;

        ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;
        savedGameClient.ShowSelectSavedGameUI("Select saved game",
            maxNumToDisplay,
            allowCreateNew,
            allowDelete,
            OnSavedGameSelected);
    }


    public void OnSavedGameSelected (SelectUIStatus status, ISavedGameMetadata game) {
        if (status == SelectUIStatus.SavedGameSelected) {
            // handle selected game save
        } else {
            // handle cancel or error
        }
    }

To open save game:

    void OpenSavedGame(string filename) {
        ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;
        savedGameClient.OpenWithAutomaticConflictResolution(filename, DataSource.ReadCacheOrNetwork,
            ConflictResolutionStrategy.UseLongestPlaytime, OnSavedGameOpened);
    }

    public void OnSavedGameOpened(SavedGameRequestStatus status, ISavedGameMetadata game) {
        if (status == SavedGameRequestStatus.Success) {
            // handle reading or writing of saved game.
        } else {
            // handle error
        }
    }

Writing save game:

    void SaveGame (ISavedGameMetadata game, byte[] savedData, TimeSpan totalPlaytime) {
        ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;

        SavedGameMetadataUpdate.Builder builder = new SavedGameMetadataUpdate.Builder();
        builder = builder
            .WithUpdatedPlayedTime(totalPlaytime)
            .WithUpdatedDescription("Saved game at " + DateTime.Now());
        if (savedImage != null) {
            // This assumes that savedImage is an instance of Texture2D
            // and that you have already called a function equivalent to
            // getScreenshot() to set savedImage
            // NOTE: see sample definition of getScreenshot() method below
            byte[] pngData = savedImage.EncodeToPNG();
            builder = builder.WithUpdatedPngCoverImage(pngData);
        }
        SavedGameMetadataUpdate updatedMetadata = builder.Build();
        savedGameClient.CommitUpdate(game, updatedMetadata, savedData, OnSavedGameWritten);
    }

    public void OnSavedGameWritten (SavedGameRequestStatus status, ISavedGameMetadata game) {
        if (status == SavedGameRequestStatus.Success) {
            // handle reading or writing of saved game.
        } else {
            // handle error
        }
    }

    public Texture2D getScreenshot() {
        // Create a 2D texture that is 1024x700 pixels from which the PNG will be
        // extracted
        Texture2D screenShot = new Texture2D(1024, 700);

        // Takes the screenshot from top left hand corner of screen and maps to top
        // left hand corner of screenShot texture
        screenShot.ReadPixels(
            new Rect(0, 0, Screen.width, (Screen.width/1024)*700), 0, 0);
        return screenShot;
    }

Reading save game:

    void LoadGameData (ISavedGameMetadata game) {
        ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;
        savedGameClient.ReadBinaryData(game, OnSavedGameDataRead);
    }

    public void OnSavedGameDataRead (SavedGameRequestStatus status, byte[] data) {
        if (status == SavedGameRequestStatus.Success) {
            // handle processing the byte array data
        } else {
            // handle error
        }
    }

Thanks for your time!

For people who finds this thread in the future and have problems setting up GPG cloud save, like I had.

I’ve made the most barebones setup I can. Hopefully this will help people get started with GPG cloud save. Since this is a very light setup once it works you should tailor it to your own setup.

IMPORTANT This is Save/Load without using GPG UI. Also GPG needs to be initialized before you can use this. GPG Cloud Save needs to be turned on in Dev console and .EnableSavedGames() needs to exist in PlayGamesClientConfiguration.

This only saves a simpel string (in our case a json string with save progress) to a GPG cloud save. It overwrites the save every time so if you want multiple saves you need to implement that yourself. Comments in code should explain most.

Look at https://github.com/playgameservices/play-games-plugin-for-unity/tree/master/samples/CubicPilot/Source/Assets/CubicPilot/GameLogic for a more full setup.

This Save a game at first launch from code · Issue #384 · playgameservices/play-games-plugin-for-unity · GitHub thread helped me a bit. Cred to iqbalmineraltown’s answer. I used it in this implementation.

Also cred to @peteradamondy with the above answer. Used his his implementation for reference aswell.

#if UNITY_ANDROID
using UnityEngine;
using System;
using System.Collections.Generic;
//gpg
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using GooglePlayGames.BasicApi.SavedGame;
//for encoding
using System.Text;
//for extra save ui
using UnityEngine.SocialPlatforms;
//for text, remove
using UnityEngine.UI;

public class GPG_CloudSaveSystem {

    private static GPG_CloudSaveSystem _instance;
    public static GPG_CloudSaveSystem Instance{
        get{
            if (_instance == null) {
                _instance = new GPG_CloudSaveSystem();
            }
            return _instance;
        }
    }

    //keep track of saving or loading during callbacks.
    private bool m_saving;
    //save name. This name will work, change it if you like.
    private static string m_saveName = "game_save_name";
    //This is the saved file. Put this in seperate class with other variables for more advanced setup. Remember to change merging, toBytes and fromBytes for more advanced setup.
    private string saveString = "";

    //check with GPG (or other*) if user is authenticated. *e.g. GameCenter
    private bool Authenticated {
        get {
            return Social.Active.localUser.authenticated;
        }
    }

    //merges loaded bytearray with old save
    private void ProcessCloudData(byte[] cloudData) {
        if (cloudData == null) {
            Debug.Log("No data saved to the cloud yet...");
            return;
        }
        Debug.Log("Decoding cloud data from bytes.");
        string progress = FromBytes(cloudData);
        Debug.Log("Merging with existing game progress.");
        MergeWith(progress);
    }

    //load save from cloud
    public void LoadFromCloud(){
        Debug.Log("Loading game progress from the cloud.");
        m_saving = false;
        ((PlayGamesPlatform)Social.Active).SavedGame.OpenWithAutomaticConflictResolution(
            m_saveName, //name of file.
            DataSource.ReadCacheOrNetwork,
            ConflictResolutionStrategy.UseLongestPlaytime,
            SavedGameOpened);
    }

    //overwrites old file or saves a new one
    public void SaveToCloud() {
        if (Authenticated) {
            Debug.Log("Saving progress to the cloud... filename: " + m_saveName);
            m_saving = true;
            //save to named file
            ((PlayGamesPlatform)Social.Active).SavedGame.OpenWithAutomaticConflictResolution(
                m_saveName, //name of file. If save doesn't exist it will be created with this name
                DataSource.ReadCacheOrNetwork,
                ConflictResolutionStrategy.UseLongestPlaytime,
                SavedGameOpened);
        } else {
            Debug.Log("Not authenticated!");
        }
    }

    //save is opened, either save or load it.
    private void SavedGameOpened(SavedGameRequestStatus status, ISavedGameMetadata game) {
        //check success
        if (status == SavedGameRequestStatus.Success){
            //saving
            if (m_saving){
                //read bytes from save
                byte[] data = ToBytes();
                //create builder. here you can add play time, time created etc for UI.
                SavedGameMetadataUpdate.Builder builder = new SavedGameMetadataUpdate.Builder();
                SavedGameMetadataUpdate updatedMetadata = builder.Build();
                //saving to cloud
                ((PlayGamesPlatform)Social.Active).SavedGame.CommitUpdate(game, updatedMetadata, data, SavedGameWritten);
            //loading
            } else {
                ((PlayGamesPlatform)Social.Active).SavedGame.ReadBinaryData(game, SavedGameLoaded);
            }
        //error
        } else {
            Debug.LogWarning("Error opening game: " + status);
        }
    }

    //callback from SavedGameOpened. Check if loading result was successful or not.
    private void SavedGameLoaded(SavedGameRequestStatus status, byte[] data) {
        if (status == SavedGameRequestStatus.Success){
            Debug.Log("SaveGameLoaded, success=" + status);
            ProcessCloudData(data);
        } else {
            Debug.LogWarning("Error reading game: " + status);
        }
    }

    //callback from SavedGameOpened. Check if saving result was successful or not.
    private void SavedGameWritten(SavedGameRequestStatus status, ISavedGameMetadata game) {
        if (status == SavedGameRequestStatus.Success){
            Debug.Log("Game " + game.Description + " written");
        } else {
            Debug.LogWarning("Error saving game: " + status);
        }
    }

    //merge local save with cloud save. Here is where you change the merging betweeen cloud and local save for your setup.
    private void MergeWith(string other) {
        if (other != "") {
            saveString = other;
        } else {
            Debug.Log("Loaded save string doesn't have any content");
        }
    }

    //return saveString as bytes
    private byte[] ToBytes() {
        byte[] bytes = Encoding.UTF8.GetBytes(saveString);
        return bytes;
    }

    //take bytes as arg and return string
    private string FromBytes(byte[] bytes) {
        string decodedString = Encoding.UTF8.GetString(bytes);
        return decodedString;
    }

    // -------------------- ### Extra UI for testing ### -------------------- 

    //call this with Unity button to view all saves on GPG. Bug: Can't close GPG window for some reason without restarting.
    public void showUI() {
        // user is ILocalUser from Social.LocalUser - will work when authenticated
        ShowSaveSystemUI(Social.localUser, (SelectUIStatus status, ISavedGameMetadata game) => {
            // do whatever you need with save bundle
        });
    }
    //displays savefiles in the cloud. This will only include one savefile if the m_saveName hasn't been changed
    private void ShowSaveSystemUI(ILocalUser user, Action<SelectUIStatus, ISavedGameMetadata> callback) {
        uint maxNumToDisplay = 3;
        bool allowCreateNew = true;
        bool allowDelete = true;

        ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;

        if (savedGameClient != null) {
            savedGameClient.ShowSelectSavedGameUI(user.userName + "\u0027s saves",
                maxNumToDisplay,
                allowCreateNew,
                allowDelete,
                (SelectUIStatus status, ISavedGameMetadata saveGame) => {
                    // some error occured, just show window again
                    if (status != SelectUIStatus.SavedGameSelected) {
                        ShowSaveSystemUI(user, callback);
                        return;
                    }

                    if (callback != null)
                        callback.Invoke(status, saveGame);
                });
        } else {
            // this is usually due to incorrect APP ID
            Debug.LogError("Save Game client is null...");
        }
    }

}
#endif

I’ve been struggling with GPG in unity too. Today, I finally figured things out. SaveDataBundle should be your “save file” implementation with custom serialization/deserialization. Hope this helps :slight_smile:

using UnityEngine;
using System.Collections;
using GooglePlayGames;
using GooglePlayGames.BasicApi.SavedGame;
using UnityEngine.SocialPlatforms;
using System;
using System.Collections.Generic;

public class AndroidSaveSystem {
	
	public static Action 					OnSaveGameSelected;
	public static Action<SaveDataBundle>  	OnSaveLoaded;

	private static SaveDataBundle 			m_currentSaveBundle;
	private static ISavedGameMetadata 		m_saveBundleMetadata;


	/// <summary>
	/// Static reference to current save data. Automatically refreshed by save system.
	/// </summary>
	/// <value>The current save.</value>
	public SaveDataBundle CurrentSave
	{
		get
		{
			return m_currentSaveBundle;
		}
	}

	/// <summary>
	/// Shows the default save system UI. This allows user to select/delete saves as required.
	/// </summary>
	/// <param name="user">User.</param>
	/// <param name="callback">Invokes, when save game has been selected. Check for status for errors, or </param>
	public void ShowSaveSystemUI(ILocalUser user, Action<SelectUIStatus,ISavedGameMetadata> callback)
	{
		uint maxNumToDisplay = 3;
		bool allowCreateNew = true;
		bool allowDelete = true;

		ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;

		if(savedGameClient != null)
		{
			savedGameClient.ShowSelectSavedGameUI(user.userName + "\u0027s saves",
			                                      maxNumToDisplay,
			                                      allowCreateNew,
			                                      allowDelete,
            (SelectUIStatus status, ISavedGameMetadata saveGame) =>
            {
				// some error occured, just show window again
				if(status != SelectUIStatus.SavedGameSelected)
				{
					ShowSaveSystemUI(user,callback);
					return;
				}

				if(callback != null)
					callback.Invoke(status,saveGame);

				if(OnSaveGameSelected != null && status == SelectUIStatus.SavedGameSelected)
					OnSaveGameSelected.Invoke();
			});

		}else{
			// this is usually due to incorrect APP ID
			Debug.LogError("Save Game client is null...");
		}
	}

	/// <summary>
	/// Creates the new save. Save returned in callback is closed!. Open it before use.
	/// </summary>
	/// <param name="save">Save.</param>
	/// <param name="saveCreatedCallback">Invoked when save has been created.</param>
	private static void CreateNewSave(ISavedGameMetadata save, Action<ISavedGameMetadata> saveCreatedCallback)
	{
		ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;
		 
		SavedGameMetadataUpdate.Builder builder = new SavedGameMetadataUpdate.Builder ();
		builder = builder
			.WithUpdatedPlayedTime (save.TotalTimePlayed.Add (new TimeSpan (0, 0, (int)Time.realtimeSinceStartup)))
			.WithUpdatedDescription ("Saved at " + DateTime.Now);
		
		SavedGameMetadataUpdate updatedMetadata = builder.Build ();

		SaveDataBundle newBundle = new SaveDataBundle();
		newBundle.RegenerateSaveData(50);
		savedGameClient.CommitUpdate ( save, 
		                               updatedMetadata, 
		                               SaveDataBundle.ToByteArray (newBundle), 
		                              (SavedGameRequestStatus status,ISavedGameMetadata game)=>
		                              {
			if(status == SavedGameRequestStatus.Success)
			{
				m_saveBundleMetadata = game;
				if(saveCreatedCallback != null)
					saveCreatedCallback(game);
			}
			Debug.Log("Creating new save finished with status :" + status.ToString());
		});
	}

	/// <summary>
	/// Opens the saved game.
	/// </summary>
	/// <param name="savedGame">Saved game.</param>
	/// <param name="callback">Invoked when game has been opened</param>
	private static void OpenSavedGame(ISavedGameMetadata savedGame, Action<ISavedGameMetadata> callback)
	{
		if(savedGame == null)
			return;

		if(!savedGame.IsOpen)
		{
			ISavedGameClient saveGameClient = PlayGamesPlatform.Instance.SavedGame;

			// save name is generated only when save has not been commited yet
			saveGameClient.OpenWithAutomaticConflictResolution(
				savedGame.Filename == string.Empty ? "Save" + UnityEngine.Random.Range(1000000,9999999).ToString() : savedGame.Filename,
				DataSource.ReadCacheOrNetwork,
				ConflictResolutionStrategy.UseLongestPlaytime,
				(SavedGameRequestStatus reqStatus, ISavedGameMetadata openedGame) =>
				{
					if(reqStatus == SavedGameRequestStatus.Success)
					{
						m_saveBundleMetadata = openedGame;
						if(callback != null)
							callback.Invoke(m_saveBundleMetadata);
					}
				});
		}else{
			if(callback != null)
				callback.Invoke(savedGame);
		}
	}


	/// <summary>
	/// Loads the saved game. This should be a starting point for loading data.
	/// </summary>
	/// <param name="user">User.</param>
	/// <param name="onSaveLoadedCallback">On save loaded callback.</param>
	public void LoadSavedGame (ILocalUser user, Action<SaveDataBundle> onSaveLoadedCallback)
	{

		if(onSaveLoadedCallback != null)
			OnSaveLoaded += onSaveLoadedCallback;

		if(m_saveBundleMetadata == null)
		{
			ShowSaveSystemUI (user, LoadGame);
		}else
		{
			LoadGame(SelectUIStatus.SavedGameSelected, m_saveBundleMetadata);
		}
	}

	static void LoadGame (SelectUIStatus status, ISavedGameMetadata game)
	{

		if (status == SelectUIStatus.SavedGameSelected){

			OpenSavedGame (game, (ISavedGameMetadata openedGame) => 
			{
				if(game.Description == null || game.Description == string.Empty)
				{
					// game has not been saved on cloud before, create new file
					CreateNewSave(openedGame, (ISavedGameMetadata newlySavedGame) =>
					              {
						LoadGame(SelectUIStatus.SavedGameSelected, newlySavedGame);
					});
					return;
				}

				// save should be opened now
				Debug.Log ("Loading save from: " + openedGame.Filename + "

" + openedGame.Description + "
Opened = " + openedGame.IsOpen.ToString ());
m_saveBundleMetadata = openedGame;
ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;
savedGameClient.ReadBinaryData (openedGame, (SavedGameRequestStatus reqStatus, byte data) =>
{
Debug.Log("Reading save finished with status: " + reqStatus.ToString());

					if (reqStatus == SavedGameRequestStatus.Success){
						SaveDataBundle bundle = SaveDataBundle.FromByteArray (data);
						m_currentSaveBundle = bundle;
						if (OnSaveLoaded != null)
						{
							OnSaveLoaded.Invoke (bundle);
							OnSaveLoaded = null;
						}
					}
				});
			});
		}
	}

	public void SaveGame (SaveDataBundle file, Action<bool> callback)
	{
		CommitSaveToCloud(file,"undefined",callback);
	}


	/// <summary>
	/// Commits the save to cloud.
	/// </summary>
	/// <param name="file">Actual save file. This will replace static reference to current save file</param>
	/// <param name="fileName">File name. Used only when saving for first time</param>
	/// <param name="callback">Invoked after commit (true = success)</param>
	private static void CommitSaveToCloud (SaveDataBundle file, string fileName, System.Action<bool> callback)
	{
		ISavedGameClient savedGameClient = PlayGamesPlatform.Instance.SavedGame;

		savedGameClient.OpenWithAutomaticConflictResolution(
				m_saveBundleMetadata.Filename == string.Empty ? fileName : m_saveBundleMetadata.Filename,
				DataSource.ReadCacheOrNetwork,
				ConflictResolutionStrategy.UseLongestPlaytime,
				(SavedGameRequestStatus reqStatus, ISavedGameMetadata openedGame) =>
				{
					if(reqStatus == SavedGameRequestStatus.Success)
					{
						// adding real time since startup so we can determine longes playtime and resolve future conflicts easilly
						m_saveBundleMetadata = openedGame; 
						SavedGameMetadataUpdate.Builder builder = new SavedGameMetadataUpdate.Builder ();
						builder = builder
							.WithUpdatedPlayedTime (m_saveBundleMetadata.TotalTimePlayed.Add (new TimeSpan (0, 0, (int)Time.realtimeSinceStartup)))
							.WithUpdatedDescription ("Saved game at " + DateTime.Now);
						
						SavedGameMetadataUpdate updatedMetadata = builder.Build ();
						
						savedGameClient.CommitUpdate ( m_saveBundleMetadata,  
						                              updatedMetadata, 
						                              SaveDataBundle.ToByteArray (file),  
						                              (SavedGameRequestStatus status,ISavedGameMetadata game)=>
						                              {
								if(status == SavedGameRequestStatus.Success)
								{
									m_saveBundleMetadata = game;
									m_currentSaveBundle = file;
								}
								if(callback != null)
									callback.Invoke(status == SavedGameRequestStatus.Success);
						});
					}
			});
	}
}

hi @peteradamondy,
i’m already try your code, but in LoadGame() i can’t see the result.
please give me clues or solution. :slight_smile:

thank you :slight_smile:

SaveDataBundle SDB;
public Text text_;
//================

SDB = GameObject.Find ("MainMenuController").GetComponent<SaveDataBundle>();
//=================

// call this method form load button
	public void LoadGame()
	{		
		// user is ILocalUser from Social.LocalUser - will work when authenticated
		m_saveSystem.LoadSavedGame(Social.localUser, (SaveDataBundle saveBundle) =>
			{
				// do whatever you need with save bundle
				text_.text = "Load " + SDB.m_coins;
			} );
	}

You can try another Play game service Unity plugin

https://github.com/unity-plugins/google-play-game-service-unity-plugin/wiki/google--Play-Game-Service-unity-plugin-Tutorial

Login Google Play Game

Login Game is required before call other API.Play Game Unity Plugin support google account ,not support google plus api now.

GoogleGame.Instance().login (true, false);
GoogleGame.Instance().gameEventHandler += onGameEvent;
void onGameEvent(int result_code,string eventName,string data){
    Debug.Log (eventName + "-----------" + data);
    if(result_code==-1&&eventName==GameEvent.onConnectSuccess){
        //login success,you can do other now
    }
}

You can get player infomation after login,the data is json format string

string json=GoogleGame.Instance().getCurrentUserInfo();

If you exit your game,you can login Out and disconnect

GoogleGame.Instance().loginOut();

Play Game Leaderboard

Display Leaderboard with default UI is very easy

GoogleGame.Instance().showLeaderboards();

Submit a leaderboard score,the first param is leaderboard id,the second param is score value.

GoogleGame.Instance().submitLeaderboardScore("CgkItJ_UzNUHEAIQCQ", 1000L);

if you want to define a ui for leaderboard,you can load data,and you will get data in event GameEvent.onLeaderboardMetadataResult

GoogleGame.Instance().loadLeaderboardsMetadata(false);

Play Game Achieve

Display Achievements with default UI is easy too

GoogleGame.Instance().showAchievements();

Unlock Achievement,the param is Achievement ID

GoogleGame.Instance().unlockAchievement("CgkItJ_UzNUHEAIQBA");

If you want to define ui for Achievement,you can load data ,and handle event GameEvent.onLoadAchievementsResult

GoogleGame.Instance().loadAchievements();

Game Event and Quest

Load Game Event List,and handle Event GameEvent.onLoadEventsResult

GoogleGame.Instance().loadEvents();

Change Event Data

GoogleGame.Instance().incrementEvent("eventID",102);

Load Quest ,and handle Event GameEvent.onLoadQuestsResult.selector is in GameConst such as SELECT_COMPLETED,sortOrder is SORT_ORDER_MOST_RECENT_FIRST or SORT_ORDER_SOCIAL_AGGREGATION

GoogleGame.Instance().loadQuests(int[] questSelectors, int sortOrder, bool forceReload);

accept Quest

GoogleGame.Instance().acceptQuest(string questid);

you can listener the state change of quest by handle event GameEvent.onQuestCompleted

Game Snapshot

Display saved snapshot with default ui

GoogleGame.Instance().showSnapshots("saved games", true, true, 10);

Save Game State with google play snapshot api.open snapshot first

GoogleGame.Instance().openSnapshot("firstgamesnap", true, GameConst.RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED);

and then write snapshot after event onOpenSnapshotResult,snapshotfilePath is a image path,the second param is your game data

GoogleGame.Instance().writeSnapshot(snapshotfilePath, System.Text.Encoding.UTF8.GetBytes("{'score':20}"));

open a snapshot first and then get you saved data

GoogleGame.Instance().openSnapshot("firstgamesnap", true, GameConst.RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED);

after open success

byte[] gamedata=GoogleGame.Instance().readSnapshot();

RealTime Multiplayer Game

Create a multiplayer game room,and show waiting Room Panel

GoogleGame.Instance().createAutoMatchRoom(mincount,maxcount,mask);
GoogleGame.Instance().showRoomWaitingPanel(minParticipantsToStart);

you can accept invite to enter a room too

GoogleGame.Instance().acceptInviteToRoom(inviteid);

if the player want to leave the room

GoogleGame.Instance().leaveRoom();

There are many Event when play multiplayer game,such as onRoomCreated,onJoinedRoom,and you can handle event to play game when GameEvent.onRoomWaitingChange fired. google game support realtime message reliable or unreliable.if recipientParticipantId is null,then message will been sent to all player except sender.

GoogleGame.Instance().sendReliableMessage(byte[] messageData, string roomId, string recipientParticipantId);
//GoogleGame.Instance().sendUnreliableMessage(byte[] messageData, string roomId, string[] recipientParticipantIds);

Turnbased Multiplayer Game

Create a turnbased game room

GoogleGame.Instance().createTurnBasedMatch(minplayer, maxplayer, mask);

or you can accept a Invitation

GoogleGame.Instance().acceptTurnBasedInvitation(invitationId);

Show All turn based Matches with default UI

GoogleGame.Instance().showTurnBasedMatches();

Show TurnBased Invitations

GoogleGame.Instance().showTurnBasedInvitations( minPlayers, maxPlayers,  exclusiveBitMask, allowAutomatch);

There are many event you can handle such as onInitiateMatchResult,onUpdateMatchResult,when onTurnBasedMatchReceived received you can do game logic

when play game,you will call taketurnbasedturn to notify the next player

GoogleGame.Instance().takeTurnBasedTurn( matchId, matchData,  pendingParticipantId);

GoogleGame source

google realtimeMultiplayer api