08 października 2025

Ja tu tylko programuję

Łatwo stwierdzić po datach, że od lutego niewiele się tu działo, a to z dość zaskakującego powodu. Gdzieś na początku kwietnia złapałem covida. Ten ostro mnie przeczołgał i przy okazji całkowicie wybił z rytmu oraz chęci pisania. Wystarczyło 2-3 tygodnie niezbyt dobrego samopoczucia (to tak oględnie mówiąc), aby przez kolejne miesiące nie wrócić do Sladuma i programowania w domowym zaciszu .

Dopiero niedawno naszła mnie chęć do wieczornych posiadówek z Godot i C#, a ich efektem jest rozszerzenie generatora postaci KC. I to właśnie o nim będzie ten wpis.

Aktualny stan mini projektu to możliwość wylosowania postaci z wybranych kilku ras, większości dostępnych profesji i dodania poziomu zaawansowania i kilku drobiazgów. 

Zrzut z niezbyt pięknego menu generatora


Lista obsługiwanych profesji 

Konfiguracja dla generatora została przygotowana na podstawie plików JSON. Te znów to efekt obróbki danych zawartych w moim starym generatorze napisanym w javaScript. Dostępny jest on-line na stronie Kryształów Czasu pod linkiem Tworzenie postaci do Kryształów Czasu na samym dole. 

A o samym kodzie 

Mamy teraz erę SI stąd mocno skorzystałem z tego narzędzia przy przekształceniu zawartości plików javascript do postaci json. Kilka iteracji promtów i otrzymałem eleganckie skrypty, które dane z javoscriptowchy obiektów generowały jsony. Te znów przerzuciłem do projektu w godot. Poniżej przykładowy plik definiujący cechy dla człowieka.

{
    "name": "Human",
    "genders": [
        {
            "gender": "male",
            "traits": [
                {
                    "name": "HP",
                    "base": 100
                },
                {
                    "name": "SF",
                    "base": 50
                },
                {
                    "name": "AG",
                    "base": 35
                },
                {
                    "name": "SP",
                    "base": 25
                },
                {
                    "name": "IQ",
                    "base": 60
                },
                {
                    "name": "WD",
                    "base": 70
                },
                {
                    "name": "SP",
                    "base": 0,
                    "special": true
                },
                {
                    "name": "CH",
                    "base": 40
                },
                {
                    "name": "PR",
                    "base": 20
                },
                {
                    "name": "FH",
                    "base": 10
                },
                {
                    "name": "DA",
                    "base": 0
                }
            ],
            "height": {
                "min": 150,
                "middleFrom": 171,
                "middleTo": 180,
                "max": 200
            },
            "weight": {
                "min": 50,
                "middleFrom": 71,
                "middleTo": 80,
                "max": 100
            }
        },
        {
            "gender": "female",
            "traits": [
                {

 Stąd już prosta (tia) droga do wygenerowania postaci zgodnie z założonymi w systemie zasadami. 

Tu zakładka z cechami podstawowymi

I zakładka odporności

Aktualnie pracuję nad uwzględnieniem poziomów postaci w zwiększaniu wysokości odporności per poziom postaci. Stąd pasek z kwadracikami na zamieszczonym powyżej zrzucie ekranowym z zakładki  `Resistances` . .

W konsoli można tymczasem podejrzeć kolejność wylosowanych cech oraz co - kiedy zostało wylosowane.


Co już jest zrobione:

  • wybór ras
  • wybór profesji
  • wybór poziomu 1-10
  • wybór charakteru
  • losowanie cech
  • uwzględnienie w nich wybranego poziomu
  • wyliczenie odporności
  • pochodzenie
  • dochód 
  • uwzględnienie pochodzenia w cechach postaci 

Czego brakuje?

  • losowania zalet
  • losowania wad
  • losowania zawodów zależnych od pochodzenia
  • postaci dwuprofesyjnej
  • dodawania poziomu do odporności
  • losowania biegłości
  • wybór broni
  • wybór zbroi
  • ładniejszego wyglądu
  • filtry na dostęp do profesji dla poszczególnych ras 

Dodatki, które chce zaimplementować

  • możliwości otworzenia na telefonie
  • możliwość zapisu postaci
  • możliwość odczytu postaci
  • dodawanie podnoszenia postaci po jednym poziomie

Czego się nauczyłem

Patrząc w kod to lepszej obsługi kolekcji w c# oraz dostępu do statycznych property. Szczególnie zadowolony jestem z (napisanego na nowo) kodu odpowiadającego za losowanie z użyciem wirtualnych kostek k10, k100 itp.

Wywołanie mocno się uprościło:

 var statRoll = Dices.d100.Roll;

Definicje klas poszczeg ólnych kostek są maksymalnie uproszczone (zmienia się oczywiście zakres od-do)

    public class D100 : Dice
    {
        private readonly Random rng = new Random();
        public int Roll => rng.Next(1, 101);
    }

A całość opakowuje klasa wystawiająca dostęp statycznie 

public class Dices
{
    public static readonly Dice add0 = new D0();
    public static readonly Dice d5 = new D5();
    public static readonly Dice d10 = new D10();
    public static readonly Dice d10Premium = new D10Premium();
    public static readonly Dice d50 = new D50();
    public static readonly Dice d100 = new D100();
    public static readonly Dice d76100 = new D76100();

Kod jest o niebo lepszy od mojego ostatniego podejścia do problemu implementacji kostek i rzucania nimi (ze 4 posty niżej pisałem o tym)

Na potrzeby zbierania wyników, których  sumy tworzą cechy widoczne na karcie postaci, utworzyłem specjalny obiekt rozszerzający typ Dictionary. Dało mi to eleganckie obejście problemu związanego z wiedza skąd biorą się wartości, z których powstają cechy. Obiekt na starcie buduje definicje cech jakie można losować w jego zakresie. Zachowuję przy tym kolejność dodawania składników pochodzących od profesji, rasy itp. co znacznie ułatwia obliczenia i szukanie ewentualnych błędów. Rozdzielenie odpowiedzialności wymusiło, że powstały dwa: jeden dla cech postaci a jeden dla odporności.

Mój stary kod w javascript radził sobie z tym elementem o wiele, wiele gorzej.

public class RessRollsCollection : Dictionary<StatDefinition.StatName, List<int>>
{
    public RessRollsCollection()
    {
        Init();
    }

    private void Init()
    {

        this.Add(StatDefinition.StatName.BM, new());
        this.Add(StatDefinition.StatName.ILLUSION, new());
        this.Add(StatDefinition.StatName.SUGESTION, new());
        this.Add(StatDefinition.StatName.CURSES, new());
        this.Add(StatDefinition.StatName.SHOCK, new());
        this.Add(StatDefinition.StatName.SPECIAL, new());
        this.Add(StatDefinition.StatName.BP, new());
        this.Add(StatDefinition.StatName.POISON, new());
        this.Add(StatDefinition.StatName.FUMES, new());
        this.Add(StatDefinition.StatName.TEMPERATURES, new());
        this.Add(StatDefinition.StatName.ELECTRITY, new());
        this.Add(StatDefinition.StatName.PETRIFICATION, new());
    }
public void AddStat(StatDefinition.StatName key, int value)
    {
        if (this.TryGetValue(key, out List<int> values))
        {
            values.Add(value);
        }
        else
        {
            Console.WriteLine($"Klucz: {key} nie istnieje.");
        }
    }
public int GetResistanceValue(StatDefinition.StatName searchedStat)
    {
        int computedStat = 0;
        if (this.TryGetValue(searchedStat, out List<int> values))
        {
            foreach (int number in values)
            {
                computedStat += number;
            }
        }
        return computedStat;
    }

Wyliczenie cech na poziomie karty postaci przekształciło się z prostackiego przypisania 

rolledHero.TEMPERATURES= 10 

do sumy zliczanej z pojedynczych elementów z wskazanych w obiekcie kolekcji.

   public int TEMPERATURES
    {
        get { return ResCollection.GetResistanceValue(StatDefinition.StatName.TEMPERATURES); }
    }

Nie zależy mi jakoś na wydajności rozwiązania a iterowania w koło Wojtek po kolekcjach cech i odporności niekoniecznie mi przeszkadza.

Na potrzeby odczytania jsonów powstał generyczny wczytywacz :)

        public static class JsonLoader
        {
            public static T LoadJson<T>(string filepath)
            {
                try
                {
                    string json = File.ReadAllText(filepath);
                    return JsonSerializer.Deserialize<T>(
                        json,
                        new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
                    );
                }
                catch (FileNotFoundException)
                {
                    Console.WriteLine($"❌ Plik nie znaleziony: {filepath}");
                }
                catch (JsonException ex)
                {
                    Console.WriteLine($"❌ Błąd JSON w pliku {filepath}: {ex.Message}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"❌ Inny błąd: {ex.Message}");
                }

                return default;
            }
        }


Piszę i pisze o c# a co z godot?

Cóż.. mało go tutaj: D 

Zaledwie kilka scen zlepianych w całość. Same w sobie nie są niczym nadzwyczajnym, zwykły TabContainer i trochę marginesów z labelkami i listami wyboru... Jak zacznę dodawać do nich jakiś sensowny wygląd napiszę więcej, a teraz po prostu nie ma za bardzo o czym.


No dobra coś ciekawego było. Przez jakiś czas ekran dwa razy ładował mi konfigurację co powodowało dublowanie wyników w listach rozwijalnych. Linijka kodu poniżej (z 56 linii) pozwoliła mi ustalić skąd startowany jest ekran.



Okazało się, że pomimo usunięcia ze sceny przycisku, którym przez jakiś ręcznie ładowałem konfigurację to tenże button nadal był widoczny, ale tylko w tekstowej definicji sceny w pliku tscn, Nie był widoczny w drzewie i na ekranie w trybie edycji w godot. Miał też dalej podpiętą akcję ładowania zawartości mechanizmu losowania jsonów i do tego (tu już nie wiem jak... nie wnikałem) restartował ekran.

Tyle!





23 lutego 2025

Inventory reloaded

 W ostatnich dniach (tygodniach) przepisywałem kod obsługi plecaka na taki, który w końcu będę mógł przenieść do głównego projektu. Przyjąłem w miarę wygodny dla mnie sposób pisania kodu gdzie tworzę jeden projekt surowy, z pomysłem i staram się napisać działającą funkcjonalność, drugi projekt, który jest rozwinięciem pierwszego, ale z wyczyszczonym kodem, po refaktorze i powiedzmy, że elegancko napisany oraz kolejny drobny projekcik, w którym sprawdzam drobne, często jedno funkcyjne zmiany (np. klikanie po kontrolkach i zliczanie kliknięć) lub dlaczego coś nie działa. W przypadku plecaka podejście sprawdziło się jako-tako, ponieważ przeglądania kodu w kilku repo bywa upierdliwe, jednakże udało mi się stworzyć kod, który spokojnie mogę przenieść do głównego repo przygody Sladuma.

Przy okazji przepisywania kodu na czysto wprowadziłem spore zmiany względem wersji sprzed miesiąca.

Korzystam teraz z jednej sceny i kodu dla obsługi plecaka gracza oraz postaci niezależnej dzięki czemu zniknęła masa zduplikowane kodu. Funkcjonalności podnoszenia / opuszczania przedmiotu czy chociażby zapamiętania co się znajduje w plecaku są tożsame.
Ta krytyczna zmiana spowodowała pojawienie się uciążliwego błędu związanego z podnoszeniem przedmiotu ze slota nie występującego w pierwszej wersji kodu. Dzięki niemu nauczyłem się iż nawet jeśli scena jest niewidoczna to przechwytuje eventy rozsyłane po drzewie (w sumie nic dziwnego). Zrozumienie przyczyny dziwacznego zachowania zajęło dobrych kilka godzin szukania, a poprawienie zamknęło się w jednej linijce kodu.

Inna ważna zmiana to możliwość zapamiętania, w który slot wylądował przedmiot po przeniesieniu. Plecak nie jest już wypełniany przedmiotami od pierwszego pustego slota do końca plecaka, ale względem zapisanej konfiguracji. Ta zmiana wprowadziła inny ciekawy błąd: przedmiot zostawiony w slocie z określoną kategorią np. tylko na hełm duplikował się co otwarcie plecaka :-).

Wprowadziłem też troszkę lepsze rozpoznawanie przedmiotów - porównywane są UID-y. 

Sam przedmiot nie jest już równoważny z rysunkiem, który go przedstawia. Rysunek generowany jest tylko na potrzeby wyświetlania np. gdy leży na ziemi lub po otworzeniu plecaka. Przedmiot wskazuje atlas i obszar atlasu, z którego wizualizacja ma być wygenerowana. Uprościło to kilka elementów w kodzie jak przenoszenie między plecakami npc i gracza gdzie zdarzało mi się dziwnie gubić rysunki w powietrzu. Ułatwiło także wyłapanie wiszących w drzewie i nie uwolnionych z użyciem QueeeFree() węzłów. 

Kilka zrzutów poniżej (i tak zmieniłem ponownie wygląd mapy abym wiedział w którym projekcie estem)

 

Startowy stan sceny

Otwarta skrzynka, układ przedmiotów od pierwszego wolnego slota...




Ale teraz jak poukładamy sobie przedmioty jak nam się podoba to po ponownym otworzeniu
skrzynki zostaną na przypisanych pozycjach.

Tak samo będzie z plecakiem gracza. Przedmioty zostaną tam gdzie miały być.

A tutaj widać efekt otworzenia pustej skrzynki oraz plecaka gracza...


    
Przerzućmy hełm z czerwonym piórem i
podejdźmy do poprzednio otworzonej skrzynki z przedmiotami..

Jak widać układ przedmiotów oraz zawartość skrzynek i plecaka
zostały zachowane


Teraz poprawię jeszcze kilka drobnych błędów i w wreszcie Sladum będzie mógł korzystać z plecaka...

Prawie zapomniałem jak wygląda Sladum :)


31 stycznia 2025

Inventory drag and drop

At statistic page i've saw several visits from all around the  world. THX! But...  I don't have time to write in english so it will be still in polish. Just drop text to some translator :-)


Mniejsza o większość i wizyty. Cóż ostatnio się działo? Nadal pracuje nad plecakiem bohatera oraz plecakami postaci niezależnych. Na testowej scenie umieściłem skrzynię, z którą można wejść w interakcję i zobaczyć jakie to kryje skarby. Po kilku iteracjach, stosunkowo niewielkiej ilości kodu udało się osiągnąć docelowy stan czyli możliwość przerzucania przedmiotów między plecakami postaci a interaktywnymi skrzyniami  oraz prawidłowego wyświetlania zawartości skrzyń po jej zamknięciu i otwarciu.

A jak to wygląda w grze:

    

Podchodzimy do skrzynki i wciskamy F w celu jej otworzenia


Pojawia się zawartość skrzyni


Naciskając I pojawia się plecak bohatera


Możemy przerzucić przedmiot z plecaka postaci
do skrzyni - tu przerzucona została srebrna zbroja


Pozamykać otwarte menusy...

A po ponownym otwarciu menu skrzyni przedmiot jest w środku.
Na razie nie zapamiętuje do którego slota przedmiot został odłożony
stąd ładowane są od początku wolnych slotów skrzynki
 

Możemy też podejść do innej skrzyni
i wyświetlona zawartości skrzyni zmieni się na
tę przypisaną do drugiej skrzynki.


Po napisaniu kodu wyszła ciekawa rzecz - sposób przechowywania zawartości skrzyni (i ewentualne NPC)  jest bardziej zaawansowany (na razie) niż plecak postaci gracza. Każda skrzynia ma przypisaną kolekcję przedmiotów którą później prezentuje, a postać gracza nie! To jakie przedmioty nosi postać gracza pamięta ... jego menu plecaka. Jest to bardzo złe rozwiązanie ponieważ np. chcąc sprawdzić czy postać posiada przedmiot związany z zadaniem trzeba przejechać po węzłach plecaka zamiast po (jakiejś) kolekcji posiadanych przedmiotów. Poprawienie tego elementu na szczęście nie będzie trudne. 

Kolejnym krokiem będzie możliwość robienia zakupów i menu sklepikarza. 

19 stycznia 2025

Inventory almost done!

Udało mi się stworzyć dobrze działający szablon dla inventory postaci. Praca nad nim trwała długo i z przerwami. Zacząłem jeszcze gdzieś na początku stycznia i po trzech tygodniach dłubania mogę powiedzieć: jakoś działa. Na tyle dobrze, że mogę przenieść kod do głównego projektu Sladuma i dodać kolejny element do samej gry.

Z ciekawych problemów:

Zasoby (Resource)

Miałem spory problem ze stworzeniem reużywalnych atlasów zawierającymi ikonki przedmiotów. Kłopot leżał w tym, że nie potrafiłem w prawidłowy sposób wczytać plików grafiki, aby później użyć ich jako sprite definiujący ikonkę przedmiotu. A to się wczytały, a to nie, a to źle później poruszałem się po utworzonym atlasie i nie było ikonek, a jak były (czasami) to wszystkie takie same i to wszędzie na całej planszy...

Przełomem był moment gdy zapisałem je jako godotowe zasoby czyli: Resource
public partial class TextureResource : Resource
{
    [Export]
    public AtlasTexture AtlasResource = new AtlasTexture();
}

To dało możliwość łatwego dodawania kolejnych atlasów do kodu w prosty sposób



i konfigurowania z użyciem gui godota


i korzystania z nich w jednolity sposób, tak aby nie rozłaziły się wszędzie w kodzie

public partial class ItemRepository
{
    private TextureResource ArmourAtlas = ResourceLoader.Load<TextureResource>(
        "res://scenes/resources/ArmourTextures.tres"
    );
    private TextureResource BootAtlas = ResourceLoader.Load<TextureResource>(
        "res://scenes/resources/BootTextures.tres"
    );

Efekt jest taki, że mam teraz jedno źródło generowania ikonek przedmiotów gotowe do stosunkowo łatwej rozbudowy. 
Samym zasobem może być dosłownie wszystko stąd może w dalszej pracy częściej będę sięgał po to rozwiązanie.


Duplikacja kodu


Po stworzeniu pierwszej w miarę działającej wersji plecaka problemem była duplikacja funkcji dodawania / zdejmowania przedmiotów w slotach odpowiedzialnych za plecak i za pasek szybkiego dostępu. W efekcie kod robił w sumie to samo (na obiekcie ItemSlot i HotBarSlot) ale per typ przekazanego obiektu. Co powodowało zbędne kopiowanie kodu i turbo utrudniało wyszukanie błędów (a było ich multum...)  
Ratunkiem stał się jakże by inaczej interfejs, który zdefiniował mi wszystkie wspólne funkcje i umożliwił mocne skrócenie kodu. 
Już teraz widzę, że przyda się do obsługi takich rzeczy jak sloty w otwartej skrzynce czy u sklepikarza.
Rzeczony interfejs jest trochę koślawy, ale działa (rozważałem dodanie obsługi przez delegate do jakiejś turbo klasy, ale jeszcze nie czaję jak to w c# delegaty działają)

public partial interface ISlot
{
    public Item VisibleItem { get; set; }
    public int SlotIndex { get; set; }
    public bool HasItem => VisibleItem != null;
    void PutIntoSlot(Item holdingItem);
    public Item PickFromSlot();
    public Category category { get; set; }
...// reszta kodu



Przenoszenie przedmiotów


Kolejnym ratunkiem stała się funkcja reparent. Ten magiczny zwrot potrafi samodzielnie przenieść węzeł Node w drzewie węzłów sceny. Funkcja ta bardzo przydała się do wykonania podnoszenia przedmiotu z plecaka do innego slota plecaka
Tutaj trzeba pamiętać że cała scena składa się z węzłów. Węzły tworzą drzewiastą strukturę i mamy możliwość przenoszenia liści tego drzewa pomiędzy gałęziami. 


Używanie drzewa w kodzie to bardzo rozbudowana funkcjonalność, o której nie należy zapominać! A ja zapomniałem i zamiast użyć tego od razu sporo namęczyłem się, aby uzyskać oczekiwany efekt który było 
- kliknięcie myszy 
- reparent  czyli podniesienie przedmiotu z węzła opisującego slot w inventory do nadrzędnego węzła w scenie opakowującego plecak
- kliknięcie w slot docelowy i operacja odwrotna czyli reparent z węzła opakowującego plecak do wybranego slota

Srebrny but został kliknięty i przeniesiony przez reparent do węzła opakowującego plecak.



Wyrzucanie przedmiotów


Kolejny krok to wyrzucanie przedmiotów. O ile napisanie kodu odpowiadającego za zbieranie ich z ziemi było stosunkowo proste tak wyrzucanie nie było takie oczywiste. Chciałem, aby przedmioty dało się wyrzucić myszką klikając obok plecaka. Ale jak to zrobić? Doszedłem do wniosku że zawieszę niewidzialny węzeł o typie Control obejmujący obszar na lewo od plecaka i gdy myszka - trzymająca przedmiot - znajdzie się w tym obszarze kliknięcie spowoduje wyrzucenie przedmiotu pod nogi postaci (z niewielką animacją i przesunięciem, aby przedmioty nie tworzyły stosu). 

Obszar do zrzucania przedmiotów na potrzeb zrzutu 
ma kolor różowy, a myszka trzyma hełm.



    
I rzucony na ziemię hełm.



Stworzenie tego kodu kosztowało mnie sporo czasu mimo, że definicja samego węzła była banalna. Głównym problemem był prawidłowe odczytanie sygnału kiedy myszka jest w obszarze rzucania przedmiotów 
Sygnały OnDropAreaEntered i OnDropAreaExited zapisują informację czy rzucenie przedmiotu jest możliwe. Funkcja MouseDraggedToDropArea przechwytuje event kliknięcia lewym klawiszem myszy i zrzuca przedmiot na ziemię obok postaci.

i odpowiednie oprogramowanie całej akcji.
public void MouseDraggedToDropArea(InputEvent @event)
    {
        if (@event is InputEventMouseButton mouseClicked)
        {
            if (
                mouseClicked.ButtonIndex == MouseButton.Left
                && mouseClicked.Pressed
                && holdingItem != null
            )
            {
                ConvertToPickable(holdingItem);
                holdingItem.Reparent(this);
                holdingItem.QueueFree();
                holdingItem = null;
            }
        }
    }
    

Powyższa funkcja nastręczyła mi multum problemów ponieważ wyrzucany przedmiot stawał się disposed, a ja, zadowolony trzymałem do niego referencje to tu to tam. Efekt był taki że przedmiot można było wyrzucić na ziemię ale podnieść to już się nie dawał i psuł całą grę eleganckim wyjątkiem. Naprawienie tego błędu zajęło mi sporo czasu (stąd powstało min. wspomniane wyżej użycie Resources do trzymania atlasów).

Błąd spowodował też turbo refaktor klas ponieważ musiałem mieć możliwość łatwego rozpoznawania jakie przedmioty są zrzucone, jakie mają przypisane ikonki, gdzie leżą ... czyli w sumie wszystko co o nich można wiedzieć.

Kategorie slotów


Jak mamy plecak i możliwość ubrania postaci to nie chcemy, aby na głowę postaci można było założyć np. buty. Stąd też każdy slot otrzymał kategorię zaś w plecaku pojawiła się możliwość ubrania bohatera i odpowiedniego umieszczenia przedmiotów w zależności od typu przedmiotu przyjmowanego przez kliknięty slot.

Buta nie da się wrzucić do miejsca na pierścień

   
Ale do slota z butem już tak. 



Niby prosty kod alesprawił mi pewne problemy - if-ologia dotycząca tego co można a czego nie można wrzucić do danego slota była uciążliwa do napisania, aż w końcu zamknęła się w prostej metodzie: 
 
private bool ItemFitToSlot(ISlot clickedSlot)
    {
        if (clickedSlot.category == ISlot.Category.ANY)
        {
            return true;
        }
        else if (holdingItem == null)
        {
            return false;
        }
        else if (clickedSlot.category == ISlot.Category.WAISTBAND)
        {
            return holdingItem._category == ISlot.Category.WEAPON
                || holdingItem._category == ISlot.Category.WAISTBAND
                || holdingItem._category == ISlot.Category.CONSUMABLE;
        }
        else
        {
            return clickedSlot.category == holdingItem._category;
        }
    }



Podsumowanie

Nieźle się nakodowałem, ale efekt końcowy uważam za zadowalający. Teraz tylko pożenić to co zrobiłem z głównym projektem i dreptać sobie powoli dalej. Kod kilkukrotnie zrefaktorowałem, aby można było go wygodnie edytować i dodawać nowe funkcjonalności. Powolutku poznaję lepiej c# min. pojawiły się pierwsze typy struct oraz record (zdaję sobie jednak sprawę, że jeszcze nie bardzo wiem jak z nich korzystać, ale jeśli ich nie użyję to się nie nauczę).

Z funkcjonalności, których brakuje w powyższym rozwiązaniu:
  1. wyświetlania opisów przedmiotów po najechaniu myszką i przy pasku szybkiego dostępu. 
  2. Same ikonki są koślawo sformatowane ale to efekt nie dobrania marginesów (mam nadzieję że ich dodanie nie zepsuje czegoś innego...). 
  3. opcja porównania przedmiotu z tym w który postać jest wyposażona
  4. możliwość przerzucania przedmiotów z czegoś co roboczo nazwiemy skrzynką do plecaka zamiast zawsze wszystko podnosić z podłogi 
  5. interakcja z NCP o wszystko mówiącym typie sklepikarz (tu ciekawy case - przejście z dialogu do zakupów)
  6. ... inne na które teraz nie wpadłem...



A tak wygląda plecak i pasek szybkiego dostępu na ekranie 





p.s.
Podczas pisania powższego postu i klikania po inventory okazało się że


Błąd ze źle utworzonym przedmiotem powrócił, ale przynajmniej wiem gdzie szukać (źle wczytany region w atlasie i w efekcie źle wygenerowany przedmiot).











06 stycznia 2025

Walka z plecakiem Sladuma

Okazało się, że pisanie kodu generującego gui jest dla mnie sporym wyzwaniem. Im dalej w las tym trudniej mi ogarnąć sposób i oczekiwane działanie elementów widocznych na ekranie. Często samo rozwiązanie problemu okazuje się banalnie proste jednakże sposób myślenia o gui od strony kodu jest dla mnie tak odmienny od tego co robię na codzień, że mam problemy ze stworzeniem czegoś działającego bez siedzenia jednym okiem na tutorialu, a drugim na stackoverflow. Niby miałem jakieś doświadczenia z pisaniem w JavaFX (zresztą nawet na blogu o tym pisałem) ale nigdy nie musiałem się jakoś mocno zagłębić w temat. 

Postęp jednak jest. 

Przerobiłem tutorial na Zenwie (ponownie) ale okazał się on niewystarczający, stąd poszukałem na youtube i  w końcu (pod dwóch niezbyt dobrych, albo ich po prostu nie zrozumiałem) trafiłem tu: How to Make an Inventory System in Godot . I to był strzał w dziesiątkę. W końcu pchnąłem pracę sensownie do przodu. Chylę czoła przed twórcą bo powoli zacząłem rozumieć co ja właściwie koduję. 

Plecak wersja 2.0


Po przerobieniu dwóch filmików wszystko działa mi jak w trzeba. Przedmioty można przerzucać między slotami (oj miałem tu ogromny problem z przekazaniem z jakiego slotu do jakiego trzeba wrzucić przedmioty) grupować i podmieniać w slotach (tu też miałem problem bo w godot kod wygląda inaczej niż w C#).

Czeka mnie jeszcze 6 kolejnych filmików w tej serii, ale już teraz widzę światełko w tunelu, ponieważ jak plecak zacznie mi działać (przynajmniej jako-tako) będę mógł skupić się na implementacji rozgrywki i samych map. 

Grupowanie przedmiotów w slotach

Przeniesienie przedmiotu między slotami


Przedmioty po sortowaniu


    

03 stycznia 2025

Generator postaci do... Kryształów Czasu

 Nie żeby mi się nudziło, ale generator postaci oparty o zasady Kryształów Czasu był jednym z założeń zabawy w pisanie mini gry rpg i wykorzystania przygody Sladuma jako bazy do nauczenia się Godota i C# przy okazji. 

Stąd po bardzo krótkim czasie (zaledwie dwa-trzy dłuższe posiedzenia w ciągu ostatnich dni) udało się osiągnąć wstępny projekt kolejnej mechaniki gry (i kolejnej na tę chwilę nie dokończonej lecz nie mogłem się powstrzymać) czyli wspomnianego generatora postaci.
I tak patrząc na to jak szybko udało mi się stworzyć wyjściową wersję w C# generującą cechy w porównaniu z tym jak męczyłem się przy JavieScript, aby osiągnąć podobny efekt... jestem miło zaskoczony. Niby kod, a w sumie struktury obiektów POJO mógłbym przenieść niemal 1:1 jednakże zauważyłem w starym kodzie mnóstwo elementów, które były po prostu źle przemyślane. 

Nie ma się co oszukiwać - doświadczenie w programowaniu procentuje.  

Aktualnie kod w C# jest wygodniejszy w użyciu oraz czytelniejszy. Skomplikowane elementy dotyczące struktur potrzebne do wygenerowania cech stały się bardziej generyczne. Mechanizm losuje cechy główne i odporności w identyczny sposób w oparciu o takie same obiekty przenoszone w kodzie. W poprzedniej wersji nie udało mi się osiągnąć takiej spójności danych. Zawsze też jest opcja wstępnego refaktoru napisanych klas ponieważ już teraz widzę kilka elementów, które z powodzeniem mogę skrócić.

Oczywiście nowy generator nie ma pełnej obsługi wszystkich kroków wymaganych do stworzenia pełnoprawnej postaci. Brakuje w nim losowania wagi, pochodzenia, uwzględnienia wszystkich profesji i ras czy zalet i wad postaci, zawodów...  ale już teraz widzę, że dodawanie tych elementów będzie o wiele prostsze niż w JavieScript. Dobrze też, że pliki które tworzyły bazę danych dla starego generatora, w miarę łatwo będę mógł przekształcić w pliki jsona i wczytać do kodu zamiast mozolnie wszystko przepisywać na nowo. 

Ale koniec lania wody, pora na prezentację. 

Można wylosować  człowieka (mężczyznę) w profesji wojownika na poziomie 0. Elementy wchodzące w definicji rasy oraz profesji są zgodne z tym co jest w KC-tach.

Mam trzy zakładki grupujące dane.
Ogólny wygląd jest paskudny, ale nie to było celem.

Wylosowane statystyki postaci wraz z wyświetlonymi
składowymi (po prawej stronie od cech) z których zostały obliczone


I to samo, ale dla odporności.

Stary generator postaci (dostępny tutaj Generator postaci Kryształy Czasu ) pod względem zawartości bije na głowę to co teraz napisałem jednakże siedziałem nad nim z miesiąc(jak nie dwa) a tu to dopiero początek. I w innym języku :) 

I trochę o kodzie

Jak wiecie pisze w dla Godot w C#.  Struktura klas nie jest nadmiernie skomplikowana.
Jest generator cech (zlany w jedną gównianą klasę z kontrolerem ekranu wyświetlającym cechy i to musze obowiązkowo poprawić) i generator dla odporności oraz klasy wspierające. 

I o ile część losująca nie jest zbyt interesująca najfajniejsze klasy to StatDefinition odpowiadająca za definicję cech postaci - tak głównych jak i odporności oraz klasa odpowiedzialna za kostki. Poniżej jak wygląda w kodzie (zwykłe POJO ale o szerokim zastosowaniu)

public class StatDefinition
{
    /* nazwa cechy*/
    public StatName Name;
    /* długa nazwa parametru*/
    public string LongName;
    /* wartośc bazowa */
    public int BaseValue;
    /* czy powinna być losowana w generatorze */
    public bool ShouldBeRolled = true;
    /* czy to zdolnosć profesji lub rasy i możliwa przerzucana dla lepszego wyniku */
    public bool ProffStat = false;
    /* definicja kostki jaka trzeba rzucać */
    public RollDefinition RollCode;
}

I sama definicja przykładowo Żywotność dla człowieka , której w KC nie przerzucamy przy generowaniu 
    internal StatDefinition HitPoints { get; } =
        new()
        {
            Name = StatDefinition.StatName.HP,
            LongName = "Hit points",
            BaseValue = 100,
            ShouldBeRolled = false,
        };

Tutaj inaczej dla Siły, którą nie dość że rzucamy z użyciem k100 to jest także zdolnością profesyjną i mamy możliwość wybrania większego z dwóch rzutów
    internal StatDefinition Strength { get; } =
        new()
        {
            Name = StatDefinition.StatName.SF,
            LongName = "Strength",
            BaseValue = 50,
            ProffStat = true,
            RollCode = Generator.RollCode.D100,
        };

A tutaj oparta o ten sam szablon odporność na iluzję
internal StatDefinition Illusion { get; } =
        new()
        {
            Name = StatDefinition.StatName.ILLUSION,
            LongName = "Illusion",
            BaseValue = 10,
            RollCode = Generator.RollCode.D10Premium,
        };

Powyższe pozwala szybko definiować statystyki i wrzucać do wspólnego kodu genrującego. Poprzedni kod w JavaScript nie dawał takich możliwości.

I druga klasa która wyszła dosyć zgrabnie - definiująca kostkę 

 public class RollDefinition
    {
        /* nazwa kostki */
        public string Name;
        /* min wartość dla kości */
        public int minValue;
        /* max wartość dla kości */
        public int maxValue;
        /* chyba do wyrzucenia */
        public int toAdd;
        /* czy kość premiowa */
        public bool premium;
        /* czy to pojedyncza kosc */
        public bool onedice;
        private Random rollStat = new Random();
        public int Roll()
        {
            var rolledValue = 0;
            if (minValue > -1)
            {
                if (premium)
                {
                    rolledValue = RollPremium();
                }
                else
                {
                    rolledValue = rollStat.Next(minValue, maxValue);
                }
            }
            return rolledValue;
        }

        private int RollPremium()
        {
            var rolledValue = rollStat.Next(minValue, maxValue);
            if (rolledValue == maxValue - 1)
            {
                rolledValue += RollPremium();
            }
            return rolledValue;
        }
    }


oraz przykładowa implementacja - tutaj k10 premiowe
 internal RollDefinition PremiumD10 { get; } =
            new()
            {
                Name = "PremiumD10",
                onedice = true,
                minValue = 1,
                maxValue = 11,
                premium = true,
            };

 A jakie zmiany jeszcze wejdą do kodu?

Poza wydaje się oczywistymi elementami czyli refaktorem nie używanych rzeczy (np. dwa pola w klasie RollDefinition  nigdzie nie zostały użyte czy wspomniane zlanie w jedno kontrolera ekranu z generatorem cech ) to obowiązkowe będzie wczytywanie charakterystyk dla cech czy definicji profesji z plików json. Aktualnie jest to zaszyte w kodzie (co widać po sposobie definiowania cech postaci podanym powyżej) i nie jest to nie jest optymalna metoda pracy. 
Docelowo oczywiście wrzucenie kodu do głównego projektu małej przygody Sladuma i uwzględnienie w mechanice walki. 

To na tyle.
Miłego!