thecrazyprogram_icon thecrazyprogram_leaderboard
Facebook leaderboard as seen in The Crazy Program on Android.

 I got a lot of feedback from my friends testing our new mobile game The Crazy Program. One thing that came up a few times was a request to have a high scores table to compete with your friends. I created a quick Facebook App version of Donut Get! last Fall and knew it wouldn’t be too much work with Facebook’s built in high scores functionality.

Facebook allows you to save high scores for your app without needing any backend. The caveat is that you can only save one score per user. So this works decently for a global high score for your game, but not so well if you have different levels and different modes. Facebook’s scores API will also return a list of your friends that are playing, in order of rank. This makes it very easy to hit the ground running with some social features.

Prime31 Social Networking Plugins for Unity

I decided to purchase the Prime31 Social Networking plugins to handle the communication between Facebook and Unity. I had a good experience with their in-app purchase plugins and the support was good. There were other options for Facebook plugins but they either weren’t for both Android and iOS or I couldn’t tell whether or not they could handle posting high scores. Some plugins seemed to just handle basic Facebook connect features, or at least this was the impression I got.

prime31social_demo
Prime31 Social Networking demo scene

I started development on Android. The example scene is straightforward and I got connected with my Facebook App fairly quickly.

Prime31 Social Networking Configuration

So by now you should have a Facebook App created. If you haven’t, create one at http://apps.facebook.com .

You’ll need to setup your Facebook App to allow your Unity app to connect to it. Additionally, you’ll need to make iOS and Android specific configurations within your app.

Android:

The Official Android Plugin Documentation: http://prime31.com/docs#androidSocial

Inputting App id

For Android apps, you’ll have to generate a Key Hash for the Facebook control panel. It’s a string that’s generated with the command-line “keytool.”

1
2
3
4
5
6
7
// enter this command into your command-line
// replace "yourappreleasekeyalias" with the alias name from your keystore
// replace the path to the keystore you're using to sign your application

$ keytool -exportcert -alias yourappreleasekeyalias -keystore ~/.your/path/release.keystore | openssl sha1 -binary | openssl base64

//

This Stackoverflow post helps explain how to generate the Key Hash. You need to input the Key Hash on this screen in the Facebook app settings.

iOS:

The Official iOS Plugin Documentation: http://prime31.com/docs#iosSocial

Follow this YouTube video to figure out how to configure the plugin on iOS.

 

Coding in Unity

The first step is logging the user into Facebook. This will bring up Facebook login window.

1
2
3
4
5
6
public void Login() {

var permissions = new string[] { "email" };
FacebookAccess.loginWithReadPermissions( permissions );

}

Once logged in, you will need to ask for “publish_actions” permissions to be able to post scores. Apparently you are not allows to ask for this all at once, so it’s supposed to be requested after connecting.

To handle this, I listened for the “sessionOpenedEvent” and when that occurs, check to see if the proper permissions are available.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public bool CheckPermissions() {

if (!loggedIn) return false;

bool permissionsValid = false;
int permissionCount = 0;

List

foreach(string permission in permissions) {
if (permission == "email" || permission == "publish_actions") {
permissionCount++;
}
}

if (permissionCount >= 2) {
permissionsValid = true;
}

return permissionsValid;

}

 

Submitting Scores to Facebook

The Facebook scores system will accept any score that you send to it. So if you’re trying to display just the highest score, you’ll have to determine what the previously highest score was and determine if the new one surpasses it. This logic need to be handled on the front-end.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void SaveScore(int score) {

// check if logged in
var isSessionValid = FacebookAccess.isSessionValid();
if (!isSessionValid) {
Debug.Log("not logged in, time to login");
Login();
return;
}

bool validPermissions = CheckPermissions();
if (!validPermissions) {
Debug.Log("not valid permissions in, request them!");
return;
}

if (score > bestScore) {
_bestScore = (int) score;
} else {
return;
}

string request = "me/scores";

var parameters = new Dictionary<string,object>()
{
{ "score", score.ToString() }
};

Facebook.instance.graphRequest( request, HTTPVerb.POST, parameters, OnPostGraphComplete );

}

 

Displaying Scores in Unity

Calling GameFacebook.instance.GetLeaderboard() will load data from the users Facebook friends that are using the app. This will return a hashtable containing user id’s, names, scores. You can retrieve avatars with the id’s, this is demonstrated in the LoadAvatar() method.

My Facebook Interface class

Here’s the complete class I used for The Crazy Program, no warranty or guarantees! It should provide a good starting point for Facebook high scores implementation.

 

Play The Crazy Program!

See how the high scores work yourself! ;) The Crazy Program is free on Android and coming soon to iOS and OUYA.

thecrazyprogram_icon_150

android_app_on_play_logo_small

I think that’s it!

  • Alejandro Foronda

    Awesome post, really helpful I always thought you needed a backend for a high score system.
    THANK YOU!

    • Naresh

      Thank you!!

  • Rungroj Keawpapai

    Help me please!
    - can not post score ( SaveScore(int score) )
    - can not get me score ( GetMyScore() )
    - can not get score ( GetLeaderboard() )

    error getpermissions

    • http://www.sokay.net Bryson Whiteman

      Make sure you can get the Prime31 demo project working. That should get you permissions to post to the user’s wall. Once you have that, that should be enough to post and save scores.

      If you can’t get the demo project working, this definitely won’t work.

      • Rungroj Keawpapai

        private const string FB_APP_ID = “[fb_id]“;

        edit to

        private const string FB_APP_ID = “142081772631899″;

        true or not ?

        • http://www.sokay.net Bryson Whiteman

          Correcto.

          • Rungroj Keawpapai

            can share your complete project ..Please
            Thank you

      • liveagain

        Thanks for your kind reply.

        I wish I am taught from you much.

        Bellow code, I’m using.

        public class GameFacebook : MonoBehaviour {

        private static string FB_APP_ID = “”;

        public static string ICON_URL = “http://www.mygamesite.com/fbicon.png”;

        // for actions after completing login

        private static string NEXT_ACTION_NONE = “NextActionNone”;

        private static string NEXT_ACTION_POST_SCORE = “NextActionPostScore”;

        private static string NEXT_ACTION_POST_TO_WALL = “NextActionPostToWall”;

        private static string NEXT_ACTION_POST_REQUEST_PERMISSION = “NextActionRequestPermission”;

        private static string nextAction = NEXT_ACTION_NONE;

        private static int tempScore; // save score temporarily while requesting permissions and whatnot

        // store user data for score tables, this data is grabbed from the leaderboard class to populate leaderboard

        public static List userListAvatars;

        public static List userListNames;

        public static List userListScores;

        public static int _FacebookScore = -1;

        public static int FacebookScore {get {return _FacebookScore;}}

        public static string _currentId;

        public static string currentId {get {return _currentId;}}

        private static bool permissionIsValid; // are the permissions valid?

        private static List currentPermissions;

        private static int leaderboardTotal;

        private static int leaderboardCount; // count leaderboard avatars loaded

        public static bool fProcessing = false;

        public static bool fProcessResult = false;

        public static string result = “”;

        // check if the player is currently logged in

        public static bool loggedIn {

        get {

        bool loggedInYes = false;

        loggedInYes = FacebookAccess.isSessionValid();

        return loggedInYes;

        }

        }

        public static void Init() {

        FacebookAccess.init();

        // add listeners for FB events

        FacebookManager.sessionOpenedEvent += LoginSuccess;

        FacebookManager.reauthorizationSucceededEvent += ReauthSuccess;

        }

        ////////////////////////

        // EVENTS

        ////////////////////////

        static void LoginSuccess() {

        //Debug.Log(” *** GameFacebook >> LoginSuccess *** “);

        bool validPermissions = CheckPermissions();

        if (validPermissions) {

        GetMyScore();

        //Debug.Log(” nextAction: ” + nextAction );

        if (nextAction != NEXT_ACTION_NONE) {

        if (nextAction != NEXT_ACTION_POST_TO_WALL) OnNextAction();

        }

        }

        }

        static void ReauthSuccess() {

        //Debug.Log(” *** Facebook >> ReauthSuccess *** permissionIsValid: ” + permissionIsValid);

        // next action

        CheckPermissions(false);

        if (permissionIsValid) {

        GetMyScore();

        OnNextAction();

        }

        }

        static void OnNextAction() {

        //Debug.Log(“GameFacebook >> OnNextAction: ” + nextAction);

        if (nextAction == NEXT_ACTION_POST_SCORE) {

        SaveScore(tempScore);

        } else if (nextAction == NEXT_ACTION_POST_TO_WALL) {

        PostScreenToWall();

        } else if (nextAction == NEXT_ACTION_POST_REQUEST_PERMISSION) {

        }

        // reset next action

        nextAction = NEXT_ACTION_NONE;

        }

        ////////////////////////

        // LOGIN

        ////////////////////////

        public static void Login() {

        //Debug.Log(“Facebook >> Login ” + FacebookAccess.getAccessToken());

        _FacebookScore = -1; // reset score for new user

        var permissions = new string[] { “email” };

        FacebookAccess.loginWithReadPermissions( permissions );

        }

        public static void Logout() {

        //Debug.Log(“Facebook >> Logout”);

        FacebookAccess.logout();

        currentPermissions = null;

        }

        public static bool CheckPermissions(bool requestIfNeeded = true) {

        //Debug.Log(“Facebook >> CheckPermissions”);

        if (!loggedIn) return false;

        bool permissionsValid = false;

        int permissionCount = 0;

        bool haveValidPermissionsStored = false;

        List permissions = FacebookAccess.getSessionPermissions();

        if (currentPermissions != null) {

        //Debug.Log(“Facebook >> use stored permissions!”);

        permissions = currentPermissions;

        }

        if (permissions != null) {

        if (permissions.Count > 0) {

        //Prime31.Utils.logObject( permissions );

        currentPermissions = new List(permissions); // store permissions locally

        foreach(string permission in permissions) {

        if (permission == “email” || permission == “publish_actions”) {

        permissionCount++;

        }

        }

        }

        }

        if (permissionCount >= 2) {

        permissionsValid = true;

        }

        if (requestIfNeeded) {

        if (!permissionsValid) {

        RequestPermissions();

        }

        }

        permissionIsValid = permissionsValid;

        return permissionsValid;

        }

        public static void RequestPermissions() {

        //Debug.Log(“Facebook >> RequestPermissions”);

        currentPermissions = null;

        var permissions = new string[] { “publish_actions” };

        #if UNITY_ANDROID

        FacebookAccess.reauthorizeWithPublishPermissions( permissions, FacebookSessionDefaultAudience.EVERYONE );

        #elif UNITY_IPHONE

        FacebookAccess.reauthorizeWithPublishPermissions( permissions, FacebookSessionDefaultAudience.Everyone );

        #endif

        }

        ////////////////////////

        //

        // POSTING

        //

        ////////////////////////

        ////////////////////////////////////////////////

        //

        // SaveScore()

        // save score to FB

        //

        ////////////////////////////////////////////////

        public static void SaveScore(int score) {

        fProcessing = true;

        //Debug.Log(“Facebook >> SaveScore >>> score: ” + score + ” , _bestScore: ” + _bestScore );

        // check if logged in

        var isSessionValid = FacebookAccess.isSessionValid();

        if (!isSessionValid) {

        //Debug.Log(“not logged in, time to login”);

        tempScore = score;

        nextAction = NEXT_ACTION_POST_SCORE;

        Login();

        return;

        }

        bool validPermissions = CheckPermissions();

        if (!validPermissions) {

        //Debug.Log(“not valid permissions in, request them!”);

        tempScore = score;

        nextAction = NEXT_ACTION_POST_SCORE;

        return;

        }

        ///////////////////////

        // save score to FB

        ///////////////////////

        string request = “me/scores”; // save score to FB

        var parameters = new Dictionary()

        {

        { “score”, score.ToString() }

        } ;

        Facebook.instance.graphRequest( request, HTTPVerb.POST, parameters, OnPostGraphComplete );

        }

        static void OnPostGraphComplete( string error, object result ) {

        fProcessing = false;

        if(error != “”)

        fProcessResult = false;

        else

        fProcessResult = true;

        result = error;

        }

        // get player score

        public static void GetMyScore() {

        string request = “me/scores”;

        Facebook.instance.graphRequest( request, HTTPVerb.GET, OnGetScoreComplete );

        }

        private static void OnGetScoreComplete( string error, object curresult ) {

        Hashtable scoreResults = curresult as Hashtable;

        ArrayList list = scoreResults["data"] as ArrayList;

        Hashtable ht = list[0] as Hashtable;

        Hashtable user = ht["user"] as Hashtable;

        int score = (int) ht["score"];

        string id = user["id"] as string;

        string name = user["name"] as string;

        name = name.Split(‘ ‘)[0] as string;

        _FacebookScore = (int) score;

        _currentId = id;

        result = curresult.ToString();

        //Debug.Log(“Best Score is: ” + bestScore + ” , currentId: ” + id);

        }

        //////////////////////////////

        //

        // post score to facebook wall

        //

        //////////////////////////////

        public static void PostScreenToWall() {

        fProcessing = true;

        if (!loggedIn) {

        nextAction = NEXT_ACTION_POST_TO_WALL;

        Login();

        return;

        }

        string screenshotFilename = “screenshot”;

        Application.CaptureScreenshot(screenshotFilename);

        var pathToImage = Application.persistentDataPath + “/” + screenshotFilename;

        var bytes = System.IO.File.ReadAllBytes( pathToImage );

        Facebook.instance.postImage( bytes, “im an image posted from Android”, OnPostImageCompletionHandler );

        }

        private static void OnPostImageCompletionHandler(string error,object result)

        {

        fProcessing = false;

        if(error != “”)

        {

        fProcessResult = false;

        }

        else

        fProcessResult = true;

        result = error;

        }

        //////////////////////////////

        //

        // invite FB friends to the game

        //

        //////////////////////////////

        public static void InvitePlayer() {

        //Debug.Log(“Facebook >> InvitePlayer”);

        string title = “Let’s learn Driving!”;

        string message = “Do you have driver’s skill? And Can you beat me?”;

        Dictionary lParam = new Dictionary();

        lParam["message"] = message;

        lParam["title"] = title;

        FacebookAccess.showDialog(“apprequests”, lParam);

        }

        }

  • liveagain

    Hi, there.
    This is very useful.
    I’m using it.
    But socre post and get score are not working.
    Image post and Invite friends are working well.
    What Did I have mistake?
    Please help me.

    Thank you for your sharing.

    • http://www.sokay.net Bryson Whiteman

      Thanks, I’m glad you were able to at least partially get it to work. But I can’t help you unless you give more information. Is there any information in the debug logs? Errors? It could be anything.

      • liveagain

        I have listed my code.
        It is very similar to yours.
        Debug logs are no output.
        In OnCompletionHandler, error param maybe is set.
        But I can’t check it.

      • liveagain

        Maybe previous person has same problem to me.
        How could he solve this problem?

      • liveagain

        Anyhow, I’m very thanks because some functions are implemented by your help.

        I’m checking bellow function

        private void OnGetScoreComplete( string error, object curresult ) {

        if(error != “”)

        {

        getmyscore = error;

        _FacebookScore = 30303;

        return;

        }

        Hashtable scoreResults = curresult as Hashtable;

        ArrayList list = scoreResults["data"] as ArrayList;

        Hashtable ht = list[0] as Hashtable;

        Hashtable user = ht["user"] as Hashtable;

        int score = (int) ht["score"];

        string id = user["id"] as string;

        string name = user["name"] as string;

        name = name.Split(‘ ‘)[0] as string;

        getmyscore = error + “:” + id + “:” + name + curresult.ToString();

        _FacebookScore = (int) score;

        _currentId = id;

        // result = curresult.ToString();

        //Debug.Log(“Best Score is: ” + bestScore + ” , currentId: ” + id);

        }

        I had output the error string to label.
        it shows “”.
        But FacebookScore was set as 30303.
        How can I understand it?

        • http://www.sokay.net Bryson Whiteman

          Try:

          getmyscore = “error: ” + error;

          To make sure the label is being updated.

          If it doesn’t say anything, then perhaps there’s no error being thrown.

          Also you can’t expect to get a score without first saving one. So maybe the saving is incorrect.

          • liveagain

            Thank you.
            I’ll try it.
            Just a second.
            Sorry to bother you.

          • liveagain

            And bellow is savescore code.

            public void SaveScore(int score) {

            int x = 100;

            fProcessing = true;

            //Debug.Log(“Facebook >> SaveScore >>> score: ” + score + ” , _bestScore: ” + _bestScore );

            // check if logged in

            var isSessionValid = FacebookAccess.isSessionValid();

            if (!isSessionValid) {

            //Debug.Log(“not logged in, time to login”);

            tempScore = x;

            nextAction = NEXT_ACTION_POST_SCORE;

            Login();

            return;

            }

            bool validPermissions = CheckPermissions();

            if (!validPermissions) {

            //Debug.Log(“not valid permissions in, request them!”);

            tempScore = x;

            nextAction = NEXT_ACTION_POST_SCORE;

            return;

            }

            ///////////////////////

            // save score to FB

            ///////////////////////

            string request = “me/scores”; // save score to FB

            var parameters = new Dictionary()

            {

            { “score”, score.ToString() }

            } ;

            Facebook.instance.graphRequest( request, HTTPVerb.POST, parameters, OnPostGraphComplete );

            }

            void OnPostGraphComplete( string error, object result ) {

            if(error != “”)

            {

            getmyscore = “error:” + error;

            }

            fProcessing = false;

            if(error != “”)

            fProcessResult = false;

            else

            fProcessResult = true;

            result = error;

            }

            I’m thinking it is same to yours.

    • http://www.sokay.net Bryson Whiteman

      I’m sorry man, but reposting the code that I wrote isn’t of much help. Were you able to get the Prime31 plugins working correctly? Did you configure the FB ID and everything? Without FB configuration the rest won’t work.

      I can’t help you unless you can explain what’s happening to cause an error. I’m not going to sort through your edits of my code.

      • liveagain

        Just now, I have tested according to your advice.

        Bellow is the savescore code tested.
        ///////////////////////////
        Facebook.instance.graphRequest( request, HTTPVerb.POST, parameters, OnPostGraphComplete );

        }

        void OnPostGraphComplete( string error, object result ) {

        if(error != “”)
        {
        getmyscore = “error:” + error;
        }

        //////////////////
        It shows “error:” only.
        Maybe error is set surely.
        but what kind of error?
        Why it can’t shows as a string?

        First of all when savescore, it occurs error.

        I’m using prim31 plugin.
        AndroidMainfest file has FB ID.
        Also image post and invite are working well.
        This is to verify FB configuration is correct, I’m thinking.
        Maybe any permission?

        Please help me. I’m very sorry to take your many time.

      • liveagain

        This is the requesting permission code

        public void RequestPermissions() {
        //Debug.Log(“Facebook >> RequestPermissions”);

        currentPermissions = null;

        var permissions = new string[] {“publish_actions”, “manage_friendlists”} ;

        #if UNITY_ANDROID

        FacebookAccess.reauthorizeWithPublishPermissions( permissions, FacebookSessionDefaultAudience.EVERYONE );

        #end if
        }

      • liveagain

        Hi, there

        Bellow is the prim31′s save and get score Code.

        But your code is simple rather than this.

        So I did wanna use your code.

        // Posts a score for your app

        public void postScore( string userId, int score, Action completionHandler )

        {

        if( appAccessToken == null )

        {

        Debug.Log( “you must first retrieve the app access token before posting a score” );

        completionHandler( false );

        return;

        }

        if( userId == null )

        {

        Debug.Log( “a valid userId is required to post a score” );

        completionHandler( false );

        return;

        }

        // post the score to the proper path

        var path = userId + “/scores”;

        var parameters = new Dictionary()

        {

        { “score”, score.ToString() },

        { “app_access_token”, appAccessToken },

        { “access_token”, appAccessToken }

        } ;

        post( path, parameters, ( error, obj ) =>

        {

        if( error == null && obj is bool )

        {

        var result = (bool)obj;

        completionHandler( result );

        }

        else

        {

        completionHandler( false );

        }

        } );

        }

        // Retrieves the scores for your app

        public void getScores( string userId, Action onComplete )

        {

        var path = userId + “/scores”;

        get( path, onComplete );

        }

        How can I understand your code?

      • liveagain

        Hi, there.
        You are very brilliant.
        I have done post and get score.
        It’s reason is due to Hashtable.
        It cannot receive from object.
        I’m wondering It is running on your game?
        Maybe it’s reason is due to any platform or OS?.
        Anyhow I’m very thanks.
        The rest is the leaderboard.
        I’m starting just now.
        I’ll ask you again if I have problem.
        It’s OK?

        Thanks

  • Naresh

    Thank You!!! We really appreciate your work please keep it up…. Cheers!!! :D :)

  • eltayeb

    man thanks for the great post but am having a problem

    when i try calling the graphrequest it the function oncompeletehandler doesnt fire do you have any idea why