Klasysy i Obiekty w C# - System Walki w Grze RPG

Klasy i Obiekty w C# – System Walki w Grze RPG w C# [Krok po kroku cz.2]

System Walki w Grze RPG w C# – Twórz Dynamiczne Obiekty i Metody (Cz. 2)

⚔️ System Walki w RPG – Czym Jest i Dlaczego Go Tworzymy?

🌐 Kontekst Projektu

W części 1 tutoriala [Zobacz też: C# Klasy i Obiekty – Gra RPG cz.1] stworzyliśmy klasę Bohater z hermetyzacją, konstruktorami i właściwościami.

Teraz czas dodać prawdziwą akcję: atak, obrażenia, leczenie i wyświetlanie statusu w pętli walki.

📊 Główne koncepcje w tej części:

  • Auto-properties z private set
  • Walidacja w konstruktorze
  • Metody interakcji między obiektami
  • Prywatna metoda OtrzymajObrazenia() i publiczna Atakuj()
  • ASCII pasek HP i konsolowa arena
  • Testy edge case’ów: obrażenia, leczenie, przepełnienie HP

🎓 Modernizacja Klasy Bohater

🔄 Zmieniamy długie właściwości na Auto-Implemented Properties

Zacznijmy od małej modernizacji naszej klasy Bohater. Wprowadzimy modyfikację i dodamy system walki. Pokażę wam bardziej elegancki sposób pisania właściwości – auto-implemented properties!

public class Bohater
{
    public string Imie { get; private set; }
    public int PunktyZycia { get; private set; }
    public int Sila { get; private set; }
    public int Poziom { get; private set; }

    public Bohater(string imie, int punktyZycia, int sila)
    {
        Imie = imie;
        PunktyZycia = punktyZycia > 0 ? punktyZycia : 1;
        Sila = Math.Clamp(sila, 1, 100);
        Poziom = 1;
    }
}

JEDNA linijka zastępuje 5, 6 linijek kodu! Public string Imie – to nasza właściwość. Get bez ograniczeń – każdy może odczytać. Private set – tylko nasza klasa może zmienić! C# automatycznie tworzy ukryte pole w tle.

Teraz konstruktor – serce naszej klasy! Przyjmuje trzy parametry.

Zauważcie – nie ma parametru poziom. Każdy bohater zaczyna od poziomu 1, to nie jest coś co gracz wybiera!

Używamy operatora warunkowego – tego ze znakiem zapytania! Czyta się:
‘Jeśli punktyZycia większe od zera? Użyj punktyZycia, w przeciwnym razie użyj 1′. To jak skrócony if-else w jednej linii!

Math.Clamp – przyjmuje trzy parametry: wartość, minimum, maksimum. Jeśli wartość jest za mała – zwraca minimum, za duża – zwraca maksimum. W zakresie – zwraca wartość.

🔎 Dlaczego to lepsze?

  • Kod krótszy i bardziej czytelny
  • Walidacja w konstruktorze = bezpieczne dane od samego początku
  • Nadal mamy enkapsulację (private set)
  • Profesjonalne podejście: prostota, czytelność, skuteczność

🏆 Bohaterowie Się Biją – Tworzymy Metodę Atakuj()

Teraz najfajniejsza część – dodajemy możliwość walki! Napiszmy metodę Atakuj.

public void Atakuj(Bohater cel)
{
    if (PunktyZycia <= 0)
    {
        Console.WriteLine($"{Imie} nie może atakować - jest pokonany!");
        return;
    }

    Console.WriteLine($"\n⚔️ {Imie} atakuje {cel.Imie}!");
    Random rand = new Random();
    int obrazenia = (int)(Sila * (0.8 + rand.NextDouble() * 0.4));

    Console.WriteLine($"   Zadaje {obrazenia} obrażeń!");
    cel.OtrzymajObrazenia(obrazenia);
}

🔄 Jak działa?

  • Parametr? To genialnie proste, przekazujemy INNEGO Bohatera jako cel ataku! Obiekt atakuje obiekt!
  • Sprawdza czy atakujący żyje (defensive programming)
  • Oblicza losowe obrażenia (80%-120%)
  • Przekazuje obrażenia do metody OtrzymajObrazenia() na obiekcie cel. To jest sedno OOP – obiekt ‘prosi’ drugi obiekt o wykonanie akcji.


☠️ Obieramy Obrażenia: OtrzymajObrazenia()

Teraz potrzebujemy metody, która obsłuży otrzymywanie obrażeń. Uwaga – będzie prywatna!

private void OtrzymajObrazenia(int obrazenia)
{
    PunktyZycia -= obrazenia;
    if (PunktyZycia < 0) PunktyZycia = 0;

    Console.WriteLine($"   {Imie} ma teraz {PunktyZycia}/100 HP");

    if (PunktyZycia == 0)
        Console.WriteLine($"💀 {Imie} został pokonany!");
}

🔒 Prywatna metoda? Tak!

Nie chcemy, by z zewnątrz ktoś mógł manipulować HP bohatera. Tylko Atakuj() może wywołać OtrzymajObrazenia().


💉 Leczenie w C# RPG

Dobra, nasz bohater umie walczyć i otrzymywać obrażenia, ale sama walka to droga do pewnej śmierci! Dodajmy możliwość leczenia, każdy bohater potrzebuje mikstur!

public void Lecz(int punkty)
{
    if (PunktyZycia == 0)
    {
        Console.WriteLine($"{Imie} nie może się leczyć - jest pokonany!");
        return;
    }

    int stareHP = PunktyZycia;
    PunktyZycia = Math.Min(PunktyZycia + punkty, 100);

    Console.WriteLine($"✨ {Imie} leczy się o {PunktyZycia - stareHP} HP!");
    Console.WriteLine($"   Aktualne HP: {PunktyZycia}/100");
}

Tu dzieje się magia Math.Min! Dodajemy punkty leczenia do aktualnego HP, ALE… Math.Min wybiera MNIEJSZĄ wartość z dwóch.
Jeśli suma wyjdzie 120, Math.Min wybierze 100. Genialnie proste!

📊 Wizualizacja Statusu: Pasek HP z ASCII

Na koniec dodajmy wisienką na torcie – wizualizację! Gracze uwielbiają widzieć status swojej postaci. Zróbmy ładny, czytelny przegląd bohatera z graficznym paskiem HP!

public void PokazStatus()
{
    string pasekHP = new string('█', PunktyZycia / 10) + new string('░', (100 - PunktyZycia) / 10);

    Console.WriteLine($"\n=== {Imie} ===");
    Console.WriteLine($"HP: [{pasekHP}] {PunktyZycia}/100");
    Console.WriteLine($"Siła: {Sila} | Poziom: {Poziom}");
}

Tworzymy wizualny pasek zdrowia używając znaków Unicode. Ten kwadracik ‘█’ to pełny blok. New string z parametrami tworzy string powtarzając znak X razy. Ile razy? PunktyZycia dzielone przez 10! Dlaczego dzielimy przez 10? Bo 100 bloków to za dużo na ekran! 10 bloków idealnie reprezentuje procenty, każdy blok to 10% zdrowia. Proste i czytelne!

Dodajemy PUSTE bloki dla straconego HP! Ten znak ‘░’ to jasny, pusty blok. Jeśli mamy 70 HP, to straciliśmy 30, czyli 3 puste bloki. Razem zawsze mamy 10 bloków!

⏲ Kompletna Pętla Walki w Program.cs

Czas na główne wydarzenie! Usuńmy ten testowy kod z poprzedniego odcinka i stwórzmy prawdziwą arenę gladiatorów!

class Program
{    
    static void Main()
    {
        Console.WriteLine("=== ARENA WALK ===\n");
        
        // Tworzenie bohaterów
        Bohater rycerz = new Bohater("Sir Galahad", 100, 30);
        Bohater czarnoksieznik = new Bohater("Mroczny Mag", 80, 35);
        
        // Pokazujemy status przed walką
        rycerz.PokazStatus();
        czarnoksieznik.PokazStatus();
        
        Console.WriteLine("\n--- WALKA ROZPOCZĘTA! ---");
        
        // Symulacja walki
        while (rycerz.PunktyZycia > 0 && czarnoksieznik.PunktyZycia > 0)
        {
            rycerz.Atakuj(czarnoksieznik);
            
            if (czarnoksieznik.PunktyZycia > 0)
            {
                czarnoksieznik.Atakuj(rycerz);
            }
            
            // Czasem rycerz się leczy
            if (rycerz.PunktyZycia < 30 && rycerz.PunktyZycia > 0)
            {
                Console.WriteLine($"\n{rycerz.Imie} używa mikstury!");
                rycerz.Lecz(20);
            }
            
            Thread.Sleep(1000); // Pauza dla dramatyzmu
        }
        
        // Podsumowanie
        Console.WriteLine("\n=== WALKA ZAKOŃCZONA ===");
        rycerz.PokazStatus();
        czarnoksieznik.PokazStatus();
        
        if (rycerz.PunktyZycia > 0)
            Console.WriteLine($"\n🏆 {rycerz.Imie} WYGRYWA!");
        else
            Console.WriteLine($"\n🏆 {czarnoksieznik.Imie} WYGRYWA!");
    }
}

📊 Diagram: Przebieg walki

  1. Tworzymy bohaterów
  2. Wyświetlamy status
  3. Walka:
    • Atak rycerza
    • Atak maga (jeśli żyje)
    • Leczenie rycerza (jeśli HP < 30)
  4. Aktualizacja statusu
  5. Pętla powtarza się aż do śmierci jednego z bohaterów
  6. Ogłoszenie zwycięzcy

🔌 Edge Case’y i zabezpieczenia:

  • Punkty życia nie mogą być < 0 (walidacja)
  • Siła ograniczona metodą Math.Clamp
  • Leczenie nie działa na martwym bohaterze
  • Pasek HP zawsze pokazuje poprawny zakres

“Daj znać w komentarzu, jeśli tutorial Ci się spodobał!

Chcesz więcej? Dołącz do kursu C# Podstawy Programowania lub C# Programowanie Obiektowe i zacznij tworzyć własne gry z nami krok po kroku!

ZADANIE DLA WAS

Rozbudujcie system o:

  • Klasę `Potwor` z różnymi typami (Ork, Smok, Goblin)
  • System doświadczenia – bohater zdobywa EXP za pokonanie potwora
  • Automatyczny awans na wyższy poziom co 100 EXP
  • Różne typy ataków (normalny, krytyczny, magiczny)

Source code [tutaj]

Dodaj komentarz

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