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.11.0.179 • 2024.11.0.227
wydana  2 dni temu
LTS
2022.0.3.85 • 2022.0.3.99
wydana 18 dni temu
Beta
2024.1100.0.713 •
2024.1100.0.726
 0.14.0

wydana 10 dni temu
= IDE, = Runtime, = GMRT
Użytkownicy online
2 użytkowników aktywnych:
gości: 1, userów: 1
 Adriann
(~ostatnie 15 minut)
Discord
37 użytkowników online na discordzie:
🧁Cupcake🧁, Alice, Nitro Slav, Carl-bot, Wielki Druid, Alkapivo, Kowu, LadyLush, GMRussell, OdrzuconyKrakers, 𝕳𝖚𝖌𝖔 𝕲𝖔𝖓𝖝𝖆𝖑𝖊𝖝, r..., antek, HappyOrange, Moldis, Arrekin, MagnusArias, yazaa, Domeen0, Dyno, 🆅🅸🆃🅾74🅼, szmalu, ZYGZAK, Miłosz, blackamul, Voytec, m..., bagno, Tidżi, Danieo, Mtax, l..., Jayu, moeglich, s..., Add92, Shockah
Shoutbox
Uzjel (20:17, 10.12.24)
Cały ruch przeniósł się na Discorda.
MagnusArias (17:43, 01.12.24)
O matko... a ja tutaj jestem od ponad 15 lat i czasami zaglądam... biernie bo biernie, ale czasem wpadnę
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
Starsze wpisy znajdziesz w Archiwum.
Ankieta
Ile zarobiłeś do tej pory na grach stworzonych w GM?