Pobieranie danych JSON z sieci - wyświetlanie aktualności w grze

Wtorek, 08 Listopada 2022, 23:43
Czas czytania 7 minut, 15 sekund
Zgodne z GM: gms2
Przykład pobierania informacji z sieci - w naszym przypadku posłuży do wyświetlania aktualności, ale może to być też lista wyników, czy ceny przedmiotów.
Od wersji 2.3 GameMaker pozwala na korzystanie ze struktur, które znacznie ułatwiają przetwarzanie danych - zamiast skomplikowanej kombinacji ds_map i ds_list, możemy znacznie łatwiej operować na danych.

Wyobraźmy sobie więc, że jakieś dane chcemy umieszczać na własnym serwerze, aby gra mogła dynamicznie je pobrać i je wyświetlić, lub nawet dostosować do nich gameplay.

Nie jest to wcale trudne i wymaga dosłownie kilkunastu minut roboty.

Tworzenie zapytania - Create
Stwórzmy nowy projekt, a w nim jeden obiekt. Będzie się on składał z zaledwie trzech zmiennych, oraz definicji enum którą dla ułatwienia tutaj wrzuciłem - warto jednak pamiętać, że enumy są globalne i podmieniane są w trakcie kompilacji na kolejne liczby rzeczywiste, więc ten fragment kodu tak naprawdę wyparuje.

Statusy pobierania danych będą 4 - początkowy, czyli wczytywanie, sukces, oraz timeout i błąd. Poza tym, potrzebujemy zmiennej do które zapiszemy pobrane przez nas dane (content), obecnego statusu (state) i identyfikatora zapytania (request), który rozpocznie zapytanie pod wskazany przez nas adres.

kod // statusy - otrzymają kolejne wartości 0,1,2,...
enum states {
loading,
succes,
timeout,
error
}

// zmienne
content = ""; // treśc którą wyświetlimy
state = states.loading; // ustawmy domyślny status
request = http_get("gmclan.org/.../example_news.json"); // rozpocznij zapytanie http

Ot i 1/3 projektu za nami!

Wyświetlanie wyniku - Draw
Jak już wspomniałem, mamy 4 statusy - zrobimy teraz ich wyświetlanie. Wiemy już, że każdy status poza success oznacza, że albo zapytanie trwa, albo coś nie wyszło. Obsłużmy więc pozostałe 3 osobno i wyświetlajmy krótki komunikat. Oczywiście, w samej grze być może chcielibyśmy wyświetlać jakiś animowany sprite robiący za pasek wczytywania. Nie zapomnijmy też stworzyć fonta, który będzie zawierał polskie znaki diakrytyczne - ja swój nazwałem fnt_default.

Wskazówka:
Domyślny font w GameMaker nie wyświetla polskich znaków. Musimy więc dodać nowy font, kliknąć "Add", a następnie wpisać ąćęłńóśżźĄĆĘŁŃÓŚŻŹ i kliknąć "Add range".

kod // ustawmy font, zawierający polskie znaki
draw_set_font(fnt_default);

if (state != states.succes) {
// jeśli wczytujemy lub jest błąd
var msg = "Wczytywanie...";
switch (state) {
case states.error:
msg = "Błąd pobierania.";
break;
case states.timeout:
msg = "Nie udało się wczytać adresu.";
break;
}

draw_text(10, 10, msg);
} else {
// wypiszmy newsy
draw_text(10, 10, content);
}

Obsługa zapytania - Async: HTTP
Teraz musimy obsłużyć moment, w którym GM wczytał (lub nie) dane z naszego adresu. Wykorzystamy event asynchroniczny HTTP.

Wskazówka:
Należy pamiętać, że jeśli więcej niż jeden obiekt ma wydarzenie Async HTTP, to w momencie gdy nasze zapytanie dobiegnie końca, zdarzenie to odpali się we wszystkich instancjach wszystkich obiektów bez względu czy to one rozpoczęły dane zapytanie. Właśnie dlatego przypisaliśmy je do zmiennej request aby dostać unikalne ID i odróżnić je od ewentualnych innych

Wskazówka:
Więcej o evencie Async HTTP znajdziecie w dokumetnacji: manual.yoyogames.com/.../HTTP.htm .

Dla nas najważniejsza informacją jest, że w tym evencie dostajemy specjalną, wygenerowaną tymczasowo przez GMa ds_mapę o nazwie async_load. Jej zaletą jest to, że zostaje ona przez GM automatycznie usunięta więc wyjątkowo nie musimy pamiętać o jej usuwaniu.

Interesują nas jej trzy klucze:
- id - które zawiera identyfikator requestu który odpalił ten event. W naszej grze request jest jeden, ale "na przyszłość" uczymy się pisać bezpieczniejszy kod
- status - jeśli jest on mniejszy niż 0, to wystąpił błąd
- result - tekst który zwróciła witryna, bez nagłówków

W pierwszej kolejności zajmiemy się osbługą błędów (status < 0) i za pomocą exit zaprzestaniemy dalszego wykonywania kodu.
W drugim kroku spróbujemy sparsować JSONa którego otrzymaliśmy i wyświetlić dane.

Co jednak, gdy natrafimy na błąd serwera (np. przestanie on działać, dostaniemy 404, zmieni się struktura JSONa)? Należałoby po kolei sprawdzać, czy json się sparsował, czy zawiera wymagane przez nas dane itd.

GameMaker od wersji 2.3 (oraz w wersjach 2021+) oferuje jednak mechanizm dzięki któremu możemy sobie pozwolić być leniwymi i totalnie olać to sprawdzanie. Dzięki try-catch będziemy przetwarzać dane, a jeśli pojawi się jakiś błąd, uznajemy, że JSON nie jest poprawny i ustawiamy "state" na "error".

Wskazówka:
Korzystając z try-catch, nie dostaniemy okienka z błędem i gra będzie działać dalej, ale z drugiej strony przerwie wykonywanie kodu wewnątrz try{} przy pierwszym błędzie. Jeśli wiemy, że jakaś zmienna może, ale nie musi istnieć, można skorzystać z funkcji is_undefined() lub z var wynik = niekoniecznie_istniejaca.wartosc ?? wartosc_domyslna;, co też nie powoduje wyświetlenia komunikatu błędu, ani przejścia do catch{}

Nasz przykładowy JSON znajdziecie tutaj:
gmclan.org/upload/gm/examples/example_news.json
Zawiera on pod własnością "news" tablicę, która składa się z własności title, content, date. Wiemy więc, że w danych jakie otrzymamy po sparsowaniu JSONa, będzie własność .news, w której jest tablica ze strukturami, mającymi te trzy własności. Za pomocą pętli for możemy więc przypisać je sobie do zmiennej content, która wyświetli listę newsów.

Wskazówka:
Wartości w zmiennej date wygenerowałem wcześniej za pomocą date_create_datetime
Wskazówka:
Pętle for nie dopuszczają przecinków w swojej składni, ale wykorzystujemy fakt, że pozwala na taką składnię konstrukcja var.

W kodzie zadbamy tez o to, żeby wyświetlić informację o braku aktualności, gdy tablica "news" będzie pusta.

kod if (async_load[? "id"] == request) { // sprawdź czy to request utworzony przez tą instancję

// najpierw obsługa błędów
if (async_load[? "status"] < 0) {
if (async_load[? "status"] == -1 and async_load[? "http_status"] == 200) {
// timeout
// lub brak domeny w DNS
state = states.timeout;
} else {
state = states.error;
}
exit;
}

// teraz parsowanie, w wersji leniwej - bez okienka errorów mimo braku sprawdzania poprawności danych
try {
// spróbujmy sparsować
var _json = json_parse(async_load[? "result"]);

// teraz przelećmy newsy
for(var i = 0, n = array_length(_json.news); i < n; i++) {
// dodajemy nagłówek, treśc i datę (liczba)
content += "> " + _json.news[i].title + "\n\n";
content += _json.news[i].content + "\n\n";
content += date_date_string(_json.news[i].date);

// dodajmy poziomą linię za każdym "newsem"
if ( i < n-1) {
// ale nie dodawaj tego fragmentu za ostatnim wpisem
content += "\n----------\n";
}
}

// jeśli nie ma żadnych elementów w _json.news
if (n == 0) {
content = "Brak aktualności.";
}

state = states.succes; // na sam koniec, gdy wiemy, że nie poleci żaden exception
} catch (e) {
state = states.error; // jakikolwiek błąd
// można też dodać:
// show_debug_message(e)
// aby zobaczyć w której linijce nastąpił problem
}
}

// async_load jest zwalniane automatycznie

I to już w zasadzie wszystko.

Wskazówka:
Powyższy kod korzysta z zapisu async_load[? "klucz"] zamiast [kbd]ds_map_find_value(async_load, "klucz"). To tzw. akcesor, więcej o nich przeczytasz w artykule: Akcesory - listy, mapy, gridy, structy, tablice .

Obsługa błędów
Można oczywiście sprawdzić jeszcze, jak działa obsługa błędów i czy faktycznie nie będzie typowych dla GMa ekranów "Code error" z nielubianym przyciskiem "Abort".

Wystarczy tylko zmienić domenę z gmclan.org na np. gmclanyyyy.org, albo nazwę pliku z example_news.json na example_news.jsonyyyy - i potwierdzimy działanie naszego kodu i poprawną obsługę błedów. Można też pokusić się o wyłączenie wifi.

Wskazówka:
Zawsze sprawdzajmy w ten sposób nasz kod dotyczący zapytań po sieci - wyłączając wifi, lub sprawdzając błędny adres - aby miec pewnośc, ze gra nie wysypie się naszym graczom przez drobną pomyłkę.

Gotowy przykład do pobraniaPrzykład stworzony w GM 2022.9.0 do pobrania tutaj:
gmclan.org/.../gmclan_example_ajax_news.yyz
Komentarze (łącznie 0):
Nie ma jeszcze żadnego komentarza. Czas to zmienić

Najnowsze wersje GameMakera:

Stabilna
2024.06.2.162 • 2024.6.1.208
wydana 12 dni temu
LTS
2022.0.2.51 • 2022.0.2.49
wydana 284 dni temu
Beta
2024.800.0.597 •
2024.800.0.620
 0.11.0

wydana  2 dni temu
= IDE, = Runtime, = GMRT
Użytkownicy online
1 użytkownik aktywny:
gości: 1,
(~ostatnie 15 minut)
Discord
27 użytkowników online na discordzie:
Alice, Carl-bot, RogerDodg3r, p..., LadyLush, GibkiKaktus, Grela, TobiasM (Morgo), HappyOrange, Tival, DungeonFairy🧚, MKP (GEM), Moldis, yazaa, Dyno, Deusald, LeD, ZYGZAK, 𝕳𝖚𝖌𝖔 𝕲𝖔𝖓𝖝𝖆𝖑𝖊𝖝, Voytec, m..., bagno, l..., Alkapivo, Jayu, Krzysiek1250, Shockah
Shoutbox
gnysek (18:31, 25.07.24)
Ogłaszam nowy etap w historii GameMakera.
gnysek (11:36, 08.07.24)
Ale w sumie taki numer GG był bezpieczniejszy niż nr. telefonu czy kontakt społecznościowy. Utrudniał stalkowanie i ułatwiał banowanie.
Wojo (08:08, 08.07.24)
Niestety to już nie te czasy kiedy pytało się kasjerki o wiek i numer Gadu-Gadu...
Adriann (08:28, 05.07.24)
Albo okraść :|
Adriann (08:28, 05.07.24)
Może pani chciała zobaczyć twoje dane i Cię poderwać :d
gnysek (10:38, 03.07.24)
Mnie ostatnio w Żabce zapytali o wiek. A mam już ponad dwie osiemnastki.
Wojo (08:27, 30.06.24)
Ogólnie to miał być żart ponieważ portal internetowy, którego można opisać jako PH jest portalem przeznaczonym dla dorosłych. Miało być śmiesznie wyszło żenująco, a wiadomości w shoutboxie nie mogę skasować :P
Wojo (09:40, 28.06.24)
Tymon jak co wizytę musiałem potwierdzić wiek
gnysek (14:15, 17.06.24)
Bo Łapusz woli alkohol, niż się organizować. Co tam Pixel Heaven, sprawdźcie jego zbiórki na książki...
Adriann (21:02, 13.06.24)
Bardzo słusznie, straszna patola z tą organizacją :D
Starsze wpisy znajdziesz w Archiwum.
Ankieta
Ile zarobiłeś do tej pory na grach stworzonych w GM?