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!














01 stycznia 2025

Inventory, podejście pierwsze

 Rozpocząłem prace nad zrobieniem inventory. Wstępna wersja wygląda ...yyy... źle, ale przynajmniej wiem gdzie mniej-więcej trafią poszczególne elementy oraz jak nimi zarządzać podczas tworzenia. Pooglądałem youtubów o tworzeniu gui w Godot i nie powiem - przydały się żeby zobaczyć jak w miarę sensownie poukładać elementy na ekranie. Jeśli wystarczy mi cierpliwości to celuję wykonanie czegoś na podobieństwo jakiegoś Diablo, ale co wyjdzie to czas pokaże.

Na tę chwilę nie ma w podglądzie postaci i inventory żadnej funkcjonalności (poza tym że się pokazuje i ukrywa) tylko placeholdery na to co gdzie ma wylądować.

Pięknie nie jest, ale od czegoś trzeba zacząć.


31 grudnia 2024

Zakończenie roku


Przygotowuje dłuższy wpis powiązany z utworzeniem kodu odpowiadającego za tworzenie przejść między scenami. Zapomniałem jak mozolne jest gdy w formie pisanej chcesz dokładnie wytłumaczyć jak działa kod oraz dlaczego tak. 

Mam nadzieję w ciągu kolejnych dni dodać działające inventory postaci. Na te potrzeby stworzyłem (nie wiem czy to się przyda ale dodałem) pasek szybkiego dostępu do wybranych przedmiotów. Efekt pracy widać na zrzucie poniżej.

Pasek wyszedł dosyć zgrabnie, przyciski są responsywne i opakowane w grafikę.
Sama mapka to wstępny projekt świątyni Seta oraz dwóch generycznych strażników,
których można ubić.

Pasek szybkiego dostępu zrobiony częściowo na bazie tutoriala: How to setup hotbar

Aktualna wersja gry (powiedzmy) pozwala przejść między poszczególnymi mapami od tawerny do jaskini pod świątynią.

Na dniach napiszę podsumowanie wykonanych do tej pory prac. Tymczasem:


                     SZCZĘŚLIWEGO NOWEGO ROKU 

                                     (pozwolicie iż zacytuje życzenia, które otrzymałem)

                     I ŻEBY SIĘ NAM KOMPILOWAŁO









 

27 grudnia 2024

Bardzo lubię aktualizacje środowisk programistycznych...

Na kilka dni odłożyłem dalsze programowanie w swojej mini-gierce. Typowe przeciążenie tematem. 

W tym czasie dodałem do kodu kilka drobnych elementów, ale za to Microsoft wypuścił duży update dla Visual Studio. Jakież było moje zaskoczenie po otworzeniu projektu gdy zobaczyłem, że wszystkie klasy, od góry do dołu świecą się na czerwono... Nie ma to jak idiotyczny update reguł pisania kodu rzucony na twarz bez wcześniejszego zapytania czy w ogóle chcesz  go używać.

Na czerwono świecą się wszystkie klasy w projekcie.

Do tej pory kod był prawidłowy a teraz już nie jest. Bo tak...

Wyłączenie wszystkich alertów lub ich wygaszenie w konfiguracji zajmie sporo czasu, którego zwyczajnie nie posiadam. 

ps.

Aktualizacje do Intellij Idea nigdy nie były tak agresywne


25 listopada 2024

Źródła wiedzy

Doszedłem do wniosku, iż podzielę się z wami informacją skąd biorę podpowiedzi lub rozwiązania problemów związanych z Godot. Nie jestem zbyt zaawansowany w programowaniu gier stąd nie wszystkie użycia wbudowanych w silnik funkcji są dla mnie jasne. W efekcie szukam informacji wszędzie, ponieważ o ile budowanie klas, struktur czy szablony projektowe nie sprawiają mi problemów tak normalizacja wektorów czy ruch w oparciu o przeliczenie delty już tak.

Jak wspominałem w pierwszym od lat wpisie na blogu bazą do kolejnego podejścia do programowania gier i godota w szczególności stała się:


Czyli platforma e-learning Zeneva Academy, którą znajdziecie tutaj: https://academy.zenva.com/

Kursy kupiłem z ciekawość, w dwóch pakietach z HumbleBundle, wszystkie wymagają znajomości języka angielskiego na zdecydowanie wyższym niż podstawowy poziome. Zapłaciłem za nie coś ok 220 pln za 69(!) kursów podzielonych na naukę Godota, trochę do Unity i chyba jakieś pojedyncze pythona. Tych do Unity na razie nie tknąłem palcem.
Dla osoby stawiającej pierwsze kroki w nauce silnika zawartość kursów będzie przydatna i pomaga oswoić się z podstawowymi pojęciami oraz sposobem poruszania się po gui silnika. Przykłady są stosunkowo proste i czytelne, a sam efekt na zakończenie lekcji zadawalający ze względu na to iż otrzymujemy działający prototyp-grę opartą o język skryptowy Godota (trochę podobny do pythona). Tutaj duży plus.
ALE
patrząc na przedstawione w lekcjach przykłady pod kątem jakości produkowanego w ten sposób kodu to ten oceniam słabo. Osoba, która nie miała wcześniej styczności z programowaniem, bardzo szybko nauczy się grupy złych nawyków poczynając od losowego umieszczania plików w projekcie przez nie dzielenia odpowiedzialności w kodzie na funkcje po wielopiętrowe ify będące zmorą wśród junior developerów. Tutaj Zenwa i jej lektorzy powinni lepiej przygotować pisany kod pod zastosowanie najprostszych zasad clean code,.  Minus! 
Czasami problemem dla słuchającego bywa sposób w jaki opisywane jest podejście do rozwiązań zastosowanych w kodzie: zdawkowe i pobieżne. Warto przedstawić słuchaczowi dlaczego musimy napisać tak, a nie inaczej zamiast po prostu pisać kod.. bo tak działa i już.
Kilkukrotnie odniosłem też wrażenie, że przykłady wymyślane są na żywo w czasie lekcji i pisania kodu. Skutecznie psuje to odbiór lekcji, a sama nauka traci na atrakcyjności.

Jeśli jednak szukacie czegoś na początek, aby poznać podstawy silnika oraz z czym to się je, kursy polecam, ale nie jako samodzielny zakup na Zenvie lecz raczej tak jak mi się udało - jakiś pakiet HumbleBundle czy coś podobnego.

YouTube

Większość z rzeczy pokazywanych na Zenvie jest dostępnych na youtube. Zenva ma tę przewagę iż uczeń dostaje ścieżkę od elementów podstawowych do tych zaawansowany. Na youtube czegoś takiego nie uświadczysz bo i nie jest to stricte platforma szkoleniowa, zaś właściciele kanałów (zwykle) nie zaprzątają sobie głowy przygotowaniem materiałów ze wskazaniem od czego najlepiej zacząć pierwsze kroki, a co zostawić sobie na moment kiedy umiemy chodzić.
Musze przyznać bez bicia, że bez lekcji z Zenvy część nagrań była by dla mnie nieczytelna lub nie zwróciłbym uwagi na elementy ważne dla rozwiązywanych problemów (np. kolejność węzłów w edytorze czy wybrane dla nich konfiguracje parametrów).

Niżej podaję linki do kanałów, które obserwuję:

https://www.youtube.com/@rapidvectors

https://www.youtube.com/@Gwizz1027

https://www.youtube.com/@ShapedByRainStudios

https://www.youtube.com/@RafaFiedo

https://www.youtube.com/@ChrisTutorialsYT

https://www.youtube.com/@dev-worm


A i jeszcze jedna rzecz na koniec jeśli nie zauważyliście po poprzednich wpisach. Swój kod w całości pisze w c# i nie używam języka skryptowego godota. 

20 listopada 2024

Pisałem, groziłem

że przerzucę kod do git-a i w końcu to zrobiłem. Był moment zwątpienia ponieważ menu bitbucket trochę się zmieniło od czasu gdy ostatni raz go używałem. Koniec końców głównym problemem po utworzeniu repozytorium był błąd w plik .gitingore, do którego nie dodałem ignorowania plików projektowych godota oraz projektu VSStudio

Efekt jest łatwy do przewidzenia. Wraz z pierwszym push na bitbucket poleciał cały cache projektowy jaki tworzy silnik oraz środowisko programistyczne.  Oczywiście po jakiejkolwiek zmianie kodu git chciał zaktualizowane wysyłane i śledzone pliki cache listując przy okazji wszystkie wykryte zmiany. Rejestrował ich setki co powodowało skuteczne zamulenie zwykłego commita. Po dłuższej walce udało się to ogarnąć i utworzyć plik .gitignore wycinający zbędne zasoby oraz przeczyścić repo.
Nie chcę tu wklejać .gitignore i po prostu wskażę skąd pobrałem bazę dla zakresu ignorowanych plików. Jak tworzycie projekt skopiujcie po prostu zakres podany w poniższym pliku: 

https://github.com/godotengine/godot/blob/master/.gitignore

Przy okazji musze przyznać, że menu plugina gita do VSStudio jest lekko nieczytelne. Niby trzy zakładki, ale po latach używania Intellij Idea jestem przyzwyczajony do innego formatu pokazywania zmian. 


Kończąc - projekt jest bezpieczny na zewnętrznym zasobie. Kamień milowy osiągnięty! (gdybym jakiekolwiek założył :) ).




p.s.
W międzyczasie musiałem zmienić klawiaturę ponieważ stara (mocno przesadzone stwierdzenie, miała może z rok) uległa skutecznemu uszkodzeniu po zalaniu z użyciem posłodzonej herbaty. Niemal wszystkie klawisze, których używa się pod małym palcem prawej dłoni, uległy zalepieniu.
Odbijający po 2s enter czy shift nie pomagają w pisaniu. 

Trzymajcie napoje z dala od klawiatur!

17 listopada 2024

Ładniejsze okna dialogowe

Jak wszyscy wiemy wygląd okien dialogowych w grze to bardzo ważna sprawa. Fajnie, aby były czytelne przejrzyste i nie wymagały analizy o co w nich w ogóle chodzi. W mojej gierce okienka były raczej... brzydkie. Musiałem coś z tym zrobić. Wymagało to zrozumienia jak pracować w Godot  z elementami, z których robi się gui.

Po przewinięciu kilku (słabych) tutoriali oraz obejrzeniu jednego dobrego (link na końcu wpisu)  wyszedłem od takiego menu aktualnych questów 


do nowego okienka   
 
Jak widać nadal jest true/false dla postępu w pod-zadaniach,
ale całość wygląda już dużo lepiej i nie zachodzi na pół ekranu

A tutaj nowe menu dialogowe:  

Mam jeszcze problem ze skalowaniem wielkości okna dialogowego
względem jego zawartości, ale przynajmniej już nie straszy jak wersja bez tekstur. 


Do wykonania teksturowanych grafik dla okien dialogowych wykorzystałem: Kenny ui-pack-adventure

Najlepiej wytłumaczone zasady tworzenia menu znalazłem u CocoCode na youtubie pod linkiem Create MAIN MENU for your Godot game

Inne tutoriale z tego kanału także są bardzo dobre i jeśli siedzisz przy godot oglądając znajdujące się tam wrzutki możesz zyskać sporo wiedzy.

15 listopada 2024

Quest Completed

Niemal udało mi się skończyć kod służący obsłudze questów. Na tę chwilę bohater nadal jest w stanie zająć się tylko jednym zadaniem, ale kod jest już przygotowany pod to, aby mógł ich otrzymać więcej. Poprawiłem mechanizm rozpoznawania, którą aktualnie kwestię powinien otrzymać od NPC w efekcie zmienił się format jsona, z którego czytam dane dla zadań przekazywanych graczowi. Dla urozmaicenia dorzuciłem muzyczkę grającą w pętli w tle podczas rozgrywki i zmieniającą się przy zakończeniu questa u NPC. Mechanizm umożliwia łatwe przełączanie utworów podczas wydarzeń z gry (przejścia między obszarami, wejścia w dany obszar czy np. walki)


Aktualny wygląd jsona opisującego sam quest z możliwościami dialogowymi poniżej. Nie do końca jestem przekonany w trzymaniu wskazania na startowy dialog dot. questa w tym pliku (pole EntryDialog) ale na razie spełnia on swoją funkcję. 

Jak łatwo można się domyślić RevisitDialog odpowiada za to co mówi NPC jeśli odwiedzimy go przed zakończeniem zadnia zaś  CompletedDialog za dialog gdy wszystkie podzadania są zakończone i otrzymamy nagrodę.

[
    {
        "Id": "CANDLES_RECOVER",
        "Title": "Odzyskaj lichtarze dla tajemniczej nieznajomej",
        "Description": "W swiatyni seta znajduja sie lichtarze,
nizeznajoma chce je odzyskac",
        "Giver": "Tajemnicza nieznajoma z karczmy",
        "GiverId": "MYSTERIOUS_STRANGER",
        "Status": "ACCEPTED",
        "EntryDialog": "res://assets/dialog/levels/taverupperlevel/
PrincessTalkOne.json",
        "RevisitDialog": "res://assets/dialog/levels/taverupperlevel/
PrincessTalkTwo.json",
        "CompletedDialog": "res://assets/dialog/levels/taverupperlevel/
PrincessTalkCompleted.json",
        "Tasks": [
            {
                "TaskCompleted": false,
                "TargetLocation": "SET_TEMPLE",
                "Type": "DISCOVER"
            },
            {
                "TaskCompleted": false,
                "Type": "FETCH",
                "TargetLocation": "SET_TEMPLE",
                "TargetNumber": "1",
                "TargetName": "CANDLE_01"
            }
        ],
        "Reward": {
            "Xp": 100,
            "Gold": 1000
        }
    }
]


Z innych ważnych zmian: 
- mocno skomplikował się plik zarządzania questami, 
- podobnie ilość sygnałów (events) jakie są do tego przypisane
- postać niezależna nie steruje już tak mocno samym oknem dialogowym (chociaż... trochę musi) oraz - - nie jest zależna od pliku z questami (a była co akurat było bez sensu)
- bez problemu mogę też dodawać dialogi do npc-tów, którzy nie przekazują żadnych questów.
- Poprawnie działa (raczej :) ) podsumowanie zadania i późniejsze interakcje z npc gdy nie ma już żadnego zadania dla gracza.


A poniżej kilka zrzutów

Dialog przy pierwszym spotkaniu, po którym możemy otrzymać zadanie.



Tutaj widać, że pod-zadania dla questa zostały wykonane - oba oznaczone jako TRUE.

Kolejno podsumowanie questa


I kolejne podejście - brak zadań u tego NPC.


Z ciekawych rzeczy które musiałem znaleźć to przypięcie się z poziomu kodu c# do sygnału przypisanego do węzła z godot. 
Poniżej jest to linia gdzie do węzła o typie AudioStreamPlayer2D podpinam się w sygnał Finished  (czyli zakończenia się grania dźwięku) i uruchamiam funkcję RemoveChildNode()


    private void QuestDone(String questDone)
    {
        AudioStreamPlayer2D questCompletedPlayer = new AudioStreamPlayer2D();
        questCompletedPlayer.Stream = ResourceLoader.Load<AudioStreamWav>("res://assets/audio/music/questcompleted.wav");
        questCompletedPlayer.SetVolumeDb(-30);
        questCompletedPlayer.Autoplay = true;
        questCompletedPlayer.Name = "questcompleted";
        AddChild(questCompletedPlayer);
        questCompletedPlayer.Play();
        backgroundPlayer.Stop();

        questCompletedPlayer.Finished += () => RemoveChildNode();
    }