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!





Brak komentarzy:

Prześlij komentarz

Tu możesz wstawić swój komentarz