Sign In Register

How can we help you today?

Start a new topic
Answered

Simple Storage and Retrieval Explanation

I'm sure that I'm missing something that should be patently obvious, but after several hours of headscratching whilst reading over documentation, tutorials, and a variety of forum posts, I'm still at a loss for how to retrieve custom player variables. From all I've read, it would appear that I need to be making use of the Cloud Code section of the GameSparks interface, but so far I've only been able to use it to alter the values I wish to address, and I have yet to find how to simply access those values without changing them. Any advice would be greatly appreciated.

For reference, here is a quick example of the code I'm using to find the number of lives the player currently has. I have an Event configured with the shortcode of postLives, containing an attribute with the shortcode lives, set to be a Number with a default value of 5 and a default calculation of Count. In the Cloud Code for the event, I have the following code snippet:
Spark.getPlayer().setScriptData("lives", Spark.getData().lives);

 Then, on the Unity side, I'm using the following code:

int livesTemp = 3;
new LogEventRequest().SetEventKey("postLives").SetEventAttribute("lives", livesTemp).Send((getLives) =>
 {
	if(getLives.HasErrors)
	{
		Debug.Log("Failed to retrieve player lives from GameSparks! (GameSparksLogin)");
	}
	else
	{
	       Brains3Manager.instance.lives = (int)getLives.ScriptData.GetObject("postLives").GetNumber("lives");
	}
});

 This, of course, simply changes the value of "lives" from the default value of 5 to that of livesTemp, which is 3. Since I've gotten the hang of altering the values of these variables, what I need to know now is how I would go about simply retrieving the stored values without altering them.


Again, I'm sure that this should be a supremely simple matter and I'm positive that I'm just missing some vital piece of the puzzle. In all fairness, I've been awake far longer than is probably sane & that may well have something to do with it. That aside, I've been working very hard on converting my game from using Parse to GameSparks and really don't wish to be balked by such a minor misstep. Any advice would be greatly appreciated.


Best Answer

Hi Kenneth,


ScriptData can be a bit confusing... I can see in your code that you only have "Spark.getPlayer().getScriptData("lives");"


Which is correct, getScriptData will just cache whatever scriptData you are getting into a variable or access it to be used for something. To have it return in the response. You need to use something like:


 

var lives = Spark.getPlayer().getScriptData("lives");

Spark.setScriptData("lives", lives);

 

The reasoning for that is:


Certain objects, like a player or challenge have their own scriptData that you can set information on and this will get stored as part the document in the collection.


However, requests and responses also have their own scriptData that allows you to set information to those as well.


You must chose what information to be surfaced in the request or response, this lets you run complex algorithms in cloud code and have only the pertinent information return.


So after modifying your code to that above you should see:


 

{
 "@class": ".LogEventResponse",
 "scriptData": "lives" : 3
}
{
 "@class": ".LogEventRequest",
 "eventKey": "postLives"
}

 

Or something similar.


Shane


I think the method from the other post is likely overkill for my simpler needs, haha ;) Still, something to keep in mind should I work on something more complex in the future.

 

Hi Kenneth,


I've added you to an example I've set up, I've named the events close to what you've described you want to do and commented much of what is there.


Places of particular note:


Cloud Code:


Responses -> Authentication Response

Responses -> Registration Response


Everything in the Events category


System -> Every Minute


I've also created an Event called "Post Score To Leaderboard", which has no Cloud Code associated with it, however it does get called from the Cloud Code event "Set Level Progression" since we are already making use of the data being passed up.


I've also created a partitioned leaderboard which should separate scores by level neatly.


In NoSQL you'll see the "playerLevelProgression" collection, this is where we'll store that information, in a format like:

 

{
 "_id": {
  "$oid": "55b80146845fb88eba4de591"
 },
 "playerId": "55b7e754e4b025d8e0f4eebc",
 "levels": [
  {
   "levelCode": "level1",
   "score": 50,
   "stars": 3,
   "timeStamp": 1438122308994
  },
{
   "levelCode": "level2",
   "score": 25,
   "stars": 2,
   "timeStamp": 1438122308994
  }
 ]
}

 

This can be modified easily to take more parameters or less.


Here is a Unity project connected to this game: Click here to download the Unity Project


But the relevant code is in Examples.cs:

 

using UnityEngine;
using GameSparks.Api.Requests;
using GameSparks.Api.Responses;
using GameSparks.Core;
using System.Collections.Generic;

public class Examples : MonoBehaviour
{

	public bool loggedIn = false;

	public void Awake()
	{
		//Subscribe to the callbacks letting us know of changes in our GameSparks Session
		GameSparks.Core.GS.GameSparksAvailable += OnGameSparksAvailable;
		GameSparks.Core.GS.GameSparksAuthenticated += OnGameSparksAuthenticated;
	}

	#region ListenerFunctions
	public void OnGameSparksAvailable(bool available)
	{
		if (available && !loggedIn)
		{
			Authenticate("shane", "password");
		}

		if (!available && loggedIn)
		{
			//We've managed to log in, however we've lost connection to GameSparks due to a bad network
			//Show a message along the lines of "attempting to reconnect to server"
		}

	}

	public void OnGameSparksAuthenticated(string authenticated)
	{

	}
	#endregion

	#region AuthenticationAndRegistration
	public void Authenticate(string userName, string password)
	{
		//If successful, will trigger the cloud code in Authentication Response
		new AuthenticationRequest().SetUserName(userName).SetPassword(password).Send((authResponse) =>
		{
			if (authResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				//It worked!
				//Lets get our full account details which will include currency, lives and itemCount
				new AccountDetailsRequest().Send((accDetailsResponse) =>
				{
					if (accDetailsResponse.HasErrors)
					{
					//It didn't work
				}
					else
					{
						Debug.Log("Player Currency: " + accDetailsResponse.Currency1.ToString());
						Debug.Log("Player Lives: " + accDetailsResponse.ScriptData.GetNumber("lives").ToString());
						Debug.Log("Player itemCount: " + accDetailsResponse.ScriptData.GetNumber("itemCount").ToString());
					}
				});

				//Lets test all our functions
				TestAll();

				loggedIn = true;

            }
		});
	}

	//If successful, will trigger the cloud code in Authentication Response
	public void DeviceAuthenticate()
	{
		new DeviceAuthenticationRequest().Send((authResponse) => {
			if (authResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				//It worked!
			}
		});
	}

	//If successful, will trigger the cloud code in Registration Response
	public void Register(string userName, string password)
	{
		new RegistrationRequest().SetUserName("shane").SetPassword("password").SetDisplayName("shane").Send((regResponse) => {
			if (regResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				//It worked!
			}
		});
	}
	#endregion

	#region EventsWithCloudCode

	public void GetItemCount()
	{
		new LogEventRequest().SetEventKey("getItemCount").Send((itemCountResponse) =>
		{
			if (itemCountResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				Debug.Log("GetItemCount(): itemCount: " + itemCountResponse.ScriptData.GetNumber("itemCount"));
			}
		});
	}

	public void SetItemCount(int amount) {
		new LogEventRequest().SetEventKey("setItemCount").SetEventAttribute("amount", amount).Send((itemCountResponse) =>
		{
			if (itemCountResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				Debug.Log("SetItemCount(): itemCount: " + itemCountResponse.ScriptData.GetNumber("itemCount"));
			}
		});
	}

	public void GetLevelProgression() {
		new LogEventRequest().SetEventKey("getLevelProgression").Send((levelProgResponse) =>
		{
			if (levelProgResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				foreach (object level in levelProgResponse.ScriptData.GetObject("playersProgression").GetObjectList("levels"))
				{
					Debug.Log("GetLevelProgression(): Level: " + new GSData((IDictionary<string, object>)level).GetString("levelCode") +
						" Score: " + new GSData((IDictionary<string, object>)level).GetNumber("score").ToString() +
						" Stars: " + new GSData((IDictionary<string, object>)level).GetNumber("stars").ToString());
				}
			}
		});
	}

	public void GetSpecificLevelProgression(string level) {
		new LogEventRequest().SetEventKey("getLevelProgression").SetEventAttribute("level", level).Send((levelProgResponse) =>
		{
			if (levelProgResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				Debug.Log("GetSpecificLevelProgression(): level: " + levelProgResponse.ScriptData.GetObject("playersProgression").GetString("levelCode"));
				Debug.Log("GetSpecificLevelProgression(): score: " + levelProgResponse.ScriptData.GetObject("playersProgression").GetNumber("score"));
				Debug.Log("GetSpecificLevelProgression(): stars: " + levelProgResponse.ScriptData.GetObject("playersProgression").GetNumber("stars"));
			}
		});
	}

	public void SetLevelProgression(string levelCode, int score, int stars)
	{
		new LogEventRequest().SetEventKey("setLevelProgression")
			.SetEventAttribute("level", levelCode)
			.SetEventAttribute("score", score)
			.SetEventAttribute("stars", stars)
			.Send((levelProgResponse) =>
		{
			if (levelProgResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				foreach (object level in levelProgResponse.ScriptData.GetObject("playersProgression").GetObjectList("levels"))
				{
					Debug.Log("SetLevelProgression(): Level: " + new GSData((IDictionary<string, object>)level).GetString("levelCode") +
						" Score: " + new GSData((IDictionary<string, object>)level).GetNumber("score").ToString() +
						" Stars: " + new GSData((IDictionary<string, object>)level).GetNumber("stars").ToString());
				}
			}
		});
	}

	public void SetPlayersCurrency(int amount)
	{
		new LogEventRequest().SetEventKey("setPlayersCurrency").SetEventAttribute("amount", amount).Send((setCurrencyResponse) =>
		{
			if (setCurrencyResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				Debug.Log("SetPlayersCurrency(): Currency: " + setCurrencyResponse.ScriptData.GetNumber("playersCurrency"));
			}
		});
	}

	public void SubtractALife()
	{
		new LogEventRequest().SetEventKey("subTractALife").Send((subtractLifeResponse) =>
		{
			if (subtractLifeResponse.HasErrors)
			{
				//It didn't work
			}
			else
			{
				Debug.Log("SubtractALife(): lives: " + subtractLifeResponse.ScriptData.GetNumber("currentLives"));
			}
		});
	}
	#endregion

	public void TestAll()
	{
		GetItemCount();
		SetItemCount(15);
		GetLevelProgression();
		GetSpecificLevelProgression("level1");
		SetLevelProgression("level1", 50, 3);
		SetPlayersCurrency(200);
		SubtractALife();
	}
}

 

Hope that helps clear things up a little, I'll be somewhat busy this morning but I'll be around to answer some questions on it later.


Shane

Hi Shane,

Wow, you've really gone above and beyond here. I can't tell you how much I appreciate it! I'm sure to have a few questions, but I already see solutions to some of the issues I was having just by glancing over the code. I'm a bit tied up myself at the moment, but will be testing things out as soon as I can free myself up again.

Thanks ever so much!
- Ken

 

While things still seem more complicated than I'd expected them to be, I have managed to make use of your excellent example and have things working a bit closer to what I need :) Woohoo, thanks so much!

I've one more Cloud Code related inquiry that I'm hoping you might assist me with, and then I think I'll be able to move forward again rather than simply retooling things to get back to where I already was, haha. I previously had things setup via Parse so that I could check the user's Facebook friends who also played the game, and set their avatars next to the last level they had completed. How might I go about using the GetLevelProgression() example (or another entirely) in order to check what level a user's friends had left off in the game, rather than simply checking the current users level?

I'm also curious what the benefits of using your in-house currency and virtual goods systems are? These are things I had already begun coding on my own and was simply going to store those arbitrary values here on GameSparks, but seeing as you've places specifically setup for them, I assume they're provisioned to be something more than simple integer storage? My former inquiry is of a far more pressing nature at this point, but if you have time and don't mind letting me know, that would be great, too.

Thanks again for your patience & support. I look forward to one day having my head fully wrapped around the inner workings of GameSparks, but in the meantime, I certainly appreciate your input.

Have a great day!
- Ken

 

I presume I'd want to try something along the lines of the following, but am unsure of precisely how I should be phrasing the query. I'll have to add some test users in a bit and test it further, but for now I think I need to take a short break.

 

new ListGameFriendsRequest().SetScriptData("getCurrentLevel").Send((response) =>
		{
			foreach(object test in response)
			{
				Debug.Log("FRIEND'S LEVEL: " + response.ScriptData.GetNumber("gameLevel");
			}
		});

 

I still need to add some test users, but I think I've managed to get the proper formatting for the request figured out using the Challenges tutorial as reference. It'll be a bit later before I can try the test users, but thought I'd post this here in case you folks had any feedback :) Thanks again!
		new ListGameFriendsRequest().Send((response) =>
		{
			if(response.HasErrors)
			{
				Debug.LogError("Problem retrieving Friend data from GameSparks.");
			}
			else
			{
				foreach(var friend in response.Friends)
				{
					Debug.Log("Friend :" + friend.DisplayName + ", Fb ID: " + friend.ExternalIds.GetString("FB") + ", Gs ID: " + friend.Id + ", Current level: " + friend.ScriptData.GetNumber("gameLevel"));
				}
			}
		});

 

 

Woohoo! That seemed to work just fine :) I'll quit spamming up the message boards now, haha. Have a terrific weekend!

 

Hello is it possible to be added to the example ? i'm stuck with cloud code my field always return null

Hi Damien,

 

We have added you to the example project for this.


Thanks,

Liam

I believe there is a problem with the cloud script on this example for "RegistrationReponse"


it works on first user but when we create a second user this cloud script is not used on player data:


 

//Add the player's id to a collection which we will use to iterate over the player's lives
Spark.runtimeCollection("playerLives").insert({"_id" : "yourCustomId", "playerId":Spark.getPlayer().getPlayerId()});
//We'll store the player's lives in their script data upon a successful registration
Spark.getPlayer().setScriptData("lives", 20);

 

what im am trying to do is having player values set to a values like 10 when he register or he connect for first time, but in the example it works only for the account Shane, other registered users will not run the cloud code for the registration response and login response for some reason i cant understand

the problem is definetly on the first line, i believe "yourCustomID" but i dont know how tho change this, if i exchange the last line to be on top it will correctly set lives upon registration to all players:


Spark.getPlayer().setScriptData("lives", 21);

Spark.runtimeCollection("playerLives").insert({"_id" : "yourCustomId", "playerId":Spark.getPlayer().getPlayerId()});

Hi,


The "yourCustomId" field is only in here as an example, it will cause an error on any registration or authentication after the first player has been registered as "yourCustomId" has already been entered in the script.PlayerLives collection. You could replace this with your own custom generated ID or use the playerID to populate this field to avoid this.


Thanks,

Liam

Login to post a comment