C# Factory Pattern w praktyce – Tworzenie postaci w grze RPG krok po kroku [Część 4]

C# Factory Pattern w praktyce

Chcesz tworzyć bohaterów i potwory w grze RPG jedną linijką kodu?
W tej części kursu pokażę Ci, jak zastosować wzorzec projektowy Factory w C#, aby centralnie zarządzać procesem generowania postaci. Nauczysz się wczytywać dane z plików TXT i JSON, oddzielać logikę biznesową od logiki tworzenia obiektów oraz pisać kod, który łatwo rozbudujesz bez kodowego spaghetti.

📌 Zobacz też: C# Klasy i Obiekty – Gra RPG cz.1 | C# System Walki – Gra RPG cz.2 | Dziedziczenie i Polimorfizm w Grze RPG [Część 3]

🧠 Dlaczego warto używać wzorca Factory w C#?

Główne zalety:

  • Czysty kod – logika tworzenia obiektów jest w jednym miejscu.
  • Elastyczność – łatwo dodajesz nowe typy postaci bez ruszania istniejącego kodu.
  • Polimorfizm – kod gry operuje na interfejsach lub klasach bazowych, nie na szczegółach implementacji.

🏗 Tworzymy interfejs fabryki

Najpierw definiujemy interfejs, który określa kontrakt tworzenia postaci:

namespace DevHobby.Code.RPG
{
    public interface IPostacFactory
    {
        Postac StworzBohatera(string typ, string imie);
        Postac StworzPotwora(string typ);
    }
}

💡 Dzięki temu możemy mieć różne implementacje fabryk i w przyszłości je wymieniać bez zmiany reszty kodu.


⚙ Implementacja klasy PostacFactory

Wzorzec Factory centralizuje logikę tworzenia:

using System.Text.Json;

namespace DevHobby.Code.RPG
{
    public class PostacFactory : IPostacFactory
    {
        public Postac StworzBohatera(string typ, string imie)
        {
            return typ.ToLower() switch
            {
                "wojownik" => new Wojownik(imie),
                "lucznik" => new Lucznik(imie),
                "mag" => new Mag(imie),
                _ => throw new ArgumentException($"Nieznany typ bohatera: {typ}")
            };
        }

        public Postac StworzPotwora(string typ)
        {
            return typ.ToLower() switch
            {
                "ork" => new Ork(),
                "goblin" => new Goblin(),
                "smok" => new Smok(),
                _ => throw new ArgumentException($"Nieznany typ potwora: {typ}")
            };
        }
    }
}

Switch expression w C# 8+ pozwala na krótszy, czytelniejszy kod.
✅ Wyjątek w przypadku błędnego typu zabezpiecza nas przed nieprawidłowymi danymi.
✅ => oznacza zwróć to co jest po prawej stronie
✅ _ to jest “default case”, oznacza “wszystko inne”

Użycie w Program.cs

Teraz, zamiast tworzyć postacie bezpośrednio za pomocą operatora new
w pliku Program.cs, użyjemy naszej nowej fabryki.
Dzięki temu główna pętla gry nie musi “wiedzieć”,
jak tworzone są konkretne obiekty.

Zamiast: (bezpośrednie tworzenie):

var rycerz = new Wojownik("Sir Galahad");	// Bezpośrednio wywołujemy konstruktor
var czarnoksieznik = new Mag("Mroczny Mag");	// Musimy "wiedzieć" jakie klasy istnieją
var lucznik = new Lucznik("Legolas");		// Kod jest "sztywny"

var ork = new Ork();
var smok = new Smok();
var goblin = new Goblin();

Problemy tego podejścia:

  • Jeśli zmieni się konstruktor Wojownika,
    musimy zmieniać kod w wielu miejscach
  • Trudno testować – nie możemy podpiąć “fake” obiektów
  • Kod gry jest bezpośrednio powiązany z konkretnymi klasami

Robimy

var factory = new PostacFactory();
var rycerz = factory.StworzBohatera("wojownik", "Sir Galahad");
var czarnoksieznik = factory.StworzBohatera("mag", "Mroczny Mag");
var lucznik = factory.StworzBohatera("lucznik", "Legolas");
var ork = factory.StworzPotwora("ork");
var smok = factory.StworzPotwora("smok");
var goblin = factory.StworzPotwora("goblin");

Zalety nowego podejścia:

  • Jedna zmiana w fabryce = zmiana w całej aplikacji
  • Możliwość tworzenia postaci na podstawie danych z pliku/bazy
  • Łatwiejsze dodawanie nowych typów postaci

📂 Tworzenie postaci z pliku TXT

Plik postacie.txt:

# typ,imie
bohater:wojownik,Sir Galahad
bohater:mag,Mroczny Mag
bohater:lucznik,Legolas
potwor:ork,Ork
potwor:goblin,Goblin
potwor:smok,Smok

Implementacja wczytywania:

        public List<Postac> WczytajPostacieZPliku(string sciezka)
        {
            var lista = new List<Postac>();

            foreach (var linia in File.ReadAllLines(sciezka))
            {
                if (string.IsNullOrWhiteSpace(linia) || linia.StartsWith("#"))
                    continue;

                var czesci = linia.Split(',');
                if (czesci.Length != 2) continue;

                var kategoria = czesci[0].Split(':')[0].Trim().ToLower();
                var typ = czesci[0].Split(':')[1].Trim();
                var imie = czesci[1].Trim();

                if (kategoria == "bohater")
                    lista.Add(StworzBohatera(typ, imie));
                else if (kategoria == "potwor")
                    lista.Add(StworzPotwora(typ));
            }

            return lista;
        }

Użycie w Program.cs

// Tworzenie bohaterów     
var factory = new PostacFactory();

// Wczytanie z pliku konfiguracyjnego
var wszystkiePostacie = factory.WczytajPostacieZPliku("postacie.txt");

// Pokazujemy status przed walką
wszystkiePostacie.ForEach(p => p.PokazStatus());

Korzyści

  • Dodanie nowej postaci = dopisana linijka w pliku
    np. potwor:troll,Brzydki Troll
  • Gra sama odczyta i stworzy obiekt bez zmian w kodzie
  • Fabryka dalej ukrywa wszystkie new

💡 Prosta struktura TXT = szybkie testy i edycja danych bez rekompilacji.


📦 Tworzenie postaci z pliku JSON

Struktura postacie.json:

[
  { "Kategoria": "bohater", "Typ": "wojownik", "Imie": "Sir Galahad" },
  { "Kategoria": "bohater", "Typ": "mag", "Imie": "Mroczny Mag" },
  { "Kategoria": "bohater", "Typ": "lucznik", "Imie": "Legolas" },
  { "Kategoria": "potwor", "Typ": "ork", "Imie": "Ork" },
  { "Kategoria": "potwor", "Typ": "goblin", "Imie": "Goblin" },
  { "Kategoria": "potwor", "Typ": "smok", "Imie": "Smok" },
]

Zalety JSON vs TXT:

  • Struktura – JSON ma wbudowaną strukturę, łatwiej parsować
  • Walidacja – JsonSerializer automatycznie sprawdzi poprawność
  • Rozszerzalność – łatwo dodać nowe pola (np. HP, siła) bez zmiany parsera

Standardowość – JSON jest uniwersalnym formatem

Model konfiguracyjny:

namespace DevHobby.Code.RPG
{
    public class PostacConfig
    {
        public string Kategoria { get; set; }  // "bohater" lub "potwor"
        public string Typ { get; set; }        // np. "wojownik"
        public string Imie { get; set; }       // np. "Sir Galahad"
    }
}

Metoda wczytująca JSON:

        public List<Postac> WczytajPostacieZJson(string sciezka)
        {
            var json = File.ReadAllText(sciezka);
            var configList = JsonSerializer.Deserialize<List<PostacConfig>>(json);

            var lista = new List<Postac>();

            foreach (var cfg in configList)
            {
                if (cfg.Kategoria.ToLower() == "bohater")
                    lista.Add(StworzBohatera(cfg.Typ, cfg.Imie));
                else if (cfg.Kategoria.ToLower() == "potwor")
                    lista.Add(StworzPotwora(cfg.Typ));
            }

            return lista;
        }

📌 Checklist – co już mamy dzięki Factory?

  • Jedno miejsce tworzenia wszystkich postaci
  • Możliwość łatwej zmiany źródła danych (TXT, JSON, baza)
  • Elastyczne dodawanie nowych typów postaci
  • Czysty i skalowalny kod

🚀 Korzyści z Factory Pattern w grach RPG

  1. Centralizacja logiki – łatwiejsze debugowanie.
  2. Mniejsze powiązania – klasy gry nie muszą znać szczegółów konstrukcji obiektów.
  3. Skalowalność – dodanie Boss lub NPC to dosłownie jedna linijka w fabryce.

📚 Kolejne kroki

  • Dodaj fabrykę przedmiotów (ItemFactory)
  • Wczytuj dane z bazy SQLite
  • Połącz system Factory z systemem walki z części 2

📌 Zobacz też: C# Klasy i Obiekty – Gra RPG cz.1 | C# System Walki – Gra RPG cz.2 | Dziedziczenie i Polimorfizm w Grze RPG [Część 3]

Podobał Ci się tutorial? Zostaw komentarz i podziel się efektami!

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *