Inventory / Ewipunek / Plecak - proste rozwiązanie

Poniedziałek, 10 Czerwca 2024, 18:47
Czas czytania 11 minut, 21 sekund
Zgodne z GM: gms2
W tym artykule dowiesz się, jak stworzyć bardzo proste inventory (plecak) z itemami (ekwipunkiem) w GameMakerze.
Jeśli chodzi o inventory, jest tyle rozwiązań, ile programistów. Nawet to w jaki sposób jest przechowywane - czy będzie to tablica, czy jakaś inna struktura, jest kwestią wyboru. Jeszcze więcej możliwości istnieje jeśli chodzi o listę dostępnych itemów - mogą to być nazwy sprite, nazwy obiektów, liczby rzeczywiste wskazujące na jakąś bazę przedmiotów (i tu znów - albo tablice dwuwymiarowe, albo structy).

Wskazówka:
Link do gotowego projektu znajduje się na samym dole.

W tej wersji artykułu skorzystamy z listy itemów opartej na nazwie sprite, dzięki czemu poziom skomplikowania nie będzie za wysoki - to rozwiązanie może jednak być niewystarczające dla wielu osób. Na koniec artykułu podpowiem gdzie można by się wpiąć, aby wykonywać akcje zależnie od wybranego itemu.

Obiekt itemów / przedmiotów
Tutaj sprawa jest prosta. Stwórzcie sobie kilka sprite'ów, ja dla wygody nazwałem swoje prefixem item_, moje będą mieć rozmiar 64x64px. Stwórzcie obiekt o nazwie obj_item i ustawcie mu jako sprite jeden z wybranych przez was przedmiotów. Na razie to wszystko.

Funkcje obsługi inventory
Nasz ekwipunek będzie prosty, stworzymy więc raptem 3 funkcje:
- dodawanie do inventory
- usuwanie z inventory
- tworzenie itemów "na ziemi"

Stwórzcie nowy skrypt i nazwijcie go scr_inventory. Można wywalić domyślną funkcję która tam będzie.

Po pierwsze, sprawimy, że nasze inventory stworzy się w momencie startu gry, umieszczając prosty kod poza jakąkolwiek funkcją w skrypcie:
kod#macro INVENTORY_SIZE 20 // rozmiar inventory
global.inventory = array_create(INVENTORY_SIZE, -1); // kazda komorka jest na razie pusta
Skorzystamy z makro, aby w przypadku zmiany rozmiaru inventory nie trzeba było przepisywać kodu i szukać każdego możliwego miejsca, gdzie wpisaliśmy rozmiar.
Domyślnie tworzymy tablicę o rozmiarze 20 (tablica taka ma indeksy 0 - 19), a każdy jej element będzie mieć wartość domyślną -1. Taką wartość będziemy uznawać za "brak przedmiotu". W innym wypadku, w danej komórce tabeli będzie referencja na sprite.

Tworzenie przedmiotów
Zaczniemy od funkcji, która stworzy nam przedmiot w losowym miejscu na mapie. Jej argumentem będzie nazwa sprite, jaki ten item otrzyma.
Funkcja przyda nam się przy usuwaniu rzeczy z inventory, oraz na starcie gry, aby wygenerować kilka losowych przedmiotów.
Jej zasada działania jest prosta - stworzy obj_item na losowej pozycji, korzystając z funkcji irandom (czyli losowa liczba całkowita), w przedziale 0 do wysokość/szerokość rooma. Korzystając z opcjonalnego argumentu w GameMakrze 2023+, od razu ustawimy sprite, na ten podany w argumencie funkcji.
kod/// Tworzy item w świecie gry
/// @param {Asset.GMSprite} _spr jaki sprite będzie mieć item
/// @return {Id.Instance}
function inventory_item_create(_spr) {
return instance_create_layer(irandom(room_width), irandom(room_height), layer, obj_item, {sprite_index: _spr});
}

Przykład użycia tej funkcji:
kodinventory_item_create(item_key);
Dodawanie przedmiotów

Teraz stworzymy funkcję, która dodaje przedmiot do inventory.
- najpierw sprawdźmy, czy w tablicy jest jeszcze jakieś pole z wartością -1 i zapamiętajmy indeks tej komórki (array_get_index)
- jeśli ten indeks jest inny niż -1 (a zatem został znaleziony), zmieńmy wartość tego elementu tablicy na sprite naszego itemu; zwracamy true
- jeśli nie udało się wstawić itemu, zwróćmy false

kod/// @desc Dodaje item do inventory jeśli jest miejsce
/// @param {Asset.GMSprite} _spr jaki sprite
/// @return {Bool}
function inventory_add(_spr) {
var _place = array_get_index(global.inventory, -1); // znajdz czy jest jakies wolne miejsce
if (_place > -1) {
global.inventory[_place] = _spr;
return true;
}

return false;
}

Przykład użycia tej funkcji:
kodinventory_add(item_key);
Usuwanie przedmiotów

Następnie czas na funkcję, która usuwa przedmioty. Tym razem, podajemy slot, z którego chcemy usunąć przedmiot, sprawdzamy czy faktycznie coś tam jest:
- jeśli tak: tworzymy przedmiot "na ziemi" w grze i ustawiamy dany slot na -1; zwracamy true
- jeśli nie: zwracamy false

kod/// Usuwa item z inventory
/// @param {Real} _slot numer slotu
/// @return {Bool}
function inventory_remove(_slot) {
if (global.inventory[_slot] != -1) {
inventory_item_create(global.inventory[_slot]); // stwórz przedmiot
global.inventory[_slot] = -1; // ustaw ze ten slot jest pusty
return true;
}

return false;
}

Dodawanie przedmiotu po kliknięciu
Wracamy teraz do obiektu obj_item i dodajemy w nim event "left pressed" (ważne - nie left press, gdyż chodzi nam o moment gdy mysz przechodzi ze stanu nieklikniętego w kliknięty; nie chodzi też o event global, gdyż wtedy każdy item dodałby się gdziekolwiek nie klikniemy)

W evencie Left Pressed dodajemy bardzo prostą logikę - próbujemy dodać item do inventory, jeśli funkcja zwróci true i item dodano, to niszczymy obecny obiekt. Jeżeli nie - nic się nie dzieje.

kodif (inventory_add(sprite_index)) {
instance_destroy();
}
Wskazówka:
Zauważ, że funkcję inventory_add() można też użyć w dowolnym kontekście z dowolnego innego obiektu - np. za przejście etapu czy na koniec dialogu - nie trzeba kliknąć w item aby go dostać.

Rysowanie inventory
To najtrudniejsza część. Cała trudność polega tutaj jednak głównie na matematyce.
Moje założenie jest takie, że narysujemy prostokąt, w którym będą sloty, w których rysowane będą itemy, a odstęp między każdym z tych slotów to 10 pikseli od brzegu inventory oraz innych slotów.

Warto więc zauważyć, że odstępów między slotami jest o 1 mniej niż kolumn/wierszy (np. 10 slotów oznacza, że między nimi jest 9 odstępów), oraz 2 odstępy od brzegów. Możemy więc uznać, że przerw jest:
- ilość slotów w danej osi minus 1 + 2
- ilość slotów w danej osi + 1 (i ten wariant wybierzemy)

Grafika: /upload/ajax/20240610_76c4efdfd98f3a1801bee7ad04e3b249.png

(A) - Cała szerokość inventory to zatem ilość slotów poziomo (ilość kolumn) razy szerokość slotów + margines, plus jeszcze jeden margines.
(B) - Cała wysokość inventory to zatem ilość slotów pionowo (ilość rzędów) razy szerokość slotów + margines, plus jeszcze jeden margines.

Do narysowania inventory wstępnie ustawimy sobie pomocnicze zmienne tymczasowe:
- i, do pętli która rysuje itemy
- row, col - aktualny wiersz i kolumna
- slots_per_row - ile itemow chcemy mieć w jednym rzędzie
- rows_total - na podstawie poprzedniej zmiennej oraz rozmiaru inventory dynamicznie liczymy ile jest wierszy
- slot_size - rozmiar slotu w px (u mnie 64)
- margin - rozmiar marginesu
- slot_x, slot_y - ponieważ potrzebujemy pozycji slotu do rysowania ramki i rysowania itemu, żeby nie powielać matematyki i nie zaczerniać kodu, wstępnie je sobie obliczymy w każdym przejściu pętli
- hovered - sprawdzamy, czy kursor myszy najechał na slot

Tworzymy obiekt obj_inventory, w którym dajemy event Draw GUI.
kodvar i; // iteracja pętli for
var row = 0, col = 0; // obceny wiersz i kolumna inventory
var slots_per_row = 10; // co ile itemow nowa linia
var rows_total = ceil(INVENTORY_SIZE/slots_per_row); // liczba wierszy, zaokr. w gore
var slot_size = 64; // rozmiar slotu w px
var margin = 10; // margines
var slot_x = 0, slot_y = 0; // pozycje obecnej komorki
var hovered = false; // czy kursor w slocie

Mamy już nasze zmienne, teraz czas na narysowanie "tła", zgodnie z założeniami (A) i (B) opisanymi wcześniej. Ta część może wydawać się skomplikowna i można ją zapisać na kilka sposobów (np. osobno szerokość slotów razy ilość slotów w kolumnie/wierszu plus liczbę marginesów (czyli ilość slotów +1) razy margines):

kod// rysuj tlo
draw_set_color(c_gray);
draw_set_alpha(1);
draw_rectangle(10, 10, 10 + margin + (slots_per_row*(slot_size+margin)), 10 + margin + (rows_total*(slot_size+margin)), false);

Teraz najdłuższy fragment - rysowanie inventory.
W pętli, item po itemie, wykonamy:
- wyliczenie aktualnej kolumny i wiersza, korzystając z funkcji modulo i division. Pierwsza zwraca resztę z dzielenia i umożliwi nam znalezienie kolumny, druga to liczba całkowita bez reszty z dzielenia i pozwoli nam uzyskać informację o aktualnym rzędzie
- ustalenie x i y obecnej komórki
- sprawdzenie czy nastąpiło najechanie mszą
- ustalenie tła slotu (czarne; czerwone jeśli najechano)
- usunięcie itemu jeśli klikniemy prawym przyciskiem myszy
- narysowanie tła slotu
- narysowanie itemu (jeśli jakiś jest na tej pozycji)

Wskazówka:
Inventory można by też rysować korzystając z dwóch pętli i trzymać itemy w tablicy 2D, ale... wtedy jego rozmiar musiałby być równy liczbie slotów w rzędzie razy liczbę rzędów. Skorzystanie z jednej pętli i wyliczanie rzędu i kolumny na bieżąco umożliwia nam mieć dowolną liczbę slotów, w tym nieparzystą, a ostatni rząd nie musi być pełen - gra mimo to nie rzuci błędami, gdyż rysuje tyle komórek ile jest itemów, nie więcej.

kod// rysuj itemy
for(i = 0; i < INVENTORY_SIZE; i++) {

// wylicz jaka jest teraz kolumna i rząd
col = i mod slots_per_row; // reszta z dzielenia całkowitego
row = i div slots_per_row; // dzielenie całkowite (bez reszty)

// ustal x i y obecnego slotu
slot_x = 10 + margin + col * (slot_size+margin);
slot_y = 10 + margin + row * (slot_size+margin);

// ustal, ze obecnie kursor nie najechał na komórkę
hovered = false;
if (mouse_x >= slot_x and mouse_x < slot_x + slot_size) {
if (mouse_y >= slot_y and mouse_y < slot_y + slot_size) {
// ustal, że jednak najechał
hovered = true;
}
}

// zresetuj kolor na czarny (jeśli poprzednio były inny)
draw_set_color(c_black);

// jeśli kursor najechał
if (hovered) {
// zmień kolor rysowania na czerwony
draw_set_color(c_red);

// sprawdź czy kliknięto przycisk
if (mouse_check_button_pressed(mb_right)) {
// usuń item (o ile jakiś jest)
inventory_remove(i);
}
}

// narysuj ramkę slotu
draw_rectangle(
slot_x, slot_y,
slot_x + slot_size, slot_y + slot_size,
true );

// narysuj item, jeśli jakiś jest
if (global.inventory[i] != -1) {
draw_sprite(global.inventory[i], 0, slot_x, slot_y);
}
}

Teraz w obj_inventory dodamy jeszcze funkcję, która na start gry zespawnuje nam kilka losowych itemów, korzystając ze znanej już funkcji inventory_item_create - event Create.

kod// stworz randomowe 5 itemow

repeat(5) {
inventory_item_create(choose(item_phones, item_wheel, item_key, item_compass, item_gamepad));
}

Dodajemy teraz obj_inventory do roomu w grze i odpalamy ją:
Grafika: /upload/ajax/20240610_4bf66ffd350173125fadfac54f4a21f5.png

Cyk, wszystko działa :)

Dodatkowe zmiany:
Użycie przedmiotów.
Aby "użyć przedmiot", można dodać event na lewy przycisk myszy w warunku if (hovered) {}. Najlepiej stworzyć nową funkcję, np. inventory_use(i);:
kod/// Uzywa itemu z inventory
/// @param {Real} _slot numer slotu
function inventory_use(_slot) {
if (global.inventory[_slot] != -1) {
var _remove = true; // czy usunąć przedmiot - domyślnie - tak
var _create = false; // czy stworzyć przedmiot na ziemi - domyślnie - nie

switch (global.inventory[_slot]) {
case item_key:
// kod gdy uzyjemy klucza
break;

case item_compass:
// kod gdy uzyjemy kompasu
break;

default:
_create = true; // nie wykonano zadnej akcji, jednak zespawnuj item
}

if (_create) {
inventory_item_create(global.inventory[_slot]); // stwórz przedmiot
}

if (_remove) {
global.inventory[_slot] = -1; // ustaw ze ten slot jest pusty
}
}
}

Ukrywanie inventory
Wystarczy dodać event Key Pressed I (nie Key Down) i dać tam:
kodvisible = !visible;Kod z Draw Gui nie wykonuje się gdy obiekt nie jest widoczny, nie uda się więc usunąć przedmiotów.
W obiekcie obj_item można wtedy dodać warunek:
kodif (not instance_exists(obj_inventory) or obj_inventory.visible = false) naokoło istniejącego kodu, co zapobiegać będzie dodawaniu itemów gdy otwarte jest inventory, zwłaszcza, gdy pod nim jest item i wykonać mogłyby się dwa eventy myszy na lewy przycisk. Oczywiście obiekt inventory można też usuwać zamiast ukrywać - to już wasz wybór.

Baza itemów
Zamiast sprite, można by zapisywać liczby naturalne i stworzyć jakąś tablicę z przedmiotami, np.:
kodglobal.inventory = [
{spr: item_wheel, name: "Kierownica", price: 100},
{spr: item_compass, name: "Kompas", price: 200},
];
Co pozwoli nam trzymać więcej parametrów danego itemu. Oczywiście wtedy należy podmienić draw_sprite w inventory na global.inventory[i].sprite itp., można też wtedy wyświetlać opis i cenę przedmiotu jeśli hovered = true. Można też dodać dowolne inne parametry, jak funkcja która wykona się gdy użyjemy przedmiotu, czy obiekt jaki ma się stworzyć - znów, to już wasz wybór.

Sortowanie itemów.
Po usunięciu itemu można wykonać array_sort(global.inventory);.

Gotowy projekt:
gmclan.org/upload/gm/gmclan_inventory.yyz - przygotowany w GM 2024.4 .

Polecam też artykuł o tym, jak zrobić ekwipowanie itemów ze statystykami: gmclan.org/artykul/110 .
Komentarze (łącznie 0):
Nie ma jeszcze żadnego komentarza. Czas to zmienić

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
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?