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
zprivate set
- Walidacja w konstruktorze
- Metody interakcji między obiektami
- Prywatna metoda
OtrzymajObrazenia()
i publicznaAtakuj()
- 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
- Tworzymy bohaterów
- Wyświetlamy status
- Walka:
- Atak rycerza
- Atak maga (jeśli żyje)
- Leczenie rycerza (jeśli HP < 30)
- Aktualizacja statusu
- Pętla powtarza się aż do śmierci jednego z bohaterów
- 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)