• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Steam API Achievements don't work

S

SoulPixel

Guest
Hey,
I can't find anything on the internet so I'll just try it here.

I want to enable steam achievements for my game.
- My game is on steam (But not buyable)
- Steam sdk is set up in game maker
- Steam is enabled and I entered the app ID

I have an achievement set up and tried to call this command steam_set_achievement("KILL_FIRST_SLIME");
I followed the instructions on the official documentary but nothing happens.
Steam shift tab works and I can do everything except for achievements.
They don't even show up on the steam page in my library so maybe that's a problem?

Tried to draw the vars and check them out.
steam_initialised() gives a 1
steam_is_overlay_enabled() gives a 1
steam_stats_ready() gives a 0

So, I guess the steam stats are not ready?
I have no clue what to do haha.

Maybe you awesome people can help!
 

Phil Strahl

Member
Hey SoulPixel!

I went through my own code of handling achievements and my hunch is that you need to either
  • wait for steam_stats to become available by repeatedly checking Steam,
  • or that you also need to check if the steam_user is true, as achievements are tied to users
I don't know about your code structure and layout, but I can show you how it works for my game in principle, maybe that helps you or others too.

So when I start the game I set a whole bunch of global variables, among them flags for the status of Steam. That's not the best way to do this, but it worked for over two years now with my game, so I am not complaining ;)

Code:
  // setting global variables in an initialization script that runs when the game launches

  global.steam_api_is_ready    = false
  global.steam_stats_are_ready = false
  global.steam_overlay_is_on   = false
  global.steam_user_online     = false
  global.steam_persona_name    = "Player"
  global.steam_user_id         = -1
  // etc.
After the game starts and is on the first room with the title screen, I have a condition that runs only once with the first step event. Inside, I call two scripts, f_checkSteam() and f_achievementsRead()

Code:
//step event of the title screen object

// ...

if (do_init)
{
  if (f_checkSteam())
  {
    f_achievementsRead()
    try_steam_again = false
  }
  else
  {
    try_steam_again = true
  }

 // more init stuff
  do_init = false
}

// ...
When f_checkSteam() returns "false", later down I set an alarm (when try_steam_again == true) that will try again to call Steam via f_checkSteam() after a timeout of 30 seconds in my case.

Important: Don't call on Steam too often, like once a second, as too many request will have you go "over quota", meaning that Steamworks won't respond to the caller anymore. That's frustrating during development, but a minor catastrophe when launching your game and players can't upload their highscores/achievements/stats anymore because Steam is blocking them.

I am assuming that you already got code in place for setting and getting your achievements, but for anyone reading this post in the future,I will outline how I do it.

Let's look at the scripts in do_init now individually.

Here's the boiled down version of my f_checkSteam()
Code:
  /// @function f_checkSteam()
  var returnval = true // return value of the script
 
   // simple call to see if steam is running.
  global.steam_api_is_ready = steam_initialised();
 
  if (global.steam_api_is_ready)
  {
    // debug
    show_debug_message(scr+"Steam API ready")
     
    // checking for steam stats
    global.steam_stats_are_ready = steam_stats_ready();
    if (global.steam_stats_are_ready)
    {
      show_debug_message("Steam stats are ready")   
    }
    else
    {
      returnval = false;
      show_debug_message("Steam stats are NOT ready")  
    }
     
    global.steam_user_online = steam_is_user_logged_on();
   
    //// if you need to do something with a user's stats, it also goes into this script
    //// you would, of course, have to set the respective global variables first
    if (global.steam_user_online)
    {
      show_debug_message(scr+"Steam user logged in")   
       
      global.steam_persona_name = steam_get_persona_name();
      global.steam_user_id      = steam_get_user_steam_id();
     
      var user     = global.steam_persona_name;
      var steam_id = global.steam_user_id;
      show_debug_message("User "+user+" identified with SID "+string(steam_id));
       
      steam_offline = false;
    }
    else
    {
      returnval = false;
      show_debug_message("Steam User not logged on.");
    }
   
  } // end of "if steam api is ready"
  else
  {
    // here go all your fallback settings, e.g.

    /*
    global.steam_persona_name = "Player";
    global.steam_user_id = global.steam_id_fallback_key;
     
    show_debug_message("Steam API not ready. Using persona name "+global.steam_persona_name+", fallback Steam ID: "+string(global.steam_id_fallback_key));
    */
   
    returnval = false;
   
  } ;//end of if steam api ready

  return returnval;
So all that happens here is setting the global variables. Once the steam_stats are ready, my games calls f_achievementsRead() to read the current value of all achievements into some global variables. Like I said, not the most elegant, but it gets the job done:

The f_achievementsRead() script:
Code:
/// @func f_achievementsRead()
// Note: I am using global variables as keys so that I only have one string to correct
// should I change an achievement's API name in SteamWorks
 
  // achievement for launching the game:
  global.achievement_val_launchGame     = steam_get_achievement(global.achievement_key_launchGame);
 
  // in your case this would be something like
  global.achievement_val_killFirstSlime = steam_get_achievement("KILL_FIRST_SLIME")
  // ...assuming "KILL_FIRST_SLIME" is also the "API Name Progress Stat" within Steamworks.
 
  // etc. for any additional achievements
Now when I want to set an achievement, I just set its global variable, as simple as
Code:
  global.achievement_val_launchGame = true
That way I can "collect" a bunch of achievements and submit them to Steam at a later point. This is useful for situations such as this: You want to give your players an achievement for collecting all 5 coins in a level but grant it only "for real" when they also finished the level alive.

So when this should happen, the aptly titled script f_achievementsWrite() is called and submits all achievements to Steam.
Code:
///@desc f_achievementsWrite()
  // for each achievement:
  if (!global.achievement_val_launchGame)
  {
    steam_set_achievement(global.achievement_key_launchGame);
  }
 
  // etc.
Sidenote: In my title screen controller object, I also have an async event, "Async - Steam" which deals with upload and download of any relevant leaderboard data, should you have some. If you're interested in how to work with those, I wrote about it in this thread.
 
S

SoulPixel

Guest
Hey Phil,
first of all thank you for your reply!
I found my mistake, I just forgot to save my achievements in the steam direct panel haha.
But still it's really helpful to see how you did it.
Your code is soo structured, I'll definitly copy some of that.

Also I'm a huge fan of your ludum dare videos!
Did not think to meet you here hehe.
 

Phil Strahl

Member
You're welcome! And sure, go ahead and take what you need for your own projects, that's the point of sharing it :)

And thank you also for watching my videos! I'm mostly of the forums here when I am coding on my own projects and feel like procrastinating a bit
 
Top