We're archiving the forums and going read only! You'll be able to see old threads, but new topics and replies have been disabled.

Visit the Game Jolt community for new questions and conversations.


Introduction

In this tutorial I am going to show you how to implement achievements including trophies and highscores, using the GJ API for GM Studio. Like my other tutorial this is aimed at those unfamilar with the Game API and those who are not keen on programming also.

Note: When you use this tutorial, I assume you already have a basic understanding of GML and GameMaker: Studio.

If you want to see how to implement achievements with previous versions of GM see my other tutorial. Below is a link to this.

Game Maker: Adding achievements complete beginners-guide

This tutorial will be using Florian van Strien's Game API library. The topic for this library is here

Preparation

So let's begin, first of all you need to download the GM Studio library from the achievements page, here is a link.

Gamejolt achievement API GM: Studio

Once you have downloaded the library into a convenient location on your computer, you need to import the scripts into your game. But first let's take a look at its contents, as shown below.

  • AchievementExample.exe

  • Documentation.rtf (I advise you read this)

  • Example.gmz

  • Scriptstoimportinyourgame.gml (needed)

The with "needed" in the brackets to one of the files, is the file that contain the scripts we need to import.

Note: Make you unzip the folder first before you use this.

To import the scripts right click "Scripts" and click "Add Existing Script" from the drop down menu. Then open "Scriptstoimportinyourgame.gml" from the file dialog box.

Important Note: If you are using the free edition of GM Studio then you are limited to importing 10 scripts.

Now you need to create an object to begin the API and open the session. Give the object a suitable name, so you will recognise it, such as "GJ_controller" and set the object to "persisent".

Then in the "Game start" event of this object add a new script and add the following:

		
			gj_init(ID, private_key)
		
	

In the first argument you enter the Game ID, for your game you can find this out by going to Game Jolt -> Dashboard -> Your Games. Then find your game and click Game API then API Settings. You should see the following screen:

5d099b1fbeaa5.png

Copy the Game ID and the Private key and pass them to gj_init() as shown below:

		
			gj_init("1234", " 381e2309cf82106f3a1d67cbbfecbdc6");
		
	

Important note: These arguments are string values so make sure you put these as such, below is an example to show this.

Important note: Ensure you only call this function once in your game, calling it again will induce memory leaks and will log the user out of the session.

Logging in the user

Now we attempt to log the user in, we are going to try and auto login the user first. So in the same action enter the following. If this fails we will prompt for user's username and password.

		
			if  (gj_user_autologin()) // Auto login the user
{
    gj_session_open(); // Open the session
    alarm[0]=30*room_speed;
}
		
	

So above we are saying if the user has been logged in automatically we open the session. This part alarm[0]=30*room_speed; will be explained in more detail later, but basically in the alarm event we are going to ping the session. If we didn't ping the session within 120 seconds the session will automatically close, so this is important.

Now extend the conditional if statement further, so it appears as below. So if the user cannot be logged in automatically you must ask the user for their GJ credentials

		
			if (gj_user_autologin()) // Try and auto login the user
{
    gj_session_open();
    alarm[0]=30*room_speed;  // ping every 30 seconds
}
else // if you cannot auto login the user
{
    userDialog[0] = get_string_async("Username:","");
}
		
	

The next thing to do is to add a "Dialog" event, as this will be triggered when you call the get_string_async() function. In this event enter the following code.

		
			var i_d;  
i_d=ds_map_find_value(async_load,"id");  
if(i_d==userDialog[0])  
{  
    if(ds_map_find_value(async_load,"status"))  
    {  
        if(ds_map_find_value(async_load,"result") !="")  
        {  
            user=ds_map_find_value(async_load,"result");  
            userDialog[1]=get_string_async("Token:","");  
            userDialog[0]=-1;  
        }  
    }  
}  
if(i_d==userDialog[1])  
{  
    if(ds_map_find_value(async_load,"status"))  
    {  
        if(ds_map_find_value(async_load,"result") !="")  
        {  
            token=ds_map_find_value(async_load,"result");  
            request[0]=gj_user_login(user,token);  
            userDialog[1]=-1;  
        }  
    }  
}
		
	

When you have done this in the "Async HTTP" event execute the following code.

		
			var result = gj_result_id();

if (result == request[0])
{
    if(gj_result_status())
    {
        gj_session_open();
        alarm[0]=30*room_speed;
     }
}
gj_result_clear()
		
	

Important note: Always call gj_result_clear() at the end of the Async HTTP event to prevent any possible memory leaks.

Note: Remember to initialise your variables before using them! In the create event I would advise you to do this. For request[0] assign -1 to it, so you have request[0] = -1 (You must do this for each slot in arrays).

Now add an alarm and make sure it is the alarm 0 event. This is where we are going to ping the session. In this alarm execute the following:

gj_session_ping(true);

alarm[0]=30*room_speed; // To continue pinging

The user should now be be able to login to the API through your game. This part is essential if you want to go onto the next sections.

Adding trophies

This section will depend on your game, as you determine when a trophy is achieved in your game. I will just go through how to set a trophy as achieved.

At the point of where you want the trophy to be achieved in your game execute the following.

		
			gj_trophy_add(Trophy ID);
		
	

Just change the argument to the ID of the trophy you want achieved in your game and make sure it is a string. To get the trophy ID go to Dashboard -> Your Games -> Game API -> Trophies then copy the trophy ID you want to be achieved. Below is an image showing this:

5d099b20ddc4d.png

And here is an example of gj_trophy_add being used.

		
			// Step event 
if (score > 100)
{
    gj_trophy_add("2340");
}
		
	

Note: When passing your Id number to gj_trophy_add you must make sure you pass as a string, NOT a real number.

Adding scores

Like trophies, how you do this will be entirely up to you. But I will show you how to post scores into your game's leaderboards. Below is the function you call in your game to post a user's score.

		
			gj_scores_add(string table_id, string scorestring, real score_sort, string extra_data);
		
	

Let me break this function down. The first argument " stringtable_id is where you add the table you wish to post the score to. To find your table ID go to Dashboard -> Your Games -> Game API -> Scores you should then see the table ID next to the table.

5d099b219abf1.png

Then in the second argument " stringscorestring " you enter the score string, so just enter the variable thats storing the score. In the third argument " realscore_sort " you enter the value that will manage the order of your scores in the table; the score variable can go here. And for the fourth argument " stringextra_data " you can enter any extra data you wish to store; but this will not show on your game page, you may leave this as blank quotations. However extra data can help identity the integrity of an added score.

Below is an example of how to use this.

		
			if (gj_user_isloggedin())
{
    request[1] = gj_scores_add("1234", string(score)+"points", score,"");
}

//in the Async HTTP event**
if (result == request[1])
{
    if(gj_result_status())
     {
         show_message_async("successfully added score to list");
     }
     else
     {
         show_message_async(gj_result_error()); 
     }
 }
		
	

Get scores

To fetch scores from leaderboards in your game you need to call this function shown below.

		
			gj_scores_get(TableID, score num)
		
	

For the first argument of this function you enter the ID of the table you wish to fetch scores from, make sure this is a string. And for the second argument you enter the number of scores you wish to fetch, this is a real number not a string.

Note: The maximum amount of scores you can fetch is 100

Below is an example to demonstrate it's use:

In the Game Start event

		
			request[2] = gj_scores_get("1234", 10); // Fetch 10 scores from this table
		
	
		
			for(i = 0; i<10; i++)  
{  
    score_name[i] = "Name";
    score_display[i] = 0;   
}
		
	

In the Async HTTP event

		
			if (result == request[2])
{
    if(gj_result_status())  
    {
        var i;
        for(i = 0; i < gj_result_data_count(); i++)  
        {
            score_name[i]= gj_result_data_field_num("display_name",i);
            score_display[i]=gj_result_data_field_num("score",i);
        }
    }
}
		
	

For more information these functions I advise you check through the documentation.

Draw scores

To draw the scores you just need to loop through the arrays score_name score_display you populated in the previous section of this tutorial. For example:

Draw event

		
			// Draw the top 10 scores
var i;

for(i=0; i<10; i++)
{
   draw_text(10, i*15, "Place " string(i+1) + ". Score by: " + score_name[i] + " Score: " +   score_display[i]);
}
		
	

You would have to tweak the above to suit your game, but this is how you would display your fetched game scores.

Conclusion

Hopefully now you should have users to be able to login to GJ through your game and achieve trophies and highscores. If you have any further questions or suggestions then please leave a reply and I will see if I can solve it.


Page 1 of 301 replies.

over 11 years ago

So far so good for the login and acheivements, the highscore stuff does nothing that i can see, can you also add some draw event stuff? and the GJ_Controller (should this be persistent) or only run once when the game starts.

over 11 years ago

Just made a few fixes to this tutorial, hopefully it should be easier to read now and explain some sections a little better than before. Please could you let me know if you find anything that needs to be changed or updated, thankyou.

over 11 years ago

Well It did something different. Created a GJ_CONTROLLER object, placed everything in it required, set it to persistant, and now my main menu room is a black screen.

Any suggestions. Kinda funny, that I'm actually excited about seeing nothing. :) lol

EDIT:

I changed the object from persistant to normal, and set the room to persistant, but still just a black screen.

Last modified on February 16, 2014 by Cool_Flow @Cool_Flow

over 11 years ago

Ok that is weird, did it only do this when you placed the object in the room? Or did you make any other changes. And what was it doing at first? Did the API work at all?

over 11 years ago

The game worked fine until I placed the object in the room, and set the room to persistant.

I un-set the room of persistant, but still nothing.

Once I removed the GJ_CONTROLLER object from the main menu, the game started working again.

over 11 years ago

Well I narrowed the problem down to the Game Start Event in the GJ_CONTROLLER object. I commented out that section of code, and the game works again, with the room persistant, and the GJ_CONTROLLER object in the room.

EDIT:

Narrowed it down even more. The commented out everything in the Game Start Event except the gj_init("12345","3489udfgjkhe4t9u340jeio34"), and the game still works. I'll keep ya posted.

Last modified on February 16, 2014 by Cool_Flow @Cool_Flow

over 11 years ago

Thank you for creating this tutorial! I will add this to the API.

Sorry for my late reply, I completely forgot about replying!

over 11 years ago

No problem, I hope this helps and thankyou for adding this to the API!

Also no worries, i'm glad you replied to this, anyway cya around.

over 11 years ago

Ye i wanna thank u too. just one thing, how do i make score submit in html game game maker html 5?

over 11 years ago

@MostafaEmgiem

Does it not already work, if not what have you got so far?

over 11 years ago

My HTTP event is failing. Perhaps I'm not getting the gj_result_id? Anyway, here's the error:


############################################################################################
FATAL ERROR in
action number 1
of Web Event: HTTP
for object obj_GJ:

Push :: Execution Error - Variable Get -1.request(100021, 0)
at gml_Object_obj_GJ_Other_62 (line 2) - if(resultid==request[0]) //If it was user information.
############################################################################################

over 11 years ago

@Nomadic

Maybe "request" needs to be initialised. Just try assigning a value to the variable in the "Create" event. If that doesn't work, would you mind uploading code where the error is occurring please.

over 11 years ago

i dont know why but im getting this error:


############################################################################################

FATAL ERROR in

action number 1

of Other Event: Game Start

for object obj_gamejolt_control:

Push :: Execution Error - Variable Get -1.myprivatecode(100029, -1)

at gml_Object_obj_gamejolt_control_Other_2 (line 1) - gj_init(gameid,myprivatecode);

############################################################################################

over 11 years ago

@WrongTurn Entertainment

Are the variables both strings, for the "gj_init()" arguments? Make sure they are not entered as real numbers. Check what they are storing and check if they have been initialised before you call "gj_init()".. If my answer doesn't help you then upload what you got in that object and then point out any changes that may need to be made. Or if I can't see the issue I will do some research on it, because this isn't the first time of i've heard of this error.

Also if there is anything there above you're unsure of, then please ask about it before uploading your code.

Last modified on May 25, 2014 by Dr. Bowen @ttbowen

over 11 years ago

Is there a version for game maker 8.0 lite or pro?

over 11 years ago

@Ganonumples McBumples

Yes there certainly is and I also wrote a tutorial for that version as well. Below is a link to the tutorial:

http://gamejolt.com/community/forums/topics/game-maker-adding-achievements-complete-beginners-guide/2309/ <- there is also a link to the Game Maker 8.0 version as well, in this tutorial.

over 11 years ago

Just so I am aware, do I have to use the pro version for it to work?

over 11 years ago

Yes you do I believe in order to use the extensions. I know this because someone asked a similar question before and when I installed the 8.0 version I couldn't use it because I didn't have a pro license for that version, as I have GM 8.1 Standard.

over 11 years ago

Ah, okay thanks. It's just that I'm only using the lite version for one of my games as a little challenge. I guess I'll forgo the trophies for now. :)

about 11 years ago

FATAL ERROR in

action number 1

of Other Event: Game Start

for object GameJolt:

DoAdd :: Execution Error

at gml_Script_gj_session_open (line 4) - var url="http://gamejolt.com/api/game/v1/sessions/open/?game_id="+gj_gameid+"&username="+gj_user+"&user_token="+gj_token;

############################################################################################


stack frame is

gml_Script_gj_session_open (line 4)

called from - gml_Object_GameJolt_StartGameEvent_1 (line 8) - gj_session_open(); // Open the session

about 11 years ago

Make sure the variables used in the API call have been initialized and are using the correct types; which all need to be strings. I would upload what you have in each event for the GameJolt object.

about 11 years ago

Sadly I'm still having problems. Here's what it says.


############################################################################################

FATAL ERROR in

action number 1

of Other Event: Game Start

for object GameJolt:

DoAdd :: Execution Error

at gml_Script_gj_session_open (line 4) - var url="http://gamejolt.com/api/game/v1/sessions/open/?game_id="+gj_gameid+"&username="+gj_user+"&user_token="+gj_token;

############################################################################################


stack frame is

gml_Script_gj_session_open (line 4)

called from - gml_Object_GameJolt_StartGameEvent_1 (line 7) - gj_session_open(); // Open the session

about 11 years ago

All I can say now is to upload what you got so far. Because I can't tell how the issue is being caused without being able to see your code. PM is always the best place to show me.

Last modified on June 15, 2014 by Dr. Bowen @ttbowen

about 11 years ago

Could I perhaps skip the logging in? If I could, What features would still be aviable

about 11 years ago

The following things would still be possible:

  • Highscores, but only with guest names.

  • Global data storage

  • User data fetch

I do recommend making a login system though if you want highscores, because it's much nicer to see actual users on the highscores. If you want any other features (including trophies, for example), logging in is required.

about 11 years ago

Yes you can do all the above, as long as you have an active session open. Anyway why skip the user log in?

about 11 years ago

I don't think you even need to have a session open; sessions are only used to show if an user is playing (and if yes: what game). You can do anything else without using sessions.

about 11 years ago

Maybe you don't then, thanks for correcting me on that. But I would recommend not to skip user login, especially if you want trophies in your games.

about 11 years ago

Why am i getting these compile errors?

In Script GJ_session_close at line 9 : unknown function or script httprequest_create

In Script GJ_session_close at line 11 : unknown function or script httprequest_connect

In Script GJ_session_open at line 6 : unknown function or script httprequest_create

In Script GJ_session_open at line 8 : unknown function or script httprequest_connect

In Script GJ_session_ping at line 6 : unknown function or script httprequest_create

In Script GJ_session_ping at line 17 : unknown function or script httprequest_connect

Compile Failed - Please check the Compile window for any additional information

EDIT: Nevermind, I had an older Game Maker API extension in my project.

Last modified on July 22, 2014 by fajksdfkajsaksjdfh @fajksdfkajsaksjdfh

about 11 years ago

Hoi Florien, and hi Bowenware! I will edit down this rather long post when my stuff is fixed, but for now, I have a question. I am currently getting this error and I'm not sure why:


############################################################################################

FATAL ERROR in

action number 1

of Async Event: HTTP

for object o_gamejolt:

DoAdd :: Execution Error

at gml_Object_o_gamejolt_WebAsyncEvent_1 (line 35) - highscorestring = gj_result_data_field("score") + " by " + gj_result_data_field("display_name")

############################################################################################

Hopefully it's only one error and there won't be more, but I'm not so sure. Here are my events in the o_gamejolt object and the code inside of them:

CREATE EVENT

// Initialize.

gj_init("30998", "blarg")

// Dialogue with the player.

asyncdialog[0] = -1

asyncdialog[1] = -1

asyncdialog[2] = -1

// Trophies.

for (i = 0; i <= 13; i++)

{

		
			senttrophy[i] = false
		
	

}

// Other variables.

loggedontimes = 0

user = ""

token = ""

loginrequest = -1

httprequest[0] = -1

httprequest[1] = -1

httprequest[2] = -1

httprequest[3] = -1

prevscore = 0

currusertype = "user"

highscorestring = "(Loading)"

// Retrieve the current high score (retrieve 10 total scores).

httprequest[1] = gj_scores_get("32212", 10)

// Try to log in automatically.

if (gj_user_autologin())

{

		
			// Initialize the session.

gj_session_open()

alarm[0] = room_speed*30 // Ping.

// Check if the user was logged in earlier.

httprequest[2] = gj_datastore_get_user("logincount")

httprequest[3] = gj_user_info_fromusername(gj_user_username())
		
	

}

ALARM 0 EVENT

// Ping the session.

gj_session_ping(true)

alarm[0] = room_speed*30 // Continue to ping every 30 seconds.

STEP EVENT

// Add trophies.

// Survivalist 1: Survive 10 waves.

if (!senttrophy[0] && o_control.wave == 10 && enemiesInQueue == 0) {senttrophy[0] = true; gj_trophy_add("10076")}

//blahblahblah more trophies

// Add scores.

if (score > prevscore)

{

		
			if (gj_user_isloggedin())

{

    // Send the user's score.

    httprequest[0] = gj_scores_add("32212", string(score) + " pts", score, "")

    prevscore = score

}

else

{

    // Send score after guest provides a name.

    asyncdialog[2] = get_string_async("Please enter a name to send your score under.", "")

}
		
	

}

HTTP EVENT

// Check result id.

var resultid = gj_result_id();

// If this was the login request...

if (resultid == loginrequest)

{

		
			if (gj_result_status())

{

    // Login was successful. Initialize session.

    gj_session_open()

    alarm[0] = room_speed*30 // Ping every 30 seconds.

    // Check if the user was logged in earlier.

    httprequest[2] = gj_datastore_get_user("logincount")

    httprequest[3] = gj_user_info_fromusername(gj_user_username())

}

else {show_message_async(gj_result_error())}
		
	

}

else if (resultid == httprequest[0])

{

		
			// Score successfully added to list.

if (gj_result_status())

{

    show_message_async("Score added.")

    httprequest[1] = gj_scores_get("32212", 10) // Get the new high score (retrieve 10 total scores).

}

// Adding score failed.

else {show_message_async(gj_result_error())}
		
	

}

// If we just received the high score...

else if (resultid == httprequest[1])

{

		
			if (gj_result_status())

{

    // Display high score.

    highscorestring = gj_result_data_field("score") + " by " + gj_result_data_field("display_name")

    //If there were multiple highscores, the following code would return the 2nd highscore.

    //gj_result_data_field_num("score",1)

    //And you would be able to check how many highscores there are with:

    //gj_result_data_count()

}

else {highscorestring = "(error)"}
		
	

}

// If we just received the login count...

else if (resultid == httprequest[2])

{

		
			var status = gj_result_status();

if (status == 1)

{

    // The user logged in already. Check the login count and increment by one.

    gj_datastore_update_user("logincount", "1", "add")

    // Check how many times the user logged in before.

    loggedontimes = real(gj_result_data_field("data"))

}

else if (status == 0)

{

    if (gj_result_error() == "No item with that key could be found.")

    {

        // Item did not exist, so the user did not log on earlier. Increment login count by one.

        gj_datastore_set_user("logincount", "1")

    }

}
		
	

}

// If we just received user information...

else if (resultid == httprequest[3])

{

		
			if (gj_result_status())

{

    currusertype = gj_result_data_field("type") // Load user type.

}
		
	

}

gj_result_clear()

DIALOG EVENT

// All dialog in the game.

var i_d;

i_d = ds_map_find_value(async_load, "id");

if (i_d == asyncdialog[0]) // The username dialog closed.

{

if (ds_map_find_value(async_load, "status"))

{

		
			  if (ds_map_find_value(async_load, "result") != "")

  {

     user = ds_map_find_value(async_load, "result");

     asyncdialog[1] = get_string_async("Please enter your GameJolt user token.", "")

     asyncdialog[0] = -1

  }
		
	

}

}

if (i_d == asyncdialog[1]) // The token dialog closed.

{

if (ds_map_find_value(async_load, "status"))

{

		
			  if (ds_map_find_value(async_load, "result") != "")

  {

     token = ds_map_find_value(async_load, "result");

     loginrequest = gj_user_login(user, token)

  }
		
	

}

}

if (i_d == asyncdialog[2]) // The guest name dialog closed.

{

if (ds_map_find_value(async_load, "status"))

{

		
			  if (ds_map_find_value(async_load, "result") != "")

  {

     httprequest[0] = gj_scores_add_guest("32212", string(score) + " pts", score, "Some extra data here.", ds_map_find_value(async_load, "result"))

     prevscore = score

  }
		
	

}

}

PRESS 'Q' EVENT

// Login, though may be unnecessary.

if (asyncdialog[0] == -1) && (!gj_user_isloggedin())

asyncdialog[0] = get_string_async("Please enter your GameJolt username.", "")

Any help is super appreciated! :D

Last modified on August 1, 2014 by Icedrake @Icedrake