31 października 2025

Zmiany idą

 Musiałem zmienić wygląd gui. Poprzednie całkowicie nie sprawdza się na telefonie. Mam nadzieję, że nowe będzie czytelniejszy oraz wygodniejsze w użyciu. Niestety jego pełne uruchomienie będzie wymagał sporo pracy i zmian w projekcie


GUI upodobni się do tego jakie jest w starym generatorze
napisanym w java script



I po kilku chwilach pracy mam zmianę na lepsze

Teraz tylko muszę przywrócić pełną funkcjonalność.

23 października 2025

Apka na telefonie

Tak jak pisałem ze dwa posty temu chciałbym mieć możliwość uruchomienia aplikacji na telefonie. Stąd też zamiast zająć się naprawianiem kilku oczywistych błędów w aplikacji ... odpaliłem AndroidStudio i po pewnych perturbacjach wrzuciłem aplikację na swój telefon (no na cudzy to się nie da :D )
Śliczna ikonka  :) Copilot się sprawił



Jakież było moje zaskoczenie po uruchomieniu programu, że ten ... delikatnie mówiąc, źle skaluje GUI względem rozdzielczości na ekranie. Właściwie to wcale ponieważ nie pomyślałem że będzie to problemem. Do kompletu nie ładuje się konfiguracja... Efekt poniżej. 

Obrazek dopasujcie wielkością do ekranu telefonów
to zrozumiecie w czym problem.

Przy tak małym GUI do zestawu z aplikacją powinienem dosyłać:
- lupę
- rysik

Brak konfiguracji powoduje, że aplikacja jest całkowicie nieprzydatna.

Walka z kodem i AndroidStudio

Kilka kliknięć później oraz parę godzin surfowania po forach... Konfiguracja ładuje się prawidłowo ale dalej jest brzydko. 

Fajnie, konfiguracja się załadowała,
tyle że litery są tak małe że nie da się w nią niemal kliknąć. 

Wybór postaci dwuprofesyjnej jest prawie niemożliwy bo odpowiadający za to checkbox ma z 10x10 px. Jak już uda się kliknąć na klawisz losowania postaci samo przejście na zakładkę z cechami jest  sporym wyzwaniem. Podgląd okienka bazy parametrów, z której tak się cieszyłem w poprzednim poście, jest niewidoczny - zasłania ją opuszek palca...


To jednak przejściowe problemy.
Kamień milowy uważam za osiągnięty! Aplikacja jest na telefonie? Jest. Trochę nieczytelna? No trochę. Wymaga pracy? Taaa i to dużej.

Godot C# JSON i AndroidStudio i jak to uruchomić

Teraz  będzie trochę programistycznego bełkotu tj. jak wczytać pliki json w apce godot do androida. A jak w ogóle się wyeksportować to znajdziecie multum tutoriali w sieci. 

Naprawienie błędu zamyka się w podpięciu prawidłowej klasy wczytującej zasoby JSON do projektu. 

Należy zrezygnować z klasy z C# 

Rezygnujemy z File z System.IO  (implementacja poprzednia)

 string json = File.ReadAllText(filepath);

na godotową klasę FileAccess (implementacja aktualna)

  var file = FileAccess.Open(filepath, FileAccess.ModeFlags.Read);
                    string json = file.GetAsText();

i kolejna sprawa. 

Ścieżki do plików json musza wskazywać na zasoby gry, a nie na plik na classpath.

Stąd też zmieniamy:
  private const string CONFIG_FILE = "assets/db/config.json";

na
 private const string CONFIG_FILE = "res://assets/db/config.json";

I jeśli pliki konfiguracyjne same w sobie wskazują kolejne pliki to ścieżki także powinny zawierać res:. Tak jak poniżej. Oczywiście wskazanie res:// możemy dodać statycznie z poziomu kodu, ale w pliku jest bardziej profesjonalnie ;-)

  {
            "filepath": "res://assets/db/profession/knights/paladin.json"
        },


I tyle. U mnie działa.

Różowo nie jest

Nie przyzwyczaiłem się jeszcze do nowego gui  AndroidStudio w efekcie napotkałem problem, który uniemożliwia mi zdalne przeniesienie wyeksportowanej aplikacji do telefonu. Rozwiązaniem jest ręczne wklejanie pliku apk na tel. Znacząco wydłuża to proces sprawdzania czy aplikacja działa.

Drugi problem - i pewnie połączony z powyższym - to brak debuga po uruchomieniu aplikacji na telefonie. Przy moim poprzednim podejściu do androida nie miałem takich problemów, a tym razem robię coś ewidentnie nieprawidłowo. Poczytam... popatrzę i znajdę rozwiązanie.

A i żeby nie było że to całe AndroidStudio to psu na budę w tej mojej zabawie. Poprawnie skonfigurowałem logcata i widzę, że losowanie postaci rzeczywiście ma miejsce na androidzie. 

To tyle na dziś.

19 października 2025

Podgląd cech postaci

Nie miałem natchnienia na dodanie kolejnych elementów generatora dlatego też postanowiłem go trochę upiększyć. Jedną z uciążliwości generatora było poprawne wyświetlanie informacji skąd wzięła się wysokość cechy postaci. 

W ekranie dodałem specjalną etykietka, która ładowała źródło wylosowanych cech. Miała jednak spory mankament...

Po staremu

Podgląd cech

A teraz jeszcze jakieś poziomy...

Jak widać powyżej - etykieta niemiłosiernie rozpychała ekran. Do tego stopnia, że nie dało się zobaczyć niczego poniżej charyzmy czy prezencji postaci. Jak dochodziły jeszcze poziomy robiło się jeszcze gorzej. Czasem na wysokości IQ ekran wpadał za krawędź. Stąd ten element wymagał naprawienia.


Na ratunek przyszłą pływająca etykieta umieszczona w CanvasLayer, które jest niezależne od elementów na ekranie. 

Po nowemu

Po zmianach informacja o wylosowanych cechach ładowana jest w etykietkę przyklejoną do cechy. Dodatkowo dorzuciłem jakieś proste przełączanie panelu z rysunkiem. aby było wiadomo, która cecha jest aktualnie w podglądzie. 

Po najechaniu na ikonkę kwadracika zamienia się on w celownik,
a poniżej pojawia się zestaw, z którego zostały wylosowane cechy.

Teraz całość mieści się bez problemu na ekranie.

Problem jest jednak z czytelnym debug w przypadku dwóch profesji. Ze względu na to, że zgodnie z regułami wybierane są większe wyniki dla poszczególnych cech nie jestem w stanie zachować danych z profesji A oraz B i ich sensownie wyświetlić.

Tutaj przykładowe losowanie dla takiego oto bohatera

profesja Wojownik / Kleryk
poziomy 5/7


Niby wszystko ok... +3  na końcu wynikają z tego,
że kleryk jest z wyższego poziomu niż wojownik.

Przy tym łączeniu dwóch profesji nie jestem pewien czy dobrze wyliczają się cechy, które w profesji A są losowane a w drugiej już nie czyli np. dla wojownika ZR to 10+k10 na starcie, a kapłan nie ma tu żadnego bonusu. Muszę trochę dopracować budowanie tego elementu ponieważ bez mozolnego porównywania wartości w konsoli nie jestem w stanie stwierdzić czy postać w 100% wylosowała się prawidłowo. Na pierwszy rzut oka wydaje się, że tak, ale pewności brak.

Nie mam zielonego pojęcia czy tutaj jest prawidłowa wartość...

Nowy podgląd działa też z panelem odporności, ale przy dwóch profesjach nie wiem czy cokolwiek obliczane jest poprawnie.

Jak widać poziomy nadal nie są uwzględniane.


Dopracuje i to, ale nie wiem czy zdążę  na Kryształkon 2025 - więcej info tu


13 października 2025

Godot i optionButton....

Plany były większe, ale weekend zszedł mi na dość się prostej rzeczy. Przynajmniej tak do tego poszedłem na początku i srogo się zdziwiłem komplikacją.

Mianowicie jak prawidłowo obsłużyć dwie listy rozwijalne tak, aby wybrane elementy z listy A nie były dostępne do wyboru w liście B i vice versa. Jak łatwo się domyślić (i o ile czytelniku znasz zasady kryształów czasu) chodziło o wybór profesji dla postaci dwuprofesyjnej. Nikt przecież nie chce grać Wojownikiem-Wojownikiem 😊

Cel

  • jak chcemy mieć tylko postać jednoprofesyjną to wszystkie opcje powinny być dostępne 
  • jak wybieramy opcję postaci dwuprofesyjnej to w ramach listy dla drugiej profesji do wyboru nie może być dostępna aktualna wybrana profesja z listy pierwszej
  • jak zmieniamy selekcję w liście z pierwszej (lub odpowiednio drugiej) listy to opcje muszą się na przemian blokować czyli opcja A z listy pierwszej profesji nie może być dostępna w liście drugiej profesji i oczywiście odwrotnie
  • zmiana selekcji powoduje odpowiednie odblokowania / blokady w obu listach
  • jak zamykamy listę drugiej profesji musza być możliwe do wyboru wszystkie opcje dla listy pierwszej profesji
  • powinny odpowiednio zmieniać się etykiety  na ekranie
  • podobnie etykiety poziomów, które teraz sprzęgnięte są z wyborem profesji

Doprowadzenie kodu do działania zajęło nadzwyczaj dużo czasu, a powodem tego była nieznajomość … powiedzmy, że API stojącym za listami rozwijalnymi godot czyli obiektami OptionButton. Cała trudność polegała na prawidłowym obsługiwaniu kolekcji, która trzyma w sobie definicje poszczególnych elementów listy rozwijalnej czyli tzw. popup. Zanim to zrozumiałem wsparłem się copilotem (wypluł trochę bzdur) oraz zwykłym – mozolnym – testowaniem co się stanie gdy ustawię opcję tak albo śmak. I powoli, powoli udało się. 

Efekt pracy


Stan startowy - jedna profesja
    

A tutaj już widać wybór profesji. Jak widać pogrupowane są per typ. 
Jak uzyskać jakieś ikonki niżej.

Wybieramy drugą profesję i ładuje się druga lista.


Profesja Hunter nie jest dostępna w drugiej liście do wyboru ponieważ wybrano ją
w liście pierwszej.

I odpowiednio profesja Knight wybrana w drugiej liście blokuję wybór w liście pierwszej.

Po zamknięciu listy drugiej profesji i zdjęciu checkboxa że tworzymy
postać dwuprofesyjną ponownie wszystkie opcje dostępne.

Jak widać na zrzucie jest jeszcze jakiś błąd z tym, że czasem odblokowuje się nagłówek grupy, ale ... to poprawie przy okazji. 

Co do kodu

Cała magia z podstawowym blokowaniem list opierała się na poprawnym przekazaniu selekcji między listami i ich ciągłe odświeżanie.


    public void EnabledOptions(OptionButton firstOption, OptionButton secondOption)
    {
        var firstOptionSelected = firstOption.Selected;

        var secondOptionSelected = secondOption.Selected;
        var popupOptions = secondOption.GetPopup();
        var popupCount = popupOptions.ItemCount;

        for (int itemIndex = 0; itemIndex < popupCount; itemIndex++)
        {

            if (itemIndex == secondOptionSelected)
            {
                continue;
            }
            if (popupOptions.IsItemDisabled(itemIndex))
            {
                popupOptions.SetItemDisabled(itemIndex, false);
            }
            if (itemIndex == firstOptionSelected)
            {
                popupOptions.SetItemDisabled(itemIndex, true);
            }
        }
    }

Kłopotem okazało się przekazanie, które elementy są nagłówkiem listy grupującym elementy oraz co zawierają poszczególne wiersze tak żeby nie opierać się na zawartości textowej wybranej opcji. Na potrzeby przekazania do generatora chciałbym wiedzieć czy coś jest Grupą Rycerz - Profesja Paladyn czy czymkolwiek innym. Stąd powstał obiekt opakowujący każdą opcję i do niej przypisany. Żeby był możliwy do użycia w kontolce silnika musi rozszerzać typ dla Variant, a najłatwiej po prostu Node

    public partial class ProfessionMetadata(
        ReadConfig.Profession proffesion,
        int index,
        int groupIndex
    ) : Node
    {
        public int ProfIndex { get; private set; } = index;
        public int GroupIndex { get; private set; } = groupIndex;
        public int OptionIndex { get; private set; } = groupIndex + index;
        public string ProfName { get; private set; } = proffesion.Name;
        public string Class { get; private set; } = proffesion.ProfessionClass;
    }

Tenże obiek wpinany był w każdą opcję z użyciem dostępnej dla wierszy listy rozwijalnej metody .SetItemMetadata

Poniżej ustawienie dla nagłówka grupy:

        optionButton.SetItemMetadata(
            header_index,
            new ProfessionMetadata(new ReadConfig.Profession(), -1, -1)
        );

Dla wiersza z profesją dane wstawiane były identycznie (tylko zmieniał się zakres) ponieważ najpierw wczytaną z json kolekcję profesji grupowałem, tworzyłem obiekty ProfessionMetadata per profesja i przypisywałem do wierszy listy rozwijalnej.

    private Dictionary<String, List<ProfessionMetadata>> BuildProfessionGroup(
        ConfigReader.ReadConfig config
    )
    {
        var professions = config.Professions;
        var professionsGroups = new Dictionary<String, List<ProfessionMetadata>>();

        var professionIndex = 0;
        int groupIndex = 0;
        foreach (var prof in professions)
        {
            var currentClass = prof.ProfessionClass;
            professionsGroups.TryGetValue(currentClass, out List<ProfessionMetadata> foundGroup);
            if (foundGroup == null)
            {
                groupIndex++;
                var group = new List<ProfessionMetadata> { new(prof, professionIndex, groupIndex) };
                professionsGroups.Add(prof.ProfessionClass, group);
            }
            else
            {
                foundGroup.Add(new ProfessionMetadata(prof, professionIndex, groupIndex));
            }
            professionIndex++;
        }
        return professionsGroups;
    }

A teraz cała funkcja budująca opcje dla podanej grupy profesji (i tak wiem ifologia dla ikon powinna zniknąć a ikonka powinna pochodzić z konfiguracji jsona. Się to kiedyś zrefaktoruje)

    private static void AddGroupToProfessionOption(
        KeyValuePair<string, List<ProfessionMetadata>> group,
        OptionButton optionButton,
        bool isFirst
    )
    {
        var header_index = optionButton.ItemCount;
        optionButton.AddItem($" --- {group.Key} ---");
        optionButton.SetItemDisabled(header_index, true);
        optionButton.SetItemIcon(header_index, null);
        Texture2D icon = null;
        if (group.Key.Equals("Warrior"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0098.png");
        }
        if (group.Key.Equals("Knight"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0096.png");
        }
        if (group.Key.Equals("Thief"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0088.png");
        }
        if (group.Key.Equals("Cleric"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0100.png");
        }
        if (group.Key.Equals("Wizard"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0084.png");
        }
        optionButton.SetItemIcon(header_index, icon);
        optionButton.SetItemMetadata(
            header_index,
            new ProfessionMetadata(new ReadConfig.Profession(), -1, -1)
        );

        foreach (var metadata in group.Value)
        {
            optionButton.AddItem(metadata.ProfName);
            optionButton.SetItemMetadata(metadata.OptionIndex, metadata);
        }
    }


I są jeszcze dwie metody wspierające przywracanie dostępności poszczególnych opcji per zmiana czy tworzymy jedno czy dwu-profesyjną postać, ale jako, że jest w nich jeszcze błąd czyli to nieszczęsne odblokowywanie nagłówków, to nie pokazuję. :)

Z rzeczy, które trzeba będzie dodać to brakuje wykluczenia profesji, aby nie wybrać np. paladyna-złodzieja, jednakże ten filtr będzie już taką wisienką na torcie jak cała funkcja będzie mi działać bez błędów.

p.s

Dorzuciłem w międzyczasie sporo konfiguracji do jsonów jak np. listy broni czy zbroi.



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!