Steam achievements in Java(libgdx). Step by step for dummies.
Steam achievements in Java(libgdx). Step by
step for dummies.
In this
tutorial I will present in a rather comprehensive way the implementation of
Steam's achievements in Java. I used the libgdx framework, but the tutorial
should apply regardless of the technology used, if it is java.
The
tutorial was tested only for a desktop game (Windows). I must also mention
that I tested adding achievements on a game that had been already released, but the same
procedure should also apply to a game in a beta state. In addition, I used
Eclipse and the standard structure of projects for libgdx, i.e. 3 projects
for desktop, core and android (which contains the assets folder).
1. Steam achievements
Achievements
are a Steam's service that allows you to extend the functionality of our game. It's
something to strive to "achieve". Instead of doing the main campaign
you can also attempt to get the achievements which add to your game completion
on your steam profile. This is a simple way to increase the value and vitality
of our game.
2. Before we start - links
Before we
start the implementation of achievements, please read the official documentation
about the achievements provided by Valve. It is available here:
and here
However, it
should be emphasized that the above documentation is quite useless, especially
from the point of view of Java. Even the Step by Step guide does not explain
exactly what steps to take.
3. What we need -
libs
We will
need two types of libraries to take advantage of the achievements. First it’s
the core library that connects to Steam service. It is available here:
We need to
download the Standalone version. Direct link is here:
At the time
of reading the version may be higher.
In the
archive you will find 3 folders:
Linux OSX-64
Windows64
Windows32
We are only
interested in folders for WIN. Inside each folder you will find three files:
for Win32
steam_api.dll
steam_appid.txt
Steamworks.Net.dll
for Win64
steam_api64.dll
steam_appid.txt
Steamworks.Net.dll
From these
two folders we will need 3 files: steam_api.dll, steam_api64.dll and
steam_appid.txt.
Let's
create a 'steam' folder (the name actually does not matter) wherever we want
(later we copy it to the assets folder) and copy these files there. The txt
file is a simple text file containing the id of our application.
The second
library is of course a library for Java. It is available here:
On the
page, click on the ‘Clone or Download’ button and then ‘Download ZIP’. Among all
folders, we will only be interested in "java-wrapper". More
specifically, we are interested in libraries from the 'java-wrapper \ src \
main \ resources' folder:
libsteamworks4j.dylib
libsteamworks4j.so
steamworks4j.dll
steamworks4j64.dll
As I mentioned
earlier, this is a tutorial for Windows, so we will only need the last two
files. If our goal is OSX or Linux, I suspect that you should also copy the .so
and .dylib files. As for Windows, we will need both steam_api files regardless of the version of our game. The files will be copied to the previously
mentioned 'steam' folder.
At this
stage, we should have the 'steam' folder with the following content (files for
OSX and Linux are optional).
steam_api.dll
steam_appid.txt
steam_api64.dll
steamworks4j.dll
steamworks4j64.dll
libsteamworks4j.dylib
libsteamworks4j.so
The second
thing we need from steamworks4j is the source code that will allow us to
connect to libraries and the Steam service. It is here "java-wrapper \ src
\ main \ java". We need to copy the whole 'com' folder to our source folder,
ie 'src'. In my case, the 'src' folder is in the libgdx 'core' project.
Of course,
we also need the Steam Client installed on the computer and our own game in the
library. It will be available without buying if you log in from the developer
account. It does not have to be released. It is also not required for the game to
be uploaded on Steam for testing.
UPDATE !!!
It turns
out that all libraries are available via Maven from this link.
We need to
download one jar file. At the time of writing, the latest version is 1.8.0.
Direct link here:
To get the
dlls just open the jar file (you can simply change the extension to .zip and use
WinRar or 7zip).
The jar
file contains the steamworks4j classes and all necessary dll libraries.
However, this jar can not be used to get rid of the 'steam' directory with
libraries. You can only replace the source code in the 'src' directory by copying the jar file to the 'libs' directory
of the android project. In Eclipse, you should also point out this jar file - Eclipse
-> Right click Core Project -> Properties -> Java Build Path -> Add
external jars.
4. Placement of the libraries
The 'steam'
folder you created earlier should be placed directly in the assets folder. If
you use a configuration like me, this folder is in the Android project. So the
assets folder should look like this:
-assets
--models
--images
--icons
--steam
...
5. App id and steam_appid.txt
The text
file which we have placed in the 'steam' folder contains the number 480, which
is supposed to be the id of Steam’s test application. We need to change this
number to the id of our application.
This number can be found by logging in to Steamworks
(https://partner.steamgames.com) where in the list of applications we have an
item such as Game (123456). This number is also available in the link to the
game’s page on Steam, e.g. https://store.steampowered.com/app/123456. Therefore,
we put only this number in the file.
We also
need to change the location of this file. It can not be in the 'steam' folder.
We have to move it up to the assets folder directly. So the assets folder
should look like this:
-assets
--models
--images
--icons
--steam_appid.txt
--steam
...
I should
make one note here. According to the documentation, the steam_appid.txt file is
for testing only and should be removed in the production version. Of course,
there is no need to delete it, because it does not contain any secret
information. However, I noticed that without this file you can not run
achievements even in the production version (more about this below). I do not
know if it is always or only in the version of the game using steamworks4j.
6. Creating achievements
At this
point, we should have all the files needed to implement the achievements. Achievements
in Steam are defined by a graphic element (icons) and a text element (name and
description). Each achievement requires two icons (achieved and unachieved) in
jpg format at 64x64 resolution. It is recommended that the unachieved icon was
gray and achieved in color.
In
addition, for each achievement, we need to come up with its name and a brief
description of how to get a certain achievement. Both elements will be visible
to the player from the level of the Steam Client. Also, you should come up with
a unique identifier for each achievement, through which we will identify the
achievement from the code level. The identifier is a String variable. It is
suggested to use capital letters and snake_case eg ACHIEV_BATTLE_1, ACHIEV_BATTLE_2
etc.
We have
been doing things offline so far. To define achievements, we must log in to our
Steam partner account (https://partner.steamgames.com). Then choose our
application. In the main window we're looking for a link to define achievements.
Now, for
each achievement, click the blue 'New Achievement' button, fill in the form and
press Save.
Warning!
Unfortunately, there is a bug (or feature) is the Steam platform which appears
when you try to save an achievement. While completing the form, you can not
upload icon files. An error occurs during the attempt. To fix it - first, fill
out the form without icons, Save, then click on the Edit button and point to
the icons and Save.
After
defining all the icons, you MUST
publish the changes. Otherwise, the achievements will not be visible to us. To
make the achievements visible in the Steam client you have to log out and log
in again to the Steams Client(not the web platform). If this does not help,
please try uninstalling and reinstalling your game.
7. Code - preparation
Having all
libraries and defined achievements, you can proceed to implement achievements
in our game. The use of achievements in the game consists of three stages:
- loading
libraries and initiation
- settings achievement
- closing libraries
While the
first stage is necessary, the last one does not seem to be a must. Of course,
you can set an unlimited number of achievements between start and closing.
Initiations and closing are carried out once in the game. I set the initiation in the main menu (right after the Loading page), and closing when leaving the
game also from the main menu.
Warning! When testing achievements in the game, you MUST have the Steam client switched on.
It seems that the game connects to the client and not directly to the Steam
servers.
Testing setting
of the achievement can be done in the IDE without the need to compile and put
the game on the Steam.
8. Code – the class
Let's start
creating of the code by defining a general class. It will be very simplified
code, especially when it comes to exceptions. The client is defined as a static
class in the main menu.
The class
will be called SteamClient:
public class SteamClient {
}
Then we
will add the necessary imports. Of course, they refer to the previously added
source code.
import com.codedisaster.steamworks.SteamAPI;
import
com.codedisaster.steamworks.SteamException;
import com.codedisaster.steamworks.SteamID;
import
com.codedisaster.steamworks.SteamLeaderboardEntriesHandle;
import
com.codedisaster.steamworks.SteamLeaderboardHandle;
import com.codedisaster.steamworks.SteamResult;
import
com.codedisaster.steamworks.SteamUserStats;
import
com.codedisaster.steamworks.SteamUserStatsCallback;
import com.codedisaster.steamworks.SteamUtils;
Then we
will add 3 variables:
private SteamUtils
utils;
private SteamUserStats userStats;
public boolean isOnline=false;
Next, we
need to define an ID for each achievement. As mentioned above, it is a String
value. We define them as static variables.
static public String achievBattle1SteamId=
"ACHIEV_BATTLE_1";
static public String achievBattle2SteamId=
"ACHIEV_BATTLE_2";
static public String achievBattle3SteamId=
"ACHIEV_BATTLE_3";
...
In
addition, we must define one callback within our SteamClient class. We do not
have to handle the callback methods in any way.
SteamUserStatsCallback
steamUserStatsCallback=new SteamUserStatsCallback()
{
@Override
public void onUserStatsReceived(long gameId, SteamID steamIDUser,
SteamResult
result)
{
}
@Override
public
void onUserStatsStored(long gameId, SteamResult result) {
}
@Override
public
void onUserStatsUnloaded(SteamID steamIDUser) {
}
@Override
public
void onUserAchievementStored(long gameId,
boolean
isGroupAchievement, String achievementName,
int
curProgress, int maxProgress) {
}
@Override
public
void onLeaderboardFindResult(SteamLeaderboardHandle leaderboard,
boolean
found) {
}
@Override
public
void onLeaderboardScoresDownloaded(
SteamLeaderboardHandle
leaderboard,
SteamLeaderboardEntriesHandle
entries, int numEntries) {
}
@Override
public
void onLeaderboardScoreUploaded(boolean success,
SteamLeaderboardHandle
leaderboard, int score,
boolean
scoreChanged, int globalRankNew, int globalRankPrevious) {
}
@Override
public
void onGlobalStatsReceived(long gameId, SteamResult result) {
}
};
In the next
step, we define three basic methods corresponding to the 3 required stages.
public boolean initAndConnect(){};
public boolean setAchiev(String achivName) {};
public void
disconnect(){};
9. Code - initiation
The first
initAndConnect () method must properly initiate the Steam libraries.
Therefore, it must contain the following code (loading the libraries is carried
out with a parameter indicating the name of the folder where you previously
placed the dll libraries):
try {
SteamAPI.loadLibraries("./steam");
}
catch
(SteamException e1)
{
System.out.println("Load libraries
error");
}
Initiation of a connection with the Steam client:
try
{
if (!SteamAPI.init())
{
System.out.println("Initialisation
failed");
isOnline=false;
return
false ;
}
}
catch (SteamException e)
{
e.printStackTrace();
}
Getting the
client achievements statistics. Here we indicate the previously defined
callback:
//Get stat object
userStats = new SteamUserStats(
steamUserStatsCallback);
//A must
before setting achievements
userStats.requestCurrentStats();
Setting the
flag to indicate that the library initiation has been done correctly.
isOnline=true;
10. Code - setting an achievement
To set an
achievement, simply call the setAchiev (String achivName) method anywhere in
the game with an appropriate achievement name. We have previously defined the
names in the class using static identifiers. In addition, the call of the
method is conditioned by the correct initiation of libraries.
Our method:
public boolean setAchiev(String achivName)
{
try {
//set
userStats.setAchievement(achivName);
//save
boolean result=userStats.storeStats();
return result;
}
catch ( Exception e) {
isOnline=false;
return false;
}
}
And the call:
//Steam
if(SteamClient.isOnline)
SteamClient.setAchiev(SteamClient.achievBattle1SteamId );
And that’s
pretty much all that is necessary for setting achievements.
UPDATE !!! The lack of an immediate appearance of the
achievement in Steam Client is the result of not calling storeStats() after
setAchievement (). If this method is called correctly, the achievement will
immediately appear in the Steam Client.
11. Disconnection
To disconnect,
you must call the shutdown method. As mentioned, this is not absolutely
necessary.
public void
disconnect()
{
SteamAPI.shutdown();
}
12. Deployment
When
testing achievements in IDE, everything will work properly. Unfortunately, if
we create a jar file (Runnable Jar) our game will either crash or the
achievements will not be set. The problem is the location of the 'steam' folder
and the steam_appid.txt file. As indicated earlier, the 'steam' folder and txt file
are in the assets folder (and now inside the jar file). Unfortunately, the game
can not see them. Therefore, you need to copy the ‘steam’ folder and
steam_appid.txt file and place it next to the file that starts our game. To clarify
how it should be done, it will show my game file structure. In my game folder,
I only have an exe file and a lib folder containing jre and the jar of my game.
Exe calls only jar file by referring to java.exe. It looks like this:
-MyGame
--mygame.exe
--lib
So if you
want to add the 'steam' folder and the steam_appid.txt file, they must be
located directly in the MyGame folder. They should be copied and not removed from the assets folder.
-MyGame
--mygame.exe
--lib
--steam
--steam_appid.txt
With this
configuration, the achievements should work in production.
UPDATE!!!
According
to the author's note (code-disaster): “The
probable cause for this is how you launch your game. On startup, the Steam
client "injects" into the game's process. If your executable just
invokes "java -jar ..." and exits, there's nothing left to identify
your application on Steam.init() - it's a new, pristine process, and needs to
look for steam_appid.txt as a fallback.”.
13. Errors
Below I
present the most common errors (both during testing and in production).
A. Can not find
libraries.
Exception in thread "LWJGL
Application" com.badlogic.gdx.utils.GdxRuntimeException:
java.lang.UnsatisfiedLinkError: Can not load library: .... \ android \ assets \
steam \ steam_api.dll
During testing this error will occur if we do not place the steam folder
in the assets folder. In production, this error will occur if we do not put the
folder next to the exe file. In addition, an error occurs when the
SteamAPI.loadLibraries ("./ steam") method indicates an invalid folder
name.
B. Win32 and Win64
Exception in thread "LWJGL
Application" com.badlogic.gdx.utils.GdxRuntimeException:
java.lang.UnsatisfiedLinkError: ... \ android \ assets \ steam \ steam_api.dll:
Can not load AMD 64-bit .dll on a IA 32-bit platforms
This error will occur when we put the steam_api.dll file only for the
Win32 version or vice versa.
Warning! In my case testing in Eclipse
required Win32, but in the production the Win64. In both cases I used the same
computer. I do not know what is causing this. Maybe I'm using the old version
of Eclipse.
C. SteamAPI.init ()
returns false
This error is caused by the Steam Client applications failing to log in
to their account during testing. In addition, it results from the lack of the
steam_appid.txt file both in the assets folder and the main folder next to the
exe file.
D. Not loaded libraries
com.codedisaster.steamworks.SteamException:
Native libraries not loaded.
Ensure to call
SteamAPI.loadLibraries () first!
at
com.codedisaster.steamworks.SteamAPI.init (SteamAPI.java:44)
Exception in thread "LWJGL
Application" com.badlogic.gdx.utils.GdxRuntimeException:
java.lang.UnsatisfiedLinkError:
com.codedisaster.steamworks.SteamAPI.getSteamUserPointer
() at com.badlogic.gdx.backends.lwjgl.LwjglApplication $ 1.run
(LwjglApplication.java:133)
Caused by:
java.lang.UnsatisfiedLinkError:
com.codedisaster.steamworks.SteamAPI.getSteamUserPointer ()
This error occurs when we try to call the SteamAPI.init () method before
calling the SteamAPI.loadLibraries ("./ steam") method.
It seems that the earlier version of the Java API did not require a calling
of SteamAPI.loadLibraries () which could be the source of the problem.
14. The end
I hope that
the tutorial will be helpful. If you want to see my game in which I implemented
achievements according to this tutorial, check this link:
Follow me
also on Twitter:
15. Helpful links
Then, for each win, they'll improve their winnings up to as} x10 by getting lucky with the traditional threat sport. In other words, betting on a mobile app is evident, easy and as easy as typing 1xbet on a team and a betting amount. In addition, we guarantee our casino site is a safe iGaming environment with out of this world bonuses and promotions.
OdpowiedzUsuń