Achievementy Steam w Java(libgdx). Przewodnik krok po kroku.
W
tym tutorialu przedstawię w dość całościowy sposób implementacji achiementów
Steama dla języka java. Używałem frameworka libgdx, ale tutorial powinien mieć
zastosowania bez względu na użytą technologie o ile to java.
Tutorial
był testowany tylko dla gry na desktop (Windows). Muszę także wspomnieć, że
testowałem dodawanie achievementów na grze już opublikowanej, ale ta sama
procedura powinna odnosić się także do gry w wersji beta. Ponadto używam
Eclipse i standardowej struktury projektu dla libgdx czyli 3 projekty desktop,
core i android (który zawiera katalog assets).
1. Steam achievements
Achievementy to usługa Steama pozwalająca na rozszerzenie funkcjonalności
naszej gry. To coś do czego dążymy w grze. Zamiast przechodzić główną kampanię
można także starać się osiągnąć achievementy, które są widoczne na twoim
profilu Steama. To prosty sposób na
zwiększenie wartości i żywotności naszej gry.
2. Zanim zaczniemy -
linki
Zanim
zaczniemy implementacje achievementów należy zapoznać się z oficjalną
dokumentacją na temat achievementów zapewnioną przez Valve. Dostępna jest tutaj.
oraz tutaj
Należy jednak podkreślić, że powyższa dokumentacja jest dość
bezużyteczna, zwłaszcza z punktu widzenia Javy. Nawet przewodnik Step by Step
nie wyjaśnia jakie dokładnie kroki należy podjąć.
3. Czego potrzebujemy
- libs
Musimy pobrać wersję Standalone. Bezpośredni link tutaj:
W momencie czytanie wersja może być wyższa.
W archiwum znajdziemy 3 katalogi.
OSX-Linux-64
Windows64
Windows32
Interesują nas tylko katalogi dla WIN. Wewnątrz każdego
katalogu znajdziemy trzy pliki
dla Win32
steam_api.dll
steam_appid.txt
Steamworks.Net.dll
dla Win64
steam_api64.dll
steam_appid.txt
Steamworks.Net.dll
Z tych dwóch folderów będziemy potrzebowali 3 pliki:
steam_api.dll, steam_api64.dll i steam_appid.txt.
Stwórzmy katalog 'steam' (sama nazwa nie ma akurat znaczenia)
gdziekolwiek chcemy (później przekopiujemy do katalogu assetów) i skopiuj tam
te pliki. Plik txt to zwykły plik tekstowy zawierający numer naszej aplikacji.
Druga biblioteka to oczywiście biblioteka dla Javy. Dostępna
jest tutaj
Na stronie należy kliknąć na przycisk Clone or Download, a
następnie Download ZIP. Pośród wszystkich katalogów będzie nas interesował
tylko "java-wrapper". Dokładniej interesują nas biblioteki z katalogu
'java-wrapper\src\main\resources' czyli:
libsteamworks4j.dylib
libsteamworks4j.so
steamworks4j.dll
steamworks4j64.dll
Jak
wspomniałem wcześniej to tutorial dla Windows dlatego będziemy potrzebować
tylko tych dwa ostatnie pliki. Jeżeli naszym celem jest OSX lub Linux
podejrzewam, że należy skopiować też plik .so i .dylib. Odnoście Windows
będziemy potrzebowali obu plików steam_api bez względu na wersję naszej gry. Pliki skopiujemy do
wcześniej wspomnianego katalog ‘steam’.
Na tym etapie powinniśmy mieć utworzony katalog ‘steam’ z
następującą zawartością (pliki dla OSX i Linuxa opcjonalnie).
steam_api.dll
steam_appid.txt
steam_api64.dll
steamworks4j.dll
steamworks4j64.dll
libsteamworks4j.dylib
libsteamworks4j.so
Drugą rzeczą, którą potrzebujemy ze steamworks4j to kod źródłowy, który umożliwi
nam połączenie z bibliotekami i usługą Steam. Znajduje się tutaj
"java-wrapper\src\main\java". Musimy skopiować cały katalog 'com' do
naszego katalogu ze źródłem gry tj. 'src'. W moim przypadku katalog ‘src’ znajduje
się w projekcie libgdx 'core'.
Oczywiście potrzebujemy także zainstalowanej na komputerze aplikacji
klienta Steam oraz własnej gry w bibliotece. Będzie ona dostępna bez kupowania
jeżeli zalogujemy się z konta deweloperskiego. Nie musi być opublikowana. Nie
jest także wymagane, aby gr już była załadowana na Steam.
UPDATE!!!
Okazuje się, że całość
bibliotek jest dostępna poprzez Maven z
tego linku:
Musimy pobrać jeden plik jar.
W momencie pisania najnowsza wersja to 1.8.0. Bezpośredni link tutaj:
Aby pobrać biblioteki dll
wystarczy otworzyć jara (można po prostu zmienić rozszerzenie na .zip i użyć WinRara
lub 7zip).
Plik jar zawiera klasy steamworks4j
oraz wszystkie potrzebne biblioteki dll. Jednak tym jarem nie można zastąpić katalogu
‘steam’ z bibliotekami. Jedynie zamiast umieszczania kodu źródłowego w katalogu
‘src’ można załączyć jara kopiując go do katalogu ‘libs’ projektu android. W Eclipsie
należy także wskazać tego jara - Eclipse -> Prawy klik Core
Project -> Properties -> Java Build Path -> Add external jars
4. Umieszczenie
bibliotek
Utworzony wcześniej katalog 'steam' należy umieścić w
bezpośrednio w katalogu assetów. Jeżeli używasz konfiguracji jak ja katalog ten
znajduje się w projekcie Android. Zatem katalog assets powinien wyglądać tak:
-assets
--models
--images
--icons
--steam
...
5. Id aplikacji i steam_appid.txt
Plik
tekstowy, który umieściliśmy w katalogu ‘steam’ zawiera numer 480, który ma być
numerem testowej aplikacji Steam. Musimy zamienić ten numer na numer naszej
aplikacji. Numer ten znajdziemy bez problem logując się Steamworks (https://partner.steamgames.com)
gdzie na liście aplikacji mamy pozycję np. Game (123456). Numer ten jest także dostępny w linku do strony na Steam
np. https://store.steampowered.com/app/123456.
Zatem umieszczamy tylko ten numer w
pliku.
Musimy także zmienić lokalizację tego pliku. Nie może się on
znajdować w katalogu ‘steam’. Musimy przenieść go poziom wyżej bezpośrednio do
katalogu assets. Zatem katalog assets powinien wyglądać tak:
-assets
--models
--images
--icons
--steam_appid.txt
--steam
...
W tym momencie należy uczynić jedną uwagę. Zgodnie z
dokumentacją plik steam_appid.txt służy jedynie testom i powinien być usunięty w wersji produkcyjnej. Oczywiście nie ma
konieczności jego usuwania, bowiem nie zawiera żadnych tajnych informacji.
Jednakże zauważyłem, że bez tego pliku nie da się uruchomić achievementów nawet
w wersji produkcyjnej (o tym poniżej). Nie wiem czy jest tak zawsze czy tylko w
wersji gry używającej steamworks4j.
6. Tworzenie
achievementów
W tym momencie powinniśmy mieć wszystkie pliki potrzebne do
implementacji achievementów. Achievementy w Steam określone są przez element
graficzny(ikony) i tekstowy (nazwa i opis). Każdy achievement wymaga dwóch ikon (osiągniętą
i nieosiągniętą) w formacie jpg w rozdzielczości 64x64. Rekomenduje się aby
ikona nieosiągnięta była szara, a osiągnięta kolorowa.
Ponadto, do każdego achievementu należy wymyślić jego nazwę i
krótki opis jak uzyskać dany achievement. Oba elementy będą widoczne dla gracza
z poziom klienta Steam. Ponadto, należy wymyślić unikalny identyfikator dla
każdego achievementa, za pomocą którego z poziomu kodu będziemy identyfikować
dany achievement. Identyfikator to zmiana typu String. Sugeruje się użycie
dużych liter i snake_case np.
ACHIEV_BATTLE_1, ACHIEV_BATTLE_2 itd.
Do tej pory działaliśmy offline. Aby zdefiniować
achievementy musimy zalogować się na naszym koncie partnera Steam (https://partner.steamgames.com).
Następnie wybieramy naszą aplikację. W głównym oknie szukamy linku do
definiowania achievementów.
Teraz dla każdego achievementu klikamy niebieski przycisk ‘New
Achievement’, wypełniamy formularz i naciskamy Save.
Uwaga! Niestety
przy zapisywaniu achievementu pojawia się bug (a może feature) platformy Steam.
Podczas wypełniania formularza nie da się uploadować plików ikon. Przy próbie
pojawia się błąd. Najpierw należy wypełnić formularz bez ikon, zachować,
następnie kliknąć na przycisk Edit i wskazać ikony i ponownie zachować.
Po zdefiniowaniu wszystkich ikon NALEŻY opublikować zmiany.
Inaczej achievementy nie będą widoczne. Żeby achievementy były widoczne w kliencie
Steam musisz wylogować się i zalogować ponownie do klienta (nie platformy webowej). Jeżeli to nie pomaga proszę
spróbować odinstalować i ponownie zainstalować swoją grę.
7. Kod - przygotowanie
Mając
już wszystkie biblioteki i zdefiniowane achievementy można przystąpić do implementowania
achievementów w naszej grze. Użycie achievementów w grze składa się z 3 etapów:
-załadowania biliotek i inicjacja
-ustawienia achievementu
-zamkniecia bibliotek
O ile pierwszy etap jest niezbędny to ostatni nie wydaje się
być konieczny. Oczywiście pomiędzy rozpoczęciem a zamknięciem można ustawić
nieograniczoną ilość achievementów. Inicjacje i zakończenie przeprowadza się
raz w grze. Ja inicjacje ustawiłem w głównym menu (zaraz po Loading page), a zakończenie
przy wychodzeniu z gry także z poziomu głównego menu.
Uwaga! Testując
achievementy w grze NALEŻY mieć włączonego klienta Steam. Wydaje się, że gra łączy
się z klientem, a nie bezpośrednio z serwerami Steam.
Testowanie ustawiania achievementów może odbywać się w IDE
bez potrzeby kompilowania i umieszczania gry na steamie.
8. Kod - klasa
Tworzenie kodu zacznijmy od zdefiniowania ogólnej klasy.
Będzie ona bardzo uproszczona, zwłaszcza jeżeli chodzi o wyjątki. Samego
klienta definiujemy jako statyczną klasę w głównym menu.
Klasa będzie nazywała się SteamClient:
public class SteamClient {
}
Następnie dodamy potrzebne importy. Oczywiście nawiązują do
wcześniej dodanego kodu źródłowego.
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;
Następnie dodamy 3 zmienne:
private SteamUtils utils;
private
SteamUserStats userStats;
public boolean isOnline=false;
Następnie musimy zdefiniować identyfikator dla każdego
achievementa. Jak wspomniano wyżej to String. Zdefiniujemy je jako zmienne
statyczne.
static
public String achievBattle1SteamId= "ACHIEV_BATTLE_1";
static
public String achievBattle2SteamId= "ACHIEV_BATTLE_2";
static
public String achievBattle3SteamId= "ACHIEV_BATTLE_3";
...
Ponadto musimy zdefiniować w ramach naszej klasy SteamClient
jeden callback. Nie musimy obsługiwać w żaden sposób metod tego callbacka.
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) {
}
};
W kolejnym kroku definiujemy 3 podstawowe metody
odpowiadające 3 wymaganym etapom.
public
boolean initAndConnect(){};
public
boolean setAchiev(String achivName) {};
public void disconnect(){};
9. Kod - inicjacja
Pierwsza metoda initAndConnect() musi prawidłowo zainicjować
biblioteki Steam. Dlatego musi zawierać następujący kod (załadowanie bibliotek
z parametrem wskazującym nazwę katalogu gdzie wcześniej umieściliście biblioteki
dll):
try {
SteamAPI.loadLibraries("./steam");
}
catch (SteamException e1)
{
System.out.println("Load libraries
error");
}
Inicjacja połączenia z klientem Steam:
try {
if (!SteamAPI.init())
{
System.out.println("Initialisation
failed");
isOnline=false;
return false ;
}
} catch (SteamException e)
{
e.printStackTrace();
}
Pobranie statystyk z achievementami. Tutaj wskazujemy
wcześniej zdefiniowany callback:
//Get stat object
userStats = new SteamUserStats(
steamUserStatsCallback);
//A must before setting achievements
userStats.requestCurrentStats();
Ustawienie flagi wskazującej, że poprawnie wykonano
inicjacje bibliotek.
isOnline=true;
10. Kod - ustawienie
achievementu
Aby ustawić achievement wystarczy wywołać metodę
setAchiev(String achivName) w dowolnym miejscu w grze z odpowiednią nazwą achievementu.
Nazwy wcześniej zdefiniowalismy w klasie za pomocą statycznych identyfikatorów.
Dodatkowo wykonanie metody warunkowane jest poprawnym zainicjowaniem bibliotek.
Nasza metoda:
public boolean
setAchiev(String achivName)
{
try
{
//set
userStats.setAchievement(achivName);
//save
boolean result=userStats.storeStats();
return result;
}
catch ( Exception e) {
isOnline=false;
return false;
}
}
I wywołanie:
//Steam
if(SteamClient.isOnline)
SteamClient.setAchiev(SteamClient.achievBattle1SteamId );
I to wszystko co jest konieczne dla ustawienia achievementu.
UPDATE!!!
Brak natychmiastowego pojawienia się achievementu w Steam Klient jest wynikiem
niewywołania storeStats() po setAchievement().
Jeżeli ta metoda zostanie poprawnie wywołana, to achievement od razu pojawi
się w Steam Client.
11. Rozłączenie się
Aby rozłączyć się należy wywołać metodę shutdown. Jak
wspomniano nie jest to bezwzględnie konieczne.
public void
disconnect()
{
SteamAPI.shutdown();
}
12. Deployment
Testując
achievementy w IDE wszystko będzie poprawnie działało. Niestety jeżeli
utworzymy plik jar (Runnable Jar) nasza gra albo się wysypie albo achievementy
nie będą ustawiane. Problemem jest umiejscowienie katalogu 'steam' oraz pliku
steam_appid.txt. Jak wskazano wcześniej katalog ‘steam’ i plik txt znajdują się
w katalogu assets (a obecnie wewnątrz pliku jar). Niestety gra ich nie widzi.
Dlatego należy skopiować katalog steam i plik steam_appid.txt i umieścić zaraz
obok pliku, który rozpoczyna naszą grę. Żeby wyjaśnić jak to ma wyglądać pokaże
moją strukturę plików gry. W moim katalogu gry mam tylko plik exe oraz katalog
lib zawierający jre oraz jar mojej gry. Exe wywołuje jedynie jara odwołując się
do java.exe. Wygląda to tak
-MyGame
--mygame.exe
--lib
Zatem jeżeli chcemy dodać katalog ‘steam’ i plik
steam_appid.txt muszą się oni znajdować bezpośrednio w katalogu MyGame. Należy
go skopiować, a nie usunąć z
katalogu assets.
-MyGame
--mygame.exe
--lib
--steam
--steam_appid.txt
Przy takiej konfiguracji achievementy powinny działać po
uplodowaniu gry Steam.
UPDATE!!!
Zgodnie z uwagą autora biblioteki (code-disaster) „konieczność umieszczenia katalogu ‘steam’i
pliku txt wynika ze sposobu uruchomienia gry. Na początku klient Steam
‘wstrzykuje’ się w proces gry. Jeżeli plik exe wywołuje "java -jar
..." nie ma niczego, co mogłoby zidentyfikować aplikacje przy wywołaniu
Steam.init()- to nowy process i musi szukać pliku steam_appid.txt jako wyjścia
awaryjne”.
13. Błędy
Poniżej przedstawiam najczęściej występujące błędy (zarówno
w trakcie testowania jak i działania gry).
A. Nie można znaleźć bibliotek.
Exception
in thread "LWJGL Application"
com.badlogic.gdx.utils.GdxRuntimeException: java.lang.UnsatisfiedLinkError:
Can't load library: ....\android\assets\steam\steam_api.dll
W czasie testowania ten błąd wystąpi jeżeli nie umieściliśmy
katalogu steam w katalogu assets. W czasie działania gry ten błąd wystąpi
jeżeli nie umieścimy katalogu obok pliku exe. Ponadto błąd pojawia się gdy
metoda SteamAPI.loadLibraries("./steam") wskazuje nieprawidłową nazwę
katalogu.
B. Win32 i Win64
Exception
in thread "LWJGL Application"
com.badlogic.gdx.utils.GdxRuntimeException: java.lang.UnsatisfiedLinkError:
...\android\assets\steam\steam_api.dll: Can't load AMD 64-bit .dll on a IA
32-bit platform
Ten błąd wystąpi gdy umieściliśmy plik steam_api.dll jedynie
dla wersji Win32 lub odwrotnie.
Uwaga! W moim
przypadku testowanie w Eclipse wymagało
Win32, a wersja produkcyjna Win64. W obu
przypadkach korzystałem z tego samego komputera. Nie wiem co jest tego
przyczyną. Może to, że używam starej wersji Eclipsa.
C. SteamAPI.init()
zwraca false
Błąd ten wywołuje brak zalogowania do swojego konta przez aplikacje
Steam Client w czasie testowania. Ponadto wynika z braku umieszczenia pliku steam_appid.txt
w katalogu assets i głównym katalogu obok pliku exe.
D. Biblioteki nie załadowane
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()
Ten błąd pojawia się gdy probujemy wywołać metodę
SteamAPI.init() przed wywołaniem metody
SteamAPI.loadLibraries("./steam").
Wydaje się, że wcześniejsza wersja API dla Javy nie wymagała
wywołania SteamAPI.loadLibraries() co może być źródłem problemu.
14. Zakończenie
Mam nadzieję, że powyższy tutorial będzie pomocny. Jeżeli
chcesz zobaczyć moją grę w której zaimplementowałem achievementy w powyższy sposób
sprawdź ten link
Proszę też o suba na Twitterze.
15. Pomocne linki
Harrah's Cherokee Casino Resort - Mapyro
OdpowiedzUsuńHarrah's Cherokee Casino Resort, Cherokee, NC: 포천 출장마사지 Casino 경상남도 출장안마 Information Find 창원 출장샵 Harrah's Cherokee Casino 충청북도 출장마사지 Resort, a 4.4 acre casino 충주 출장안마 with 2480 slots, table games,