Klasy i Obiekty w C# – Dziedziczenie i Polimorfizm
🧱 1. Problem z kopiowaniem kodu – czyli jak NIE programować
Kiedy próbujemy dodać nową klasę Potwór
identyczną z Bohater
, szybko zaczynamy… kopiować.
Ten sam konstruktor, te same metody Atakuj()
, OtrzymajObrazenia()
i PokazStatus()
.
To droga donikąd. A co jeśli masz 10 typów postaci?
Utrzymanie tego kodu będzie piekłem. Pora użyć dziedziczenia!
🌳 2. Klasa bazowa Postac
– fundament każdej istoty
namespace DevHobby.Code.RPG;
public abstract class Postac
{
// Właściwości auto-implemented (skrócona składnia)
public string Imie { get; protected set; }
public int PunktyZycia { get; protected set; }
public int MaxPunktyZycia { get; protected set; } // Maksymalne HP dla każdej postaci
public int Sila { get; protected set; }
// Konstruktor
public Postac(string imie, int punktyZycia, int sila)
{
Imie = imie;
MaxPunktyZycia = punktyZycia > 0 ? punktyZycia : 1;
PunktyZycia = MaxPunktyZycia;
Sila = Math.Clamp(sila, 1, 100); // Ogranicza wartość do zakresu
}
// Metoda ataku
public virtual void Atakuj(Postac cel)
{
if (PunktyZycia <= 0)
{
Console.WriteLine($"{Imie} nie może atakować - jest pokonany!");
return;
}
if (cel.PunktyZycia <= 0)
{
Console.WriteLine($"{cel.Imie} jest już pokonany!");
return;
}
}
// Metoda otrzymywania obrażeń
protected internal void OtrzymajObrazenia(int obrazenia)
{
PunktyZycia -= obrazenia;
if (PunktyZycia < 0) PunktyZycia = 0;
Console.WriteLine($" {Imie} ma teraz {PunktyZycia}/{MaxPunktyZycia} HP");
if (PunktyZycia == 0)
Console.WriteLine($"💀 {Imie} został pokonany!");
}
// Metoda wyświetlania statusu
public abstract void PokazStatus();
}
📌 Co warto zapamiętać:
abstract
= nie można tworzyć instancji klasyPostac
virtual
= metoda może zostać nadpisanaprotected
= dostęp tylko dla klas dziedziczącychinternal
= dostęp w ramach projektu
🦸♂️ 3. Klasa Bohater
i jej dzieci – Wojownik, Łucznik, Mag
Każdy bohater dziedziczy z Postac
, ale ma swoje supermoce.
namespace DevHobby.Code.RPG;
public abstract class Bohater : Postac
{
// Właściwości auto-implemented
public int Poziom { get; protected set; } = 1;
// Konstruktor
public Bohater(string imie, int punktyZycia, int sila) : base(imie, punktyZycia, sila) {}
// Metoda leczenia
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, MaxPunktyZycia);
Console.WriteLine($"✨ {Imie} leczy się o {PunktyZycia - stareHP} HP!);
Console.WriteLine($" Aktualne HP: {PunktyZycia})");
}
// Metoda wyświetlania statusu
public override void PokazStatus()
{
string pasekHP = new string('█', PunktyZycia / 10) +
new string('░', (100 - PunktyZycia) / 10);
Console.WriteLine($"\n=== {Imie} - nasz Bohater ===");
Console.WriteLine($"HP: [{pasekHP}] {PunktyZycia}/{MaxPunktyZycia}");
Console.WriteLine($"Siła: {Sila} | Poziom: {Poziom}");
}
}
💥 Wojownik.cs
namespace DevHobby.Code.RPG;
public class Wojownik : Bohater
{
public Wojownik(string imie) : base(imie, 100, 30) {}
public override void Atakuj(Postac cel)
{
base.Atakuj(cel);
Console.WriteLine($"\n ⚔️ Wojownik {Imie} uderza z furią {cel.Imie}!");
int obrazenia = (int)(Sila * (0.8 + new Random().NextDouble() * 0.4));
Console.WriteLine($" Zadaje {obrazenia} obrażeń!");
cel.OtrzymajObrazenia(obrazenia);
}
}
🏹 Lucznik.cs
public class Lucznik : Bohater
{
public Lucznik(string imie) : base(imie, 90, 20) {}
public override void Atakuj(Postac cel)
{
base.Atakuj(cel);
Console.WriteLine($"\n 🏹 Lucznik {Imie} strzela celnie w {cel.Imie}!");
int obrazenia = (int)(Sila * 1.2) + new Random().Next(1, 5); // Łucznik ma bonus do siły, ale mniejszy losowy modyfikator
Console.WriteLine($" Zadaje {obrazenia} obrażeń!");
cel.OtrzymajObrazenia(obrazenia);
}
}
✨ Mag.cs
public class Mag : Bohater
{
public Mag(string imie) : base(imie, 80, 40) {}
public override void Atakuj(Postac cel)
{
base.Atakuj(cel);
Console.WriteLine($"\n ✨ Mag {Imie} rzuca potężne zaklęcie na {cel.Imie}!");
int obrazenia = (int)(Sila * 1.5); // Mag zadaje stałe, duże obrażenia
Console.WriteLine($" Zadaje {obrazenia} obrażeń!");
cel.OtrzymajObrazenia(obrazenia);
}
}
Zobacz też: Tworzenie klas w C# – część 1
👹 4. Klasa Potwor
i bestie z piekła rodem
Potwory też dziedziczą z Postac
, ale nie mają funkcji leczenia – bo kto widział Orka z apteczką?
namespace DevHobby.Code.RPG;
public abstract class Potwor : Postac
{
public Potwor(string imie, int punktyZycia, int sila) : base(imie, punktyZycia, sila) {}
public override void PokazStatus()
{
string pasekHP = new string('█', PunktyZycia / 10) +
new string('░', (100 - PunktyZycia) / 10);
Console.WriteLine($"\n=== {Imie} - Potwor ===");
Console.WriteLine($"HP: [{pasekHP}] {PunktyZycia}/100");
Console.WriteLine($"Siła: {Sila}");
}
}
🧟 Ork.cs
, Goblin.cs
, Smok.cs
– unikalne ataki
Każdy potwór nadpisuje Atakuj()
i prezentuje inny styl:
- Ork – brutalny cios
- Goblin – atak trucizną
- Smok – zieje ogniem i niszczy wszystko na drodze
🧟 Ork.cs
namespace DevHobby.Code.RPG;
public class Ork : Potwor
{
public Ork() : base("Ork", 80, 20) {}
public override void Atakuj(Postac cel)
{
base.Atakuj(cel);
Console.WriteLine($"\n 👺 {Imie} uderza maczugą w {cel.Imie}!");
int obrazenia = Sila + 3; // Ork zadaje obrażenia większe o stałą wartość
Console.WriteLine($" Zadaje {obrazenia} obrażeń!");
cel.OtrzymajObrazenia(obrazenia);
}
}
🧟 Goblin.cs
namespace DevHobby.Code.RPG;
public class Goblin : Potwor
{
public Goblin() : base("Goblin", 60, 15) {}
public override void Atakuj(Postac cel)
{
base.Atakuj(cel);
Console.WriteLine($"\n 🐍 {Imie} rzuca zatruty sztylet w {cel.Imie}!");
int obrazenia = (int)(Sila * 0.9); // Goblin zadaje mniej obrażeń
Console.WriteLine($" Zadaje {obrazenia} obrażeń!");
cel.OtrzymajObrazenia(obrazenia);
}
}
🧟 Smok.cs
namespace DevHobby.Code.RPG;
public class Smok : Potwor
{
public Smok() : base("Smok", 100, 30) {}
public override void Atakuj(Postac cel)
{
base.Atakuj(cel);
Console.WriteLine($"\n 🔥 {Imie} zieje ogniem na {cel.Imie}!");
int obrazenia = Sila * 2; // Smok jest bardzo potężny i zadaje podwójne obrażenia
Console.WriteLine($" Zadaje {obrazenia} obrażeń!");
cel.OtrzymajObrazenia(obrazenia);
}
}
🧠 5. Polimorfizm – jedna metoda, wiele zachowań
Dzięki polimorfizmowi możemy stworzyć listę:
List<Postac> postacie = new() { nowyWojownik, goblin, mag, smok };
I wywołać:
foreach(var p in postacie) p.Atakuj(inny);
Każdy obiekt zachowa się inaczej, mimo że wywołujemy tę samą metodę Atakuj()
!
To magia programowania obiektowego.
⚔️ 6. Arena Walk – przykład w Program.cs
Zobacz fragment prawdziwej walki:
using DevHobby.Code.RPG;
public class Program
{
static void Main()
{
Console.WriteLine("Nacisnij Enter aby zobaczyć Bohaterów Gry");
Console.ReadLine();
Console.WriteLine("=== ARENA WALK ===\n");
// Tworzenie bohaterów
var rycerz = new Wojownik("Sir Galahad");
var czarnoksieznik = new Mag("Mroczny Mag");
var lucznik = new Lucznik("Legolas");
var ork = new Ork();
var smok = new Smok();
var goblin = new Goblin();
// Lista wszystkich postaci biorących udział w walce
var wszystkiePostacie = new List<Postac> { rycerz, czarnoksieznik, lucznik, ork, goblin, smok };
// Pokazujemy status przed walką
wszystkiePostacie.ForEach(p => p.PokazStatus());
Console.WriteLine("Nacisnij Enter aby rozpocząć Grę");
Console.ReadLine();
Console.WriteLine("\n--- WALKA ROZPOCZĘTA! ---");
var random = new Random();
// Symulacja walki
while (wszystkiePostacie.Count(p => p.PunktyZycia > 0) > 1)
{
// Losowanie atakującego spośród żywych postaci
var zywePostacie = wszystkiePostacie.Where(p => p.PunktyZycia > 0).ToList();
if (zywePostacie.Count <= 1) break;
var atakujacy = zywePostacie[random.Next(zywePostacie.Count)];
// Losowanie celu (różnego od atakującego)
Postac cel;
do
{
cel = zywePostacie[random.Next(zywePostacie.Count)];
} while (cel == atakujacy);
// Wykonanie ataku
atakujacy.Atakuj(cel);
// Krótka pauza, aby śledzić przebieg walki
Thread.Sleep(1000);
if (cel.PunktyZycia > 0)
{
cel.Atakuj(atakujacy);
}
// Czasem boharter się leczy
if (atakujacy is Bohater bohater && atakujacy.PunktyZycia < 30 && atakujacy.PunktyZycia > 0)
{
Console.WriteLine($"\n{atakujacy.Imie} używa mikstury!");
bohater.Lecz(30);
}
Thread.Sleep(2000); // Pauza dla dramatyzmu
}
// Podsumowanie
Console.WriteLine("\n=== WALKA ZAKOŃCZONA ===");
wszystkiePostacie.ForEach(p => p.PokazStatus());
// Zakończenie gry i ogłoszenie zwycięzcy
var zwyciezca = wszystkiePostacie.FirstOrDefault(p => p. PunktyZycia > 0);
if (zwyciezca != null)
Console.WriteLine($"\n🏆 {zwyciezca.Imie} WYGRYWA!");
else
Console.WriteLine("\n 💥 Wszyscy polegli w walce!");
}
}
Cały system działa jak orkiestra – każdy gra inaczej, ale według tej samej partytury Postac
.
✅ 7. Podsumowanie: Co zyskujesz dzięki dziedziczeniu i polimorfizmowi?
- ✅ Mniej duplikacji – wspólny kod w klasie bazowej
- ✅ Większa elastyczność – łatwo dodawać nowe postacie
- ✅ Czysty kod – zasady SOLID na pokładzie
- ✅ Łatwe testowanie i utrzymanie
- ✅ Wzorzec projektowy jak z Unity lub Unreal Engine
🧪 Zadanie dla Ciebie!
Stwórz własne klasy:
- 🛡️
Paladyn
z umiejętnością leczenia innych - 🧌
Troll
z regeneracją HP - 🎯 System poziomowania postaci!
Podziel się efektami w komentarzu. Chcę zobaczyć Wasze potwory i epickie starcia!
👉 Jeśli podobał Ci się ten artykuł:
- Zostaw komentarz!
- Subskrybuj kanał YT