Sign In Register

How can we help you today?

Start a new topic
Answered

Migrating from Parse. How to implement this in GS?

Parse migrant here, trying to figure out how to port my back end logic to GS.


At the end of each game I record some simple stats for the player (identified by a string, their iOS Game Centre ID):

- which of the four colours they played as red/green/blue/yellow

- which size of playing board they used

- if they were playing a local or online game


These are counters that optionally increment by one at each game over call.

eg: +1 red, +1 large board, +1 online


I've been reading the GS docs and it's not clear how to implement this on the GS platform. What way would be best to create these counters so I could later retrieve their current values? I need this data to award Game Centre achievements for example "Won 50 Games on a Large Board".




Best Answer

So you need to store the following structure and link it to the player

{

"iOsGameCenterId":"theIDValue",

"playedColor":1,//or a string 

"boardSize":16,

"playedOnline": true

}


You have two choices either:

-attach extra data to the builtin gamespark player by using Spark.getPlayer().setScriptData();

-create your own runtime collection.


I'll show you how to do it with a custom collection


1-in "Configurator->events" create 2 events:

getPlayerStats, with no parameter

setPlayerStats with 4 parameters: iOsGameCenterId, playedColor, boardSize, playedOnline


2-in "Configurator->Cloud Code->Events->getPlayerStats paste the following code

   

var player = Spark.getPlayer();
var playerStats = Spark.runtimeCollection("PlayerStats").findOne({"playerId":player.getPlayerId()});
//on the client side you'll have to manage a possible null value
Spark.setScriptData("result", playerStats);

    3-in "Configurator->Cloud Code->Events->setPlayerStats paste the following code

  

var player = Spark.getPlayer();
var playerId = player.getPlayerId();

var iOsGameCenterId = Spark.data.iOsGameCenterId;
var playedColor = Spark.data.playedColor;
var boardSize = Spark.data.boardSize;
var playedOnline = Spark.data.playedOnline;

var playerStats = Spark.runtimeCollection("PlayerStats").findOne({"playerId":playerId});
if (playerStats === null){
	playerStats = {
		"playerId":playerId,
		"iOsGameCenterId":"theIDValue",
		"playedColor":1,//or a string 
		"boardSize":16,
		"playedOnlineCount": playedOnline ? 1 : 0
	}
}
else {
//here do your increment logic
if (playedOnline) {
 playerStats.playedOnlineCount++;
}
}
Spark.runtimeCollection("PlayerStats").save(playerStats);

//here you can return whatever suits you, the update stats or success flag, i'll simply return true
Spark.setScriptData("result", true);

4- if you need the stats on the client, call getPlayerStats after a login or register to get the player stats on the client 


5-call setPlayerStats when the game is over.


that's it



Answer

So you need to store the following structure and link it to the player

{

"iOsGameCenterId":"theIDValue",

"playedColor":1,//or a string 

"boardSize":16,

"playedOnline": true

}


You have two choices either:

-attach extra data to the builtin gamespark player by using Spark.getPlayer().setScriptData();

-create your own runtime collection.


I'll show you how to do it with a custom collection


1-in "Configurator->events" create 2 events:

getPlayerStats, with no parameter

setPlayerStats with 4 parameters: iOsGameCenterId, playedColor, boardSize, playedOnline


2-in "Configurator->Cloud Code->Events->getPlayerStats paste the following code

   

var player = Spark.getPlayer();
var playerStats = Spark.runtimeCollection("PlayerStats").findOne({"playerId":player.getPlayerId()});
//on the client side you'll have to manage a possible null value
Spark.setScriptData("result", playerStats);

    3-in "Configurator->Cloud Code->Events->setPlayerStats paste the following code

  

var player = Spark.getPlayer();
var playerId = player.getPlayerId();

var iOsGameCenterId = Spark.data.iOsGameCenterId;
var playedColor = Spark.data.playedColor;
var boardSize = Spark.data.boardSize;
var playedOnline = Spark.data.playedOnline;

var playerStats = Spark.runtimeCollection("PlayerStats").findOne({"playerId":playerId});
if (playerStats === null){
	playerStats = {
		"playerId":playerId,
		"iOsGameCenterId":"theIDValue",
		"playedColor":1,//or a string 
		"boardSize":16,
		"playedOnlineCount": playedOnline ? 1 : 0
	}
}
else {
//here do your increment logic
if (playedOnline) {
 playerStats.playedOnlineCount++;
}
}
Spark.runtimeCollection("PlayerStats").save(playerStats);

//here you can return whatever suits you, the update stats or success flag, i'll simply return true
Spark.setScriptData("result", true);

4- if you need the stats on the client, call getPlayerStats after a login or register to get the player stats on the client 


5-call setPlayerStats when the game is over.


that's it


oops copy/paste mistake

just after if (playerStats === null){ you shoud have read:

replace playerStats = {

        "playerId":playerId,

        "iOsGameCenterId":iOsGameCenterId ,

        "playedColor":playedColor,

        "boardSize":boardSize,

        "playedOnlineCount": playedOnline ? 1 : 0


Great example, thanks. I'd gone back to the docs and figured I could maybe do it with an event and some attached cloud code to update player scriptData and you confirmed that :) My cloud code is below. I haven't hooked it up to the game yet but the test harness results seem to be behaving.

var plyr = Spark.getPlayer();
var data = Spark.getData();
var t;

t = data.win_red + (plyr.getScriptData("win_red") || 0);
plyr.setScriptData("win_red", t);
Spark.setScriptData("win_red", t);

t = data.win_grn + (plyr.getScriptData("win_grn") || 0);
plyr.setScriptData("win_grn", t);
Spark.setScriptData("win_grn", t);

t = data.win_blu + (plyr.getScriptData("win_blu") || 0);
plyr.setScriptData("win_blu", t);
Spark.setScriptData("win_blu", t);

t = data.win_yel + (plyr.getScriptData("win_yel") || 0);
plyr.setScriptData("win_yel", t);
Spark.setScriptData("win_yel", t);


t = data.win_lrg + (plyr.getScriptData("win_lrg") || 0);
plyr.setScriptData("win_lrg", t);
Spark.setScriptData("win_lrg", t);

t = data.win_med + (plyr.getScriptData("win_med") || 0);
plyr.setScriptData("win_med", t);
Spark.setScriptData("win_med", t);

t = data.win_sml + (plyr.getScriptData("win_sml") || 0);
plyr.setScriptData("win_sml", t);
Spark.setScriptData("win_sml", t);


t = data.win_online + (plyr.getScriptData("win_online") || 0);
plyr.setScriptData("win_online", t);
Spark.setScriptData("win_online", t);

t = data.win_local + (plyr.getScriptData("win_local") || 0);
plyr.setScriptData("win_local", t);
Spark.setScriptData("win_local", t);

t = data.lose_online + (plyr.getScriptData("lose_online") || 0);
plyr.setScriptData("lose_online", t);
Spark.setScriptData("lose_online", t);

t = data.lose_local + (plyr.getScriptData("lose_local") || 0);
plyr.setScriptData("lose_local", t);
Spark.setScriptData("lose_local", t); 

  

I would not separate the player stats in separate scriptData keys like "win_blu", "win_red" ...

use only one object with all the player profiles properties ans store it in one scriptData property "playerStats"

 

var clientData = Spark.data.clientData; //assuming it's the same structure has the one in getScriptData

var playerStats = plyr.getScriptData("playerStats");
//it will be null if it's not set yet
if (playerStats  === null)
{
	playerStats = {
	 "win_red":0,
	 "win_grn":0,
	
	 ....
	 "lose_locale":0
	}
}

playerStats.win_red += clientData.win_red;
playerStats.win_grn += clientData.win_grn;
....
playerStats.lose_locale += clientData.lose_locale;

plyr.setScriptData("playerStats", playerStats);

 



I would not separate the player stats in separate scriptData keys like "win_blu", "win_red" ...

use only one object with all the player profiles properties ans store it in one scriptData property "playerStats"

 

var clientData = Spark.data.clientData; //assuming it's the same structure has the one in getScriptData

var playerStats = plyr.getScriptData("playerStats");
//it will be null if it's not set yet
if (playerStats  === null)
{
	playerStats = {
	 "win_red":0,
	 "win_grn":0,
	
	 ....
	 "lose_locale":0
	}
}

playerStats.win_red += clientData.win_red;
playerStats.win_grn += clientData.win_grn;
....
playerStats.lose_locale += clientData.lose_locale;

plyr.setScriptData("playerStats", playerStats);

 



OK, why's that? To keep the stats data together in case the client data is ever expanded?

Whenever possible you have to isolated your components from vendor specific implementation. It will be easier if you ever need to port your implementation to another solution. Gamesparks offers a service to add extra data to a player, that's practical but don't use it as your data model structure. You have to isolate and design your "Player Statistics" component on its own with it's specific data structure and behaviors. 

Maybe you don't have to change anything. It depends on many factors, are your the sole programmer? you have no plan to update the game and so on.  It's a simple advice easy to implement that will make your code easier to maintain, understand and evolve.

Fine, just a general design principle. I wondered if it was GS specific advice that's all. Thanks :)

Next question :)


Is there a way, other than through the client API, to register GS players? I have <x> players in Parse and I'd like to import their data to GS somehow. If the data at the GS side is attached to scriptData then I need to create the GS player account to store it. 


I have a working solution that will register one player and import their data on the fly but this requires them to run the game on their device. I guess I could hack up a batch import from this code and run it myself, but is there a nicer way? Could the GS REST API be used to create and pre-populate player records?


It is no feasible because you cannot extract the password from ParseUser to set it in a GS Player.

Players don't log in as such, it all happens in the background and their credentials are automatically generated so I don't actually need the Parse passwords.



OK then, I would export the ParseUser into a file, copy all the text in the enclosing top level array (including []) and paste it in the NoSql "Insert" tab under a new runtime collection named ParseUser.


Then run a cloud code function that creates all the GS Players


function migrateParseUserToGSPlayer(){

 var parseUsers = Spark.runtimeCollection("ParseUser").find();

 while (parseUsers.hasNext()){

  var parseUser = parseUsers.next();

  

  var re = new SparkRequests.RegistrationRequest();

    re.userName = parseUser.email;

    re.displayName = parseUser.username;

    re.password = generatePassword();//you'll have to implement this

    

    //I don't know how to set the scriptData field, sorry

    re.scriptData = ????

    

    re.Send();

 }

}


then drop the ParseUser collection

I didn't do it your way in the end but you pointed me in the right direction :) I hadn't seen the SparkRequests API as it doesn't seem to be linked from the main Cloud Code API docs.


I made an "import" event and hooked up some Cloud Code to it. It uses the Parse REST API to fetch up to date Player records, then for each one tries a GS AuthenticationRequest followed by a RegistrationRequest to get or create a GS player account appropriately. The Parse player stats are then merged in to the GS player scriptData (max value is taken).


I needed to do it this way so I could re-run the import as and when I wanted, as players will continue to use the Parse back end until they update their game client.


Cheers :)


Login to post a comment