Ekwpiunek i statystyki

Wtorek, 11 Czerwca 2024, 22:52
Czas czytania 9 minut, 32 sekundy
Zgodne z GM: gms2
Opis tego jak stworzyć ekwipunek i statystyki postaci.
Mamy już na GMCLANie artykuł o tym jak stworzyć inwentory, a teraz przyszedł czas na ekwipunek. Oba systemy można ze sobą połączyć, lub używać rozdzielnie. Dla uproszenia, w tym artykule założymy, że nie będzie istniał plecak, a zdobyty przedmiot będziemy mogli wymieniać.

Baza przedmiotów
Po pierwsze, musimy stworzyć bazę przedmiotów. W przeciwieństwie do przykładu z inventory, nie wystarczy nam lista spritów czy obiektów - itemy muszą bowiem mieć swoje statystyki.

Itemy muszą gdzieś trafić - potrzebujemy dwóch rzeczy - tablicy z ID itemów jakie obecnie są wyekwipowane (-1 gdy żaden), oraz typów slotów, żeby przedmioty automatycznie trafiały w dobre miejsca.

Ja uznałem, że przygotujemy sloty na: hełm, miecz, zbroję, tarczę, spodnie, buty.

Skrypt scr_items

Tworzymy nowy skrypt i definiujemy wspominany enum.
kod// defnicja miejsc, w ktore moga trafic itemy
enum slot_type {
helmet,
sword,
armor,
shield,
pants,
boots,

__size, // bedziemy uzywac tego enuma gdy trzeba policzyć ilość elementów; tutaj __size = 6
}
Wskazówka:
Ponieważ enum działa podobnie do #macro - w momencie kompilacji enumy zamieniane są na kolejne liczby rzeczywiste - w grze nie istnieje możliwość policzenia ile elementów miał enum. Wystarczy jednak dodać na końcu jakiś dodatkowy element, który po konwersji będzie podobnie jak przy tablicach oznaczał, ile jest elementów i poniżej jakiego elementu wykonywać pętle for.

Teraz stworzymy konstruktor, który będzie tworzył nam structy z definicją itemów.
kod/// @desc konstruktor dla itemów
/// @param {String} _name Nazwa
/// @param {Asset.GMSprite} _sprite Sprite
/// @param {Enum.slot_type} _slot Slot (slot_type. )
/// @param {Real} _atk Atak
/// @param {Real} _def Defensywa
function item(_name, _sprite, _slot = slot_type.sword, _atk = 0, _def = 0) constructor {
name = _name;
atk = _atk;
def = _def;
sprite = _sprite;
slot = _slot;
}
W tym przykładzie itemy będą mieć tylko dwie statystyki - atk i def, czyli atak i defensywę.

Następnie stworzymy definicję zmiennych gracza - jego ataku, defensywy i ewkipunku:
kod// statsy gracza
global.atk = 0;
global.def = 0;
// definicja itemów i ekwipunku
global.equipment = array_create(slot_type.__size, -1); // tablica o rozmiarze 6, indeksy od 0 - 5
A na koniec stworzymy tablicę, w której znajdą się structy z itemami. ID itemów będą indeksy tej tablicy.
kodglobal.items = [
new item("Hełm (srebro)", spr_helmet, slot_type.helmet, 0, 2),

new item("Miecz (brąz)", spr_sword_brown, slot_type.sword, 2, 0),
new item("Miecz (srebro)", spr_sword_silver, slot_type.sword, 5, 0),

new item("Zbroja (brąz)", spr_armor_brown, slot_type.armor, 0, 5),
new item("Zbroja (srebro)", spr_armor_silver, slot_type.armor, 0, 10),

new item("Tarcza (srebro)", spr_shield_silver, slot_type.shield, 0, 2),
new item("Tarcza (złoto)", spr_shield_gold, slot_type.shield, 0, 5),

new item("Nogawki (brąz)", spr_pants_brown, slot_type.pants, 0, 1),
new item("Nogawki (srebro)", spr_pants_silver, slot_type.pants, 0, 2),

new item("Buty (brąz)", spr_boots_silver, slot_type.boots, 0, 2),
new item("Buty (srebro)", spr_boots_silver, slot_type.boots, 0, 2),
];
Zatem Hełm ze srebra to item 0, a Buty ze srebra to 10 (bo to 11 element).

Potrzebujemy jeszcze 3 funkcji - tworzącej przedmioty na ziemi (wykorzystamy ją na starcie gry, oraz wyrzucając ekwipunek), funkcji dodawania i usuwania ekwipunku.

Tworzenie przedmiotu:
kod/// @desc Tworzy item na ziemi
/// @param {Real} _item_id id itemu w global.inventory
function create_item(_item_id) {
return instance_create_layer(irandom_range(300, 900), irandom_range(200, 500), layer, obj_item, {item_id: _item_id});
}

Dodanie itemu do ekwipunku (po kliknięciu, ale można też wymusić jakiś item w dowolnym momencie gry). Ponieważ za chwilę dodamy funkcję do usuwania przedmiotu z ekwipunku, skorzystamy z niej i jeśli coś już aktualnie jest w ekwipunku, wyrzucimy to - to raptem 2 dodatkowe linijki kodu.
Wskazówka:
Jeśli nie chcesz takiej funkcji i gracz ma ręcznie wyrzucić item zanim doda nowy, wystarczy zakomentować pierwszego ifa.
kod/// @desc Ekwpuje item
/// @param {Real} _item_id id itemu w global.inventory
/// @returns {Bool}
function item_equip(_item_id) {
var _slot = global.items[_item_id].slot; // w jaki slot władować

// usuń jeśli coś już jest
if (global.equipment[_slot] != -1) {
item_remove(_slot);
}

global.equipment[_slot] = _item_id; // ustal jaki item jest teraz w tym slocie
// zwieksz statsy
global.atk += global.items[_item_id].atk;
global.def += global.items[_item_id].def;

return true;
}
Następnie, usuwanie itemu z danego slotu ekwipunku - oczywiście o ile coś tam jest (-1 oznacza, że nic).
kod/// @desc Usuwa item z ekwipunku
/// @param {Real} _slot numer slotu w global.equipment
/// @returns {Bool} Zwraca czy usunieto item
function item_remove(_slot) {
if (global.equipment[_slot] == -1) {
return false; // juz jest pusty
}

var _item_id = global.equipment[_slot]; // sprawdź jaki item jest w tym slocie

// ZMNIEJSZ statsy
global.atk -= global.items[_item_id].atk;
global.def -= global.items[_item_id].def;

create_item(_item_id); // wyrzuc na ziemię

global.equipment[_slot] = -1; // ustaw, ze nic tu nie ma

return true;
}

Na koniec dodamy jeszcze jedną funkcję pomocniczą, która wyświetli nam opis przedmiotu obok kursora myszy:
kod/// @desc Rysuje opis itemu
/// @param {Real} _item_id id itemu w global.inventory
function item_desc_draw(_item_id) {
/// ustaw kolor i font
draw_set_color(c_white);
draw_set_font(fnt_main);
/// ustaw opis
var _desc = $"{global.items[_item_id].name}\nAtk: {global.items[_item_id].atk}, Def: {global.items[_item_id].def}";

/// rysuj, podążaj za kursorem
draw_text(mouse_x + 20, mouse_y + 10, _desc);
}

To już wszystko co dodawaliśmy w skrypcie scr_items, można go zamknąć. Razem powinno tam być 5 funkcji, 4 zmienne globalne (poza funkcjami) i enum.

Przy okazji, w funkcji rysowania zauważyliście może, że wymuszam czcionkę fnt_main. To dlatego, że domyślny GMowy font nie obsługuje polskich znaków diakrytycznych, więc aby je wyświetlić, należy stworzyć nową czcionkę fnt_main, oraz wybrać w niej:
- Add (Add new range)
- From Code, Add Range
Można zamknąć okno czcionek.

Teraz przejdziemy do programowania obiektów.

Obiekt - item
Pierwszy z obiektów jest bardzo prosty - to obiekt item. Będzie posiadał zmienną item_id, ale skorzystamy z funkcjonalności GMa która pozwala nam przekazać zmienną w trakcie tworzenia obiektu, przed eventem Create i w tym evencie sprawdzić, czy została ona ustawiona.

Tworzymy więc obj_item i otwieramy okno Variable Definitions. Tam dodajemy zmienną item_id, typu Real, o domyślnej wartości -1.

Grafika: /upload/ajax/20240612_32c38909fecf5a28ea04481e86c91faa.png

Event Create
Sprawdzamy, czy przy instance_create_layer() przekazano id_itemu i jeśli tak - ustawiamy sprite tego obiektu, jeśli nie - niszczymy go:
kodif (item_id == -1) {
instance_destroy();
}

sprite_index = global.items[item_id].sprite;

Draw
Rysujemy item, a jeśli akurat jest nad nim kursor myszy, pokazujemy opis:
koddraw_self();

if position_meeting(mouse_x, mouse_y, self) {
item_desc_draw(item_id);
}

Left Pressed
Jeśli wciśnięto lewy przycisk myszy na danym itemie - dodajmy go do ekwipunku i niszczymy item w grze:
kod/// @desc ekwipuj
item_equip(item_id);
instance_destroy();

Wyświetlanie ekwipunku
Teraz sprawa najtrudniejsza, ale i najważniejsza - wyświetlanie ekwipunku na ekranie. Część kodu będzie przypominać ten dotyczący wyświetlania inventory z przykładu który wcześniej wspomniałem. Zaczniemy jednak od tego, że nasz obiekt na start stworzy nam po 1 sztuce każdego możliwego itemu na obszarze gry.

Tworzymy obiekt obj_equipment.

Event Create
kod/// stwórz po 1 sztuce każdego itemu
var n = array_length(global.items);

for(var i = 0; i < n; i++) {
create_item(i);
}

Draw GUI
Teraz czas na rysowanie.
Ponieważ itemy mogą być wyświetlane na tle postaci, odpowiednie sloty muszą wylądować w odpowiednich miejscach.
Wykonamy pewną sztuczkę - zamiast rysować po kolei każdy z 6 slotów, narysujemy ich nieco więcej - aż 12.
Stworzymy jednak tymczasową tablicę, która będzie trzymała informację w jakim kwadracie jest jaki slot, a jeśli w danym kwadracie nie ma być nic wyświetlane, wpiszemy tam -1.
Zamiast więc wyświetlać ekwipunek na pozycjach 0 - 6, wyświetlimy go na pozycjach 1, 3, 4, 5, 7, 10.
Następnie, w pętli, odczytamy jaki "faktycznie" slot ma być w danym miejscu i na tej podstawie wyświetlimy już prawidłowy element z ekwipunku.
kod// ustal w którym kwadracie będzie widoczny item z jakiego slotu
var slot_id = [
-1, slot_type.helmet, -1,
slot_type.sword, slot_type.armor, slot_type.shield,
-1, slot_type.pants, -1,
-1, slot_type.boots, -1,
];
Teraz przygotujemy tymczasowe zmienne:
kod// przygotuj zmiene tymczasowe
var row = 0, col = 0; // rząd, kolumna komórki
var n = array_length(slot_id); // ilosc tablicy ze slotami
var xx = 0, yy = 0; // x i y aktualnej komórki
var hover = false; // czy kursor najechał

A następnie narysujemy cały ekwipunek. Jak już wspomniałem, jeśli dany slot_id = -1, pomijamy rysowanie korzystając z instrukcji continue;, a w przeciwnym wypadku wiemy, że jest to slot z global.inventory. Wciąż jednak może tam nie być żadnego itemu, dlatego trzeba będzie sprawdzić czy nie jest on równy -1 (ostatni "if").

Standardowo sprawdzimy też czy najeżdżamy kursorem - jeśli tak, to zmieniamy obwódkę prostokątu na czerwono. Jeśli jest tam jakiś item, dodatkowo klikając PRAWYM przyciskiem myszy, możemy go usunąć z tego slotu.
kod/// pętla rysująca przedmioty
for(var i = 0; i < n; i++) {

if (slot_id[i] == -1) {
// w tym slocie nie ma zadnego typu itemu, pomiń
continue;
}

// rysuj w prostokacie, max 3 sloty na linię
col = i mod 3;
row = i div 3;

// pozycja slotu
xx = 10 + col * 40; // 40, bo 32 + 8 pikseli marginesu
yy = 10 + row * 40;

// sprawdź kursor
hover = false;
draw_set_color(c_gray);

// jeśli kursor najechał
if (mouse_x >= xx and mouse_x < xx + 32) {
if (mouse_y >= yy and mouse_y < yy + 32) {
hover = true;
draw_set_color(c_red);
}
}

// rysuj prostokąt
draw_rectangle(xx, yy, xx + 32, yy + 32, true);

// sprawdź czy w tym slocie jest jakiś item
var _item_id = global.equipment[ slot_id[i] ]; // mapuj slot w tym kwadracie na ekwipunek

// jeśli jest, narysuj go
if (_item_id != -1) {
draw_sprite(global.items[ _item_id ].sprite, 0, xx, yy);

// jeśli kursor najechał, dodatkowo pokaż opis
if (hover) {
item_desc_draw(_item_id);

// jeśli kursor najechał i wciśnięto PPM, usuń item
if (mouse_check_button_pressed(mb_right)) {
item_remove(slot_id[i]);
}
}
}
}

Na koniec wyświetlimy jeszcze globalne statystyki gracza, żeby sprawdzić, czy dodawanie i usuwanie itemów zwiększa/zmniejsza atak i defensywę.
kod// statystyki gracza
draw_set_color(c_white);
draw_set_font(fnt_main);
var _stats = $"Atak: {global.atk}\nObrona: {global.def}";
draw_text(10, 180, _stats);

Uff, prawie 70 linijek za nami i... niemal wszystko gotowe.

Wystarczy teraz dodać obj_equipment do Roomu i odpalić grę.

Grafika: /upload/ajax/20240612_228f9c20c0006fc95b3cb04eb3703988.png

Gotowy projekt (GM 2024.4) pobierzesz tutaj: gmclan.org/upload/gm/gmclan_equipment.yyz .
Komentarze (łącznie 0):
Nie ma jeszcze żadnego komentarza. Czas to zmienić

Najnowsze wersje GameMakera:

Stabilna
2024.8.1.171 • 2024.8.1.218
wydana 33 dni temu
LTS
2022.0.2.51 • 2022.0.2.49
wydana 362 dni temu
Beta
2024.1100.0.634 •
2024.1100.0.658
 0.13.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
40 użytkowników online na discordzie:
Moho, s..., Nitro Slav, Tymon, Carl-bot, EchoDuck, GibkiKaktus, Alkapivo, Kuzyn, Gameduro, OdrzuconyKrakers, HappyOrange, fervi, Sevitaus, Radek Ignatów, PhysX ᴺⱽᴵᴰᴵᴬ, r..., Skini, MKP (GEM), Moldis, Arrekin, MagnusArias, Domeen0, Dyno, 🆅🅸🆃🅾74🅼, szmalu, Morro, ZYGZAK, Miłosz, 21Lancz, 🧁Cupcake🧁, Voytec, m..., bagno, Tidżi, LadyLush, l..., Add92, Shockah, Kandif
Shoutbox
Wojo (15:38, 05.09.24)
Ciekawe
gnysek (11:54, 14.08.24)
Ruszyła beta nowego runtime, a stary dostanie już tylko dwa ficzery (UI Layery i obsługę SVG jako vertexy).
Wojo (11:51, 14.08.24)
Co się stało?
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
Starsze wpisy znajdziesz w Archiwum.
Ankieta
Ile zarobiłeś do tej pory na grach stworzonych w GM?