Using Parse.com with PlayCanvas

ss1Recently I made a memory card matching game called Card Flippy (play) in PlayCanvas. It uses the Parse.com JavaScript SDK to store and retrieve the high scores (Leaderboards, Personal Bests as shown above) of all players in a Parse.com hosted database. So, as requested, here are the details on how to use Parse.com with PlayCanvas.

The first step is setting up the database on Parse.com:

  • Login to Parse.com (after creating a new account if needed) and create a new app
  • Hover over your new app and click ‘Core’, this is where you can design the database
  • Click ‘+ Add Class’ on the left and create a custom class called Player – You should see a few useful default column headers appear (A unique objectID for each row, the date the row was created, when the row was last updated and the access level)
  • Click ‘+ Col’ and add a column called ‘name’ of type string – this will be the players username
  • Click ‘+ Col’ again and add a column called ‘score’ of type number – this will be the players high score

Your column headers should now look something like this:

Screen Shot 2015-06-08 at 11.17.22

Now that the database is set up, you will need to find your api keys to allow you to access the database from a PlayCanvas script. To view all your keys for this app click Settings at the top of the page, and then Keys on the left. For use with PlayCanvas you will need the Application ID and the JavaScript Key.

Setting up the PlayCanvas project:

  • Login to PlayCanvas (after creating a new account if needed) and create a new project
  • Click on Editor and then the primary scene for the project
  • In the editor click on the top-most entity (Root, in this case), click ‘+ Add Component’ and add the Scripts component
  • Firstly we need to add the latest Parse.com JavaScript SDK (details here) as a script in the editor, so type ‘parse.js’ into the Script URL box in the Scripts component and hit enter. This will create the parse.js script for you. Click on ‘parse’ in the script component on the right to open the script for editing. The parse script will contain the PlayCanvas default code, you need to delete all of this and replace it with the full Parse.com JavaScript SDK (the Development version from the Parse downloads page)
  • Secondly we will add our own custom script for talking to our database. In the Script URL box type ‘db.js’ and hit enter. Again the db script will contain the PlayCanvas default code, but this time we need it so do not delete it
  • To access our Parse database we need to put the keys mentioned earlier into the db.js script. Add the Parse.initialize call as the first line of the db.js script so it looks like the following (but with your keys added):
Parse.initialize("Your-ApplicationID-Key", "Your-JavaScript-Key");
pc.script.create('db', function (app) {
  • Finally we need to make sure the Parse SDK loads before our custom db.js script, otherwise we will get ‘Parse is not defined’ errors. To do this in the PlayCanvas editor click the PlayCanvas logo in the top left, then click Script Priority. Add only the parse.js script to this list

Now we have everything needed to write the code to read and write from the database. We will write the following functions:

  • Create a new player in the database
  • Read existing player from database
  • Update existing player in database to have a new high score
  • Read person with highest score from database

Create a new player in the database

The two pieces of information we need to create a player are for the two custom columns we added earlier. All default Parse columns generate their information automatically, so we just need a name and a score. In this project I am assuming that the player enters their username before playing the game, so when we first create a player their score will always be 0 as they have not played the game yet, so now we just need a name.

In Card Flippy I used CanvasInput to ask the players username, this appends a HTML5 Canvas to the page which allows text entry and is very simple to use. Alternatively you could use a prompt, some old school arcade game name input, or for the sake of testing just randomly generate usernames.  So now we can assume we have a name and a score of 0 let’s create a function called createNewPlayer in the db.js script with the following code:

createNewPlayer: function(name){
  var playerObj = Parse.Object.extend("Player");
  var player = new playerObj();
 
  player.set("name", name);
  player.set("score", 0);
 
  player.save(null, {
    success: function(obj) {
      console.log("New Player saved!");
    },
    error: function(obj, error) {
      console.log("Player not saved: ", error.message);
    }
  });
}

In this function we specify the class type we defined in the Parse.com database (Player) and then create a new object of that type called player. Then we set the name and score of that object using player.set() and save it using player.save().

So now we can call this function with a username and it will create a database entry for that username with a score of 0. Test this works by calling it from the scripts initialize function like so:

this.createNewPlayer("Lizzip");

Now there should be an entry in the database on Parse.com with this information

Screen Shot 2015-06-08 at 12.53.45

Read existing player from database

If someone plays our game multiple times with the same username we don’t want to create a new Player for them each time with a 0 score, we want to reuse the same one and let them keep their high score. This also means we need to determine if a player already exists or not, we can do this with a Parse Query.

Create a new function in db.js called login. This function will determine if a player exists. If they do not exist it will call our createNewPlayer function, but if they do exist it will call another new function called loadPlayer.

login: function(name){
  var that = this;
  var query = new Parse.Query("Player");
  query.equalTo("name", name);
  query.find({
    success: function(player) {
      if(player.length > 0){
        that.loadPlayer(name);
      }
      else {
        that.createNewPlayer(name);
      }
    },
    error: function(error){
      console.log("Could not get player info: ", error.message);
    }
  });
}

In this function we are requesting all Players who have a name equal to the name we are passing in to the function, it will return an array of matching objects. Because we now will always reuse the same object when the name exists, this query should only ever return 0 or 1 Players found. If it returns 0 then we call the createNewPlayer function as no players with this name exist, if it does not return 0 then we call the loadPlayer function as someone with this name exists.

So now we need the loadPlayer function. Again create a new function in the db.js script like so:

loadPlayer: function(name){
  var that = this;
 
  var query = new Parse.Query("Player");
  query.equalTo("name", name);
 
  query.first({
    success: function(player){
      that.highscore = player.get("score");
      that.username = name;
    },
    error: function(error){
      console.log("Could not get player info: ", error.message);
    }
  });
}

Again we are using a Parse Query, but this time we are only retrieving the first object with a name equal to the name we passed into the function. The query will return the Player with the matching name as ‘player’ and we can use .get() to get the score. We could also get any other column information from the player like .get(“objectId”) or .get(“name”) but we only need the score for now.

Once the player is found we are saving the score and name to variables on the db.js script instance so they can be accessed as this.highscore and this.username anywhere in the script. We should also do this in the createNewPlayer function by adding the following two lines right before the end of the function:

this.highscore = 0;
this.username = name;

Update existing player in database to have a new high score

Create a new function called updatePlayer like so:

updatePlayer: function(name, score){
  var query = new Parse.Query("Player");
  query.equalTo("name", name);
 
  query.first({
    success: function(player){
      player.set("score", score);
 
      player.save({
        success: function(object) {
          console.log("Save complete");
        },
        error: function(object, error) {
          console.log("Save failed: ", error.message);
        }
      });
    },
    error: function(error){
      console.log("Could not save score info: ", error);
    }
  });
}

Updating a players high score is very similar to both the createNewPlayer and loadPlayer functions, we use the Query from loadPlayer to get the existing player, and the .set() and .save() functions from createNewPlayer to update the existing players score.

Read person with highest score from database

If our game has highscores, then it is probably a competitive game and we can create a leaderboard. With Parse Queries we can provide sorting orders and constraints to find the person with the highest score easily (and without having to retrieve all players and iterate them ourselves). Create a new function called getBestScore

getBestScore: function(){
  var query = new Parse.Query("Player");
  query.descending("score");
  var that = this;
 
  query.first({
    success: function(player){
      that.overallHighscore = player.get("score");
      that.overallHighscorer = player.get("name");
    } 
  });
}

In this case we are looking for the highest score, so we use .descending(), but if we were looking for the lowest score we could use .ascending(), or we could use .greaterThan() and .ascending() to find the lowest score above a certain value.

This function saves the top scorer name and score to variables on the db.js script instance so we can display them on the leaderboard.

That’s it!

In Card Flippy I had multiple levels and scores per person but it was all based on these same functions. For more details about the Parse API look at the Parse.com JavaScript guide and for more help with PlayCanvas take a look at their developer guide.

I have created a public project with this code over at PlayCanvas so feel free to fork the project and have a go yourself (but remember to put your own Parse.com API keys in db.js). If you have any questions please post them as a comment on the Overview page of the project.

Here’s the full code for db.js:

Parse.initialize("Your-ApplicationID-Key", "Your-JavaScript-Key");
pc.script.create('db', function (app) {
  var Db = function (entity) {
    this.entity = entity;
        
    this.username;
    this.highscore;
    this.overallHighscore;
    this.overallHighscorer;
  };

  Db.prototype = {
    initialize: function () {
    },

    update: function (dt) {
    },
        
    createNewPlayer: function(name){
      var playerObj = Parse.Object.extend("Player");
      var player = new playerObj();
            
      player.set("name", name);
      player.set("score", 0);
            
      player.save(null, {
        success: function(obj) {
          console.log("New Player saved!");
        },
        error: function(obj, error) {
          console.log("Player not saved: ", error.message);
        }
      });
    },
        
    login: function(name){
      var that = this;
      var query = new Parse.Query("Player");
      query.equalTo("name", name);
      query.find({
        success: function(player) {
          if(player.length > 0){
            that.loadPlayer(name);
          }
          else {
            that.createNewPlayer(name);
          }
        },
        error: function(error){
          console.log("Could not get player info: ",error.message);
        }
      });
    },
        
    loadPlayer: function(name){
      var that = this;
            
      var query = new Parse.Query("Player");
      query.equalTo("name", name);
            
      query.first({
        success: function(player){
          that.highscore = player.get("score");
          that.username = name;
        },
        error: function(error){
          console.log("Could not get player info: ",error.message);
        }
      });
    },
        
    updatePlayer: function(name, score){
      var query = new Parse.Query("Player");
      query.equalTo("name", name);
            
      query.first({
        success: function(player){
          player.set("score", score);
                    
          player.save({
            success: function(object) {
              console.log("Save complete");
            },
            error: function(object, error) {
              console.log("Save failed: ", error.message);
            }
          });
        },
        error: function(error){
          console.log("Could not save score info: ", error);
        }
      });
    },
        
    getBestScore: function(){
      var query = new Parse.Query("Player");
      query.descending("score");
      var that = this;
            
      query.first({
        success: function(player){
          that.overallHighscore = player.get("score");
          that.overallHighscorer = player.get("name");
        } 
      });
    }
  };

  return Db;
});