Integrating Google Tables in Unity

Even developing a simple game sooner or later you will have to deal with data management. It might be game objects parameters (units, weapons, skills), game economics, localization or any other data.

Electronic tables can come in handy when solving this kind of problems. They are really good for data visualization. Using formulas and sorting will help being precise when calculating game economics, game loops and levels. One of the Google Tables advantages is that a group of people can work with the service online at the same time. That speeds up the development process and creates both reliable and clear communication system for the team members: programmers, artists, game designers. 

In this article I want to tell you, how I benefited from using Google Tables. On top of that I’ll list the problems I faced, when preparing the game for Google Tables integration. I will also share a little tutorial about integrating Google Spreadsheets in a Unity project. 

In our game we used Google Tables for: 1) storing and arrangement of data about characters color schemes 2) calculating their price in the in-game currency; 3) building the task system for players. Being in charge of the programming part of the app, I saved a lot of time by using Google Tables, particularly when dealing with the graphic data and working with the designer. 

You might have noticed that different color schemes spiced this pretty simple game up, this made the game a bit more inviting and interesting. It wouldn’t be possible without data arrangement.

Formulas and sorting helped calculating all parameters of the game challenges: their order, aims and rewards. It’s very important to properly build the economics system in order to keep users in the game. The Tables make data alteration fast and easy, which acts in our favor here.

Let’s switch to the technical side of integrating Google Tables in Unity. Unity game engine provides users with plenty of ways to interact with web-resources, one of them is integrating Google Tables with a standalone application. One of the biggest challenges for you might be . usage of .NET 2.0.

Starting on 05/05/2015 Google stopped supporting deprecated authorization protocol OAuth 1.0, therefore all the apps that hadn’t switched to the new OAuth 2.0 stopped working. Developers of the free plugins I used to use (Unity-GData and its updated version Unity-Quicksheet) hadn’t taken care of this problem, so I had to find a solution on my own. 
At first I thought that there should be no problems with authorization in Unity using C#, because the documentation about it is exhaustive. It turned out to be not that easy 
Firstly, there were troubles with the very authorization process, because some of the parameters of OAuth 2.0 request weren’t mentioned in the Google Spreadsheets documentation (e.g. “Access Type” and “Token Type”).
Secondly, some of the updated dll-libraries for Google Data API, that I had imported from Google Data API SDK for Mono, caused compilation errors in Unity. I faced the same problems with the old dlls from Unity-GData plugin. All I could do is try and combine them in a way they would work. 
Thirdly, I had to find a matching Newtonsoft library to work with JSON queries.

Let’s start. The process of integrating Google Tables in Unity can be split into a few steps: 

  •   Customizing Google account.
  •   Customizing access to Google Drive API.
  •   Receiving and saving data from Google Tables.

For starters let’s download the unitypackage, that already has all required libraries and scripts

Customizing Google account

  •   Log in to your account.
  •   Create new Project.
  •   On the side panel of the developer console select “APIs” tab from the “APIs & Auth” section.
  •   Click “Drive API” from the “Google Apps Api” group and add this API to the app by clicking “Enable API”. 
  •   At “APIs & Auth” click “OAuth Consent screen”. Enter your product name, and optionally add the home page address, logo etc.
  •   At “APIs & Auth” click “Credentials” tab. Click “Create client ID” in order to create a new Client ID to work with Auth 2.0. Check the “Installed application” in the “Application Type” field and “Other” in the “Installed application type” field. Finally click “Create Client ID”.
  •   Now we got the “Client ID” and “Client Secret” values, we’re gonna need them later on.
     

Customizing access to Google Drive API 

  •   After you imported the unitypackage, “SpreadsheetEntity.cs” script is supposed to automatically start its work on Unity launch. Now open the script in the editor and enter your authorization info. 
  •   Assign Client ID and Client Secret values (the ones you got from the developer console) to the _ClientId and _ClientSecret variables. 
  •   Open Unity project. It automatically follows the link, where you can get Access Code to make your app successfully authorize with Google. 
  •   Copy Access Code and assign its value to the _AccessCode variable in the “SpreadsheetEntity.cs” script. 
  •   Once you’ve done that, run Unity project and the console log is going to print values of OAuth 2.0 Access Token and Refresh Token, that we’ll need for the constant access to the Google Tables. Assign the values to the _AccessToken and _RefreshToken 

Let me give you a script example:
 

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using Google.GData.Client;
using Google.GData.Spreadsheets;

[InitializeOnLoad]
public class SpreadsheetEntity : MonoBehaviour 
{
	// enter Client ID and Client Secret values
	const string _ClientId = "";
	const string _ClientSecret = "";
	// enter Access Code after getting it from auth url
	const string _AccessCode = "";
	// enter Auth 2.0 Refresh Token and AccessToken after succesfully authorizing with Access Code
	const string _RefreshToken = "";
	const string _AccessToken = "";

	const string _SpreadsheetName = "";


	static SpreadsheetsService service;

	
	public static GOAuth2RequestFactory RefreshAuthenticate() 
	{
		OAuth2Parameters parameters = new OAuth2Parameters()
		{
			RefreshToken = _RefreshToken,
			AccessToken = _AccessToken,
			ClientId = _ClientId,
			ClientSecret = _ClientSecret,
			Scope = "https://www.googleapis.com/auth/drive https://spreadsheets.google.com/feeds",
			AccessType = "offline",
			TokenType = "refresh"
		};
		string authUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
		return new GOAuth2RequestFactory("spreadsheet", "MySpreadsheetIntegration-v1", parameters);
	}

	static void Auth()
	{
		GOAuth2RequestFactory requestFactory = RefreshAuthenticate();
		
		service = new SpreadsheetsService("MySpreadsheetIntegration-v1");  
		service.RequestFactory = requestFactory;
	}
	

	// Use this for initialization
	static SpreadsheetEntity(){
		if (_RefreshToken == "" && _AccessToken == "")
		{
			Init();
			return;
		}
		
		Auth();
		
		Google.GData.Spreadsheets.SpreadsheetQuery query = new Google.GData.Spreadsheets.SpreadsheetQuery();
		
		// Make a request to the API and get all spreadsheets.
		SpreadsheetFeed feed = service.Query(query);
		
		if (feed.Entries.Count == 0)
		{
			Debug.Log("There are no spreadsheets in your docs.");
			return;
		}
		
		AccessSpreadsheet(feed);
	}

	// access spreadsheet data
	static void AccessSpreadsheet(SpreadsheetFeed feed)
	{

		string name = _SpreadsheetName;
		SpreadsheetEntry spreadsheet = null;

		foreach (AtomEntry sf in feed.Entries)
		{
			if (sf.Title.Text == name)
			{
				spreadsheet = (SpreadsheetEntry)sf;
			}
		}

		if (spreadsheet == null)
		{
			Debug.Log("There is no such spreadsheet with such title in your docs.");
			return;
		}

		
		// Get the first worksheet of the first spreadsheet.
		WorksheetFeed wsFeed = spreadsheet.Worksheets;
		WorksheetEntry worksheet = (WorksheetEntry)wsFeed.Entries[0];
		
		// Define the URL to request the list feed of the worksheet.
		AtomLink listFeedLink = worksheet.Links.FindService(GDataSpreadsheetsNameTable.ListRel, null);
		
		// Fetch the list feed of the worksheet.
		ListQuery listQuery = new ListQuery(listFeedLink.HRef.ToString());
		ListFeed listFeed = service.Query(listQuery);


		foreach (ListEntry row in listFeed.Entries)
		{
			//access spreadsheet data here
		}


	}
	
	static void Init()
	{
		
		////////////////////////////////////////////////////////////////////////////
		// STEP 1: Configure how to perform OAuth 2.0
		////////////////////////////////////////////////////////////////////////////

		if (_ClientId == "" && _ClientSecret == "")
		{
			Debug.Log("Please paste Client ID and Client Secret");
			return;
		}

		string CLIENT_ID = _ClientId;

		string CLIENT_SECRET = _ClientSecret;

		string SCOPE = "https://www.googleapis.com/auth/drive https://spreadsheets.google.com/feeds https://docs.google.com/feeds";

		string REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
		
		string TOKEN_TYPE = "refresh";
		
		////////////////////////////////////////////////////////////////////////////
		// STEP 2: Set up the OAuth 2.0 object
		////////////////////////////////////////////////////////////////////////////
		
		// OAuth2Parameters holds all the parameters related to OAuth 2.0.
		OAuth2Parameters parameters = new OAuth2Parameters();

		parameters.ClientId = CLIENT_ID;

		parameters.ClientSecret = CLIENT_SECRET;

		parameters.RedirectUri = REDIRECT_URI;
		
		////////////////////////////////////////////////////////////////////////////
		// STEP 3: Get the Authorization URL
		////////////////////////////////////////////////////////////////////////////

		parameters.Scope = SCOPE;
		
		parameters.AccessType = "offline"; // IMPORTANT and was missing in the original
		
		parameters.TokenType = TOKEN_TYPE; // IMPORTANT and was missing in the original

		// Authorization url.

		string authorizationUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
		Debug.Log(authorizationUrl);
		Debug.Log("Please visit the URL above to authorize your OAuth "
		          + "request token.  Once that is complete, type in your access code to "
		          + "continue...");

		parameters.AccessCode = _AccessCode;

		if (parameters.AccessCode == "")
		{
			Application.OpenURL(authorizationUrl);
			return;
		}
		////////////////////////////////////////////////////////////////////////////
		// STEP 4: Get the Access Token
		////////////////////////////////////////////////////////////////////////////

		OAuthUtil.GetAccessToken(parameters);
		string accessToken = parameters.AccessToken;
		string refreshToken = parameters.RefreshToken;
		Debug.Log("OAuth Access Token: " + accessToken + "\n");
		Debug.Log("OAuth Refresh Token: " + refreshToken + "\n");
	
	}
	
}

Here’s the thing: this script is executed every time Unity editor is launched, which is very convenient, because all the data will be loaded before the main game code execution, for more details follow the link.

After you’ve done all the steps, the project should be ready to work with Google Tables. 

Receiving and storing data from Google Tables 

Google documentation describes the whole process of working with Tables really well. I will provide you with an example to make it clear. I use list-based feeds for getting data and XML-files for its storing. For more details on XML and Unity, check out the article.
Code example
 

// modified AccessSpreadsheet Method
void AccessSpreadsheet(SpreadsheetFeed feed)
        {
 
                string name = _SpreadsheetName;
                SpreadsheetEntry spreadsheet = null;
 
                foreach (AtomEntry sf in feed.Entries)
                {
                        if (sf.Title.Text == name)
                        {
                                spreadsheet = (SpreadsheetEntry)sf;
                        }
                }
 
                if (spreadsheet == null)
                {
                        Debug.Log("There is no such spreadsheet with such title in your docs.");
                        return;
                }
 
               
                // Get the first worksheet of the first spreadsheet.
                WorksheetFeed wsFeed = spreadsheet.Worksheets;
                WorksheetEntry worksheet = (WorksheetEntry)wsFeed.Entries[0];
               
                // Define the URL to request the list feed of the worksheet.
                AtomLink listFeedLink = worksheet.Links.FindService(GDataSpreadsheetsNameTable.ListRel, null);
               
                // Fetch the list feed of the worksheet.
                ListQuery listQuery = new ListQuery(listFeedLink.HRef.ToString());
                ListFeed listFeed = service.Query(listQuery);
               
                //create list to add dynamic data
                List<TestEntity> testEntities = new List<TestEntity>();
 
                foreach (ListEntry row in listFeed.Entries)
                {
                        TestEntity entity = new TestEntity();
                        entity.name = row.Elements[0].Value;
                        entity.number = int.Parse(row.Elements[1].Value); //use Parse method to get int value
                        Debug.Log("Element: " + entity.name + ", " + entity.number.ToString());
                        testEntities.Add(entity);
                }
 
                TestContainer container = new TestContainer(testEntities.ToArray());
 
                container.Save("test.xml");
 
        }
 
// classes for xml serialization
public class TestEntity {
 
        public string name;
        public int number;
       
        public TestEntity(){
                name = "default";
                number = 0;
        }
}
 
[XmlRoot("TestCollection")]
public class TestContainer {
        [XmlArray("TestEntities")]
        [XmlArrayItem("testEntity")]
        public TestEntity[] testEntities;// = new skinEntity[];
       
        public TestContainer(){
        }
       
        public TestContainer(TestEntity[] arch){
                testEntities = arch;
        }
       
        public void Save(string path)
        {
                var serializer = new XmlSerializer(typeof(TestContainer));
                using(var stream = new FileStream(path, FileMode.Create))
                {
                        serializer.Serialize(stream, this);
                }
        }
       
        public static TestContainer Load(string path)
        {
                var serializer = new XmlSerializer(typeof(TestContainer));
                using(var stream = new FileStream(path, FileMode.Open))
                {
                        return serializer.Deserialize(stream) as TestContainer;
                }
        }
}

I should mention that the script only works in the editor, you can’t use Google Tables on devices (at least with the plugins version I found. This is due to a problem in compatibility of certain libs parts with some of the platforms). The libraries from the unitypackage won’t compile for any platform but the Editor. If you do need to use Tables on your device, in Unity 5 you can select a platform, your plugin should support using Plugin Inspector; in older versions of Unity you’ll need to move the plugin to a certain folder, for more details on that go here

To sum it up, electronic tables is an essential instrument when developing games. They help optimizing routine and putting in order interaction of the whole team, visualizing huge arrays of data and using math to process them. Integrating Google Tables in Unity is a simple process, but it still requires a little knowledge and effort. 

Here’s the link for the project’s GitHub. 

I hope the article turned out to be useful for you.

 

All data posted on the site represents accessible information that can be browsed and downloaded for free from the web.

http://habrahabr.ru/post/262901/

 

User replies

No replies yet