Zapisywanie użytych przedmiotów w grze (np. skrzyń)

Wtorek, 11 Października 2022, 01:14
Czas czytania 6 minut, 9 sekund
Zgodne z GM: gms2
Jak zapisać informacje o otwartych skrzyniach, drzwiach, lub zebranych przedmiotach? Oto jeden ze sposobów.
Gotowy przykład pobierzecie tutaj: gmclan.org/.../saving_used_items.yyz

Jednym z zagadnień, które często pojawia się w grach, jest zapamiętywanie, czy gracz wykonał już w jakimś miejscu akcje. Kto programuje już chwilę, całkiem szybko wpadnie na to, że najlepiej skorzystać w tym celu z tablic i tam zapisywać jako true/false informację na ten temat.
Problem pojawia się jednak w momencie, gdy trzeba jakoś oznaczyć przedmioty w roomach, żeby było wiadomo, który indeks w tablicy odpowiada któremu przedmiotowi. Prostym sposobem byłaby zmienna, która przechowuje indeks tablicy, ustawiana np. w Creation Code, ale taki system nie jest łatwy do zarządzania, bowiem gdy usuniemy pewien przedmiot, nie możemy nie wiedzieć którego nam brakuje. Poza tym, gdy nagle dodamy nowy etap, możemy nie pamiętać, na jakiej cyfrze skończyliśmy (teoretycznie tabela powinna mieć ten sam rozmiar, ale co jeśli od razu zarezerwowaliśmy np. 100 miejsc na zaś?). Innym sposobem którym widziałem, jest ds_mapa, w której przy otwarciu zapisujemy klucze w postaci:
object_get_name(id) + string(x) + string(y), gdyż raczej nie zdarzy się, że dwa obiekty których stan chcemy zapamiętać stoją w tym samym miejscu (to wręcz przeszkadzałoby np. w ich użyciu i wykryciu kolizji). Niestety, wszystko się zepsuje w momencie, gdy taki obiekt przesuniemy. Warto jednak wiedzieć, że ten sposób wymaga najmniej kodu, więc nie odradzam go całkowicie.

Ja natomiast wybrałem sposób nieco trudniejszy w implementacji (ok 30 linijek kodu), ale za to bardziej praktyczny. Korzysta on z pewnej mało używanej właściwości GameMakera - mianowicie faktu, że każda dodana w roomie instancja dostaje swoją "stałą", której można użyć w kodzie (w formacie inst_8ZNAKOW). Plusem jest to, że można tę nazwę ustawić samemu (ale nie trzeba), można też swobodnie usunąć przedmiot ze środka kolejki (i automatycznie kolorowanie składni przestanie go podświetlać, co ułatwi jego zlokalizowanie), a można go nawet przenieść do innego roomui i nadać starą nazwę, żeby kod nadal działał. Wykorzystuje on dwie tablice, dzięki czemu łatwo go też zapisać do pliku.

Wskazówka:
Nie korzystamy z DS_MAP, gdyż id ukrywające się pod stałą mogą się zmienić, gdy dodamy/usuniemy inne obiekty z danego roomu. Nigdy nie zakładaj, że id zasobów i instancji się nie zmienią - dlatego nie używaj ich jako kluczy struktur/tablic, oraz nie zapisuj do pliku.

Na początek stworzymy sobie dwa obiekty:

obj_player (dowolny sprite):

Kod poruszania w step:
kodx += (-keyboard_check(vk_left) + keyboard_check(vk_right)) * 4;
y += (-keyboard_check(vk_up) + keyboard_check(vk_down)) * 4;

Kod do testów, w Key Pressed R
kodroom_restart();
Teraz stworzymy obj_chest, któremu stworzymy sprite z dwiema klatkami - skrzynią zamkniętą (0) i otwartą (1). Ustawmy też FPS w sprite na 0, dzięki czemu nie będzie się animował i zawsze pokaże zerową klatkę jako domyślną.
obj_chest:

Create:
kodopened = false;
Kod w kolizji z obj_player:
kodif (opened == false) {
opened = true;
image_index = 1;
}

Teraz można w roomie wstawić jednego playera, kilka skrzyń i przetestować, czy działają kolizje i otwieranie skrzyń.

Grafika: /upload/ajax/20221011_f87705ca03ce7c403df69fb8607607af.png

Etap drugi

Utwórzmy teraz nowy skrypt, ja nazwałem go "chest_scripts". Skorzystamy z właściwości GameMakera 2.3+ (2021+), która pozwala na zdefiniowanie zmiennych przed rozpoczęciem gry - wystarczy napisać kod poza function(). Zdefiniujemy sobie tablicę z naszymi skrzyniami (w moim przypadku są cztery), kopiując ich identyfikatory z room editora. Znajdziecie je w inspektorze po zaznaczeniu instancji, lub klikając dwa razy (input tekstowy z inst_XXXXX). Możecie je dowolnie zmienić, ale ja zostawiłem te losowe.

Grafika: /upload/ajax/20221011_c8483e3e38b3fe4e5782ac14a47e2bd5.png

Stworzymy też od razu tablicę z informacją czy skrzynia jest otwarta/zamknięta - o tym samym rozmiarze, domyślnie z wartościami false. Żeby nie musieć jej zawsze modyfikować, skorzystamy z array_create, gdzie rozmiarem tablicy będzie rozmiar tablicy ze skrzyniami, pobrany przy pomocy array_length:

kodglobal.chests = [inst_18134AA6, inst_1C8CC5E6, inst_2B0F70D8, inst_1AFF602];
global.opened = array_create(array_length(global.chests), false);

Wskazówka:
Powyższy kod wykona się tylko gdy gra startuje i nie wykona się ani przy room_restart(), ani nawet przy game_restart();

Teraz, wypadałoby napisać funkcję, która sprawdza, czy dana skrzynia jest otwarta. W tym samym jedynym w naszej grze skrypcie, dodamy kod, który "przelatuje" tablicę i sprawdza, czy ID obecnej skrzyni w ogóle jest na liście i jeśli ją znajdzie, zwróci informację o tym, czy jest otwarta. Dzięki użyciu dwóch tablic, nawet jeśli zmienią się ID skrzyń w grze, nie zmieni się ich pozycja w tablicy. Gdyby skrzynia była kluczem ds_mapy, mimo tej samej "stałej", miałaby ona inną wartość i skrzynie by się losowo zamykały po zmianach w roomie - bowiem tę wartość GM ustawia dopiero w momencie kompilacji.
Funkcja, poza tym, że zwraca, czy skrzynia jest otwarta, od razu zmienia index sprite'a na 1.

kodfunction check_if_opened() {
image_index = 0;
var len = array_length(global.chests);
for(var i = 0; i < len; i++) {
if (global.chests[i] == id) {
if (global.opened[i] == true) {
image_index = 1;
return true;
}
}
}

return false;
}

Została ostatnia funkcja - zapisująca otwarcie skrzyni. Napiszemy ją w bardziej uniwersalny sposób, który pozwoli zapisać też w przyszłości zamknięcie skrzyni. Zmianę sprite'a wywołamy poprzednią funkcją check_if_opened(), dzięki czemu od razu obsłużymy oba przypadki otwarcia i zamknięcia.

kodfunction chest_save() {

var len = array_length(global.chests);
for(var i = 0; i < len; i++) {
if (global.chests[i] == id) {
global.opened[i] = opened;
check_if_opened(); // change sprite
return true; // exit for-loop early
}
}

return false;
}

Pozostaje poprawić kod w obj_chest, aby korzystał z nowych funkcji. Dodajemy/zmieniamy pierwszy skrypt w create, a drugi w step:

Create (po zmianach):
kodopened = false;
check_if_opened(); // nowa linijka

Kolizja:
kodif (opened == false) {
opened = true;
chest_save(); // zmieniona linijka
}

I to tyle. Wszystko działa, a po odpaleniu gry wystarczy wcisnąć "R", aby zobaczyć, że skrzynie są zapamiętane.

Co dalej?

Resetowanie otwarcia skrzyń (w menu, zaczynając/wczytując nową grę) wykonujemy przez ponowne wykonanie:
kodglobal.opened = array_create(array_length(global.chests), false);Warto na pewno utworzyć sobie z tego kawałka kodu funkcję, żeby nie powtarzać go dwa razy.

Natomiast zapis do pliku, pozostawiam już wam - ostatecznie można skorzystać z json_stringify do zapisu i json_parse do odczytu - dodając walidację długości i danych (najlepiej więc wczytać ją do nowej tablicy i porównać pętlą for, o długości najmniejszej (min) z dwóch długości - global.opened i wczytanych danych, rzutując wszystko na true/false).

Gotowy przykład pobierzecie tutaj: gmclan.org/.../saving_used_items.yyz
Komentarze (łącznie 4):
Adriann (Wto., 18 Paź. 22, 22:17)
#1

Wreszcie udało mi się przetestować, super przykład :P
Jedyny minus to potrzeba ręcznego umieszczenia listy obiektów ale w zasadzie to drobiazg.
Dobra robota!

gnysek (śro., 19 Paź. 22, 08:58)
#2

Niestety, co prawda istnieje opcja automatycznego generowania takiej listy, ale jak usuniesz/dodasz dowolną instancję, lub zmienisz kolejność roomów, to całość może nie działać z sejvami. Można też zapisać stringa z nazwą obiektu i x oraz y, ale tutaj znów - przesunięcie rozwali stare gry.

W ten sposób który tutaj jest - można przenieść skrzynię w dowolne miejsce w dowolnym roomie, zachować lub podmienić "stałą" nazwy i gra nadal będzie działać (można też dać undefined jak chcemy skrzynię wywalić na zawsze, a zachować kompatybilność).

Teoretycznie jest to więc ręczna robota, ale w praktyce więcej czasu oszczędza go 10x tyle na przyszłość.

YYG chyba planowało tagi dla instancji w rommach i to by rozwiązało sprawę, no ale w ostatnich wersjach z plików .yy zniknęły takie wpisy, więc się wycofali.

Adriann (śro., 19 Paź. 22, 16:24)
#3

Nie no jest spoko, i łatwo było dodać to do systemu zapisu :P

gnysek (Czw., 20 Paź. 22, 14:13)
#4

Być może da się zrobić wygodniejszy system, ale na pewno nie w tak małej ilości kodu. Lepszy wymagałby pewnie tyle pracy, że chyba szkoda się za niego brać (chyba, że ktoś by chciał zrobić z niego bibliotekę do dzielenia się :D).

Najnowsze wersje GameMakera:

Stabilna
2024.8.1.171 • 2024.8.1.218
wydana 72 dni temu
LTS
2022.0.2.51 • 2022.0.2.49
wydana 401 dni temu
Beta
2024.1100.0.686 •
2024.1100.0.707
 0.13.0

wydana  6 dni temu
= IDE, = Runtime, = GMRT
Użytkownicy online
1 użytkownik aktywny:
gości: 1,
(~ostatnie 15 minut)
Discord
33 użytkownicy online na discordzie:
Kysiu, 🧁Cupcake🧁, Nikas, Alice, Nitro Slav, Carl-bot, Saus, GibkiKaktus, GMRussell, fervi, r..., antek, Michał Parkoła, Pako, Arrekin, MagnusArias, LadyLush, yazaa, Dyno, szmalu, Korodzik, LeD, Ulti, bagno, Tidżi, Danieo, g..., Huder, l..., moeglich, s..., Krzysiek1250, Shockah
Shoutbox
gnysek (11:46, 17.11.24)
Witamy, witamy!
baca (12:22, 16.11.24)
To już 25 lat.. Witam po paru latach nieobecności.
gnysek (11:05, 15.11.24)
Natomiast obecne forum istnieje od 2004, jak z iglu.cz na gmclan.org przeszliśmy i od tego czasu nie było resetów danych.
gnysek (12:35, 13.11.24)
Ogólnie GMCLAN istnieje 22 lata, ale na to trofeum nie zrobiłem (jeszcze xD)
Chell (20:41, 08.11.24)
wow, ta emotka w ogóle nie wygląda jak : O xD
Chell (20:40, 08.11.24)
tylko? :O 4tk ma 15
Borek (18:12, 07.11.24)
Właśnie dostałem powiadomienie z forum, że jestem na GMClanie 18 lat :D Ja pierdzielę...
S
Sutikku (08:43, 18.10.24)
TIL, gamemaker jest starszy ode mnie
gnysek (16:04, 15.10.24)
Za równo miesiąc, GameMaker kończy 25 lat.
Wojo (15:38, 05.09.24)
Ciekawe
Starsze wpisy znajdziesz w Archiwum.
Ankieta
Ile zarobiłeś do tej pory na grach stworzonych w GM?