Często uczniowie pytają:
„Proszę pana, a to nam się przyda na egzamin?”
No to pogadajmy normalnie, bez ściemy.
Ta gra, którą robimy (CyberRunner), to nie jest zabawa dla zabawy.
To jest konkretny zestaw umiejętności, które egzamin INF.04 sprawdza – tylko że ubrane w formę gry, a nie nudnego formularza z trzema TextBoxami.
1. Obsługa klawiatury i zdarzeń
W grze:
- reagujesz na
KeyDowniKeyUp - obsługujesz kilka klawiszy naraz (ruch + skok + strzał)
- sterowanie nie jest „na klik”, tylko ciągłe
Na egzaminie:
- to jest dokładnie obsługa zdarzeń
- INF.04 uwielbia sytuacje typu: po naciśnięciu klawisza wykonaj akcję
Tu nie uczysz się teorii.
Tu robisz to naprawdę.
2. Timer i pętla gry = serce aplikacji
W grze:
DispatcherTimer- pętla, która wykonuje się co ~16 ms
- osobny timer do bonusów (magazynki)
Na egzaminie:
- cykliczne wykonywanie kodu
- aktualizacja stanu aplikacji w czasie
- animacje, liczniki, zegary
To jest 100% INF.04, tylko że w wersji, która ma sens.
3. Kolizje i współrzędne (czyli myślenie logiczne)
W grze:
- sprawdzasz, czy gracz dotknął wroga
- czy pocisk trafił
- czy bonus został zebrany
Na egzaminie:
- instrukcje warunkowe
- praca na danych
- logika „jeśli – to – wtedy”
Egzamin nie pyta: „czy znasz Rect”
Egzamin sprawdza: czy umiesz myśleć warunkami.
4. Stan aplikacji (bardzo ważne!)
W grze masz:
poziompunktyżyciaammonietykalność
To wszystko to stan aplikacji.
Na egzaminie:
- bardzo często jest: zależnie od wartości zmiennej zrób coś
- odblokowywanie funkcji
- ograniczenia
Tutaj to widać czarno na białym.
5. Progresja – coś się dzieje „od któregoś poziomu”
W grze:
- od levelu 3 → nowy wróg
- od levelu 4 → druga linia
- od levelu 8 → UFO
- z każdym levelem gra przyspiesza
Na egzaminie:
- warunki typu
if (poziom >= ...) - zmiana zachowania programu w trakcie działania
To jest dokładnie ten sam mechanizm, tylko w grze jest bardziej oczywisty.
6. Praca na kontrolkach (WPF w praktyce)
W grze:
CanvasRectangleTranslateTransformVisibility
Na egzaminie:
- manipulowanie kontrolkami
- zmiana widoczności
- dynamiczny interfejs
Różnica?
Tutaj widzisz efekt od razu, a nie zgadujesz, czy coś działa.
7. Listy i obiekty (pociski)
W grze:
- lista
List<Pocisk> - własna klasa
- dodawanie i usuwanie elementów
Na egzaminie:
- kolekcje
- obiekty
- operacje na liście
To jest bardzo często punktowane, a uczniowie zwykle się tego boją.
Tutaj – robią to naturalnie.
8. Metody i porządek w kodzie
W grze:
DodajPunkty()Traf()ObsluzPociski()SprawdzKolizje()
Na egzaminie:
- podział programu na logiczne części
- czytelność
- brak „spaghetti code”
Egzaminator naprawdę to docenia.
9. Matematyka, ale taka normalna
W grze:
- grawitacja
- sinus dla UFO
- przyspieszanie gry o 5%
Na egzaminie:
- proste obliczenia
- zastosowanie matematyki w kodzie
Nie wzory z tablicy.
Matematyka, która ma sens.
Podsumowując – na spokojnie
Ta gra:
- nie jest „bajerem”
- nie jest stratą czasu
- nie jest oderwana od egzaminu
To jest:
praktyczny INF.04
logika + UI + zdarzenia
dokładnie to, co egzamin sprawdza
Jak ktoś ogarnia tę grę i rozumie, co tu się dzieje,
to egzamin INF.04 przestaje być straszny.
A to chyba najlepszy cel nauki, nie?
To teraz przejdźmy do kodu. Nie ma tu MVVM, to już dobrze znacie, i to będzie wasze zadanie, by dostosować tę gre do standardu egzaminacyjnego MVVM. Ja wam daję Algorytm (stworzony dla zabicia czasu w pociągu :)), a wasze zadanie to utworzyć z tego profesjonalną apkę.
No to do dzieła.
using System;
using System.Collections.Generic;
using System.Linq; // Potrzebne do obsługi rankingu (metody Append, OrderBy)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace CyberRunner
{
public partial class MainWindow : Window
{
// ==========================================================
// TIMERY - Mechanizm czasu w grze (INF.04 - cykliczność)
// ==========================================================
// DispatcherTimer działa w tym samym wątku co UI, co ułatwia aktualizację grafiki.
DispatcherTimer zegarGry = new DispatcherTimer(); // Główna pętla gry (ruch, grawitacja)
DispatcherTimer zegarMagazynku = new DispatcherTimer(); // Odpowiada za cykliczne pojawianie się amunicji
DispatcherTimer zegarZycia = new DispatcherTimer(); // Odpowiada za cykliczne pojawianie się apteczek
// Statystyki sesji dla podsumowania
DateTime czasRozpoczecia;
int punktyCalkowite = 0; // Suma punktów ze wszystkich poziomów
// ==========================================================
// STAŁE - Wartości, które ułatwiają zarządzanie poziomami
// ==========================================================
const double Y_DOL = 310; // Pozycja gracza na dolnym pasie
const double Y_GORA = 170; // Pozycja pasu górnego (platformy)
const int PUNKTY_NA_POZIOM = 12; // Ile punktów trzeba zdobyć, by wejść na wyższy level
// ==========================================================
// GRACZ - Fizyka i stan
// ==========================================================
double graczX = 150; // Pozycja pozioma
double graczY = Y_DOL; // Pozycja pionowa
double grawitacja = 0; // Siła ciągnąca gracza w dół (lub pęd skoku)
bool ruchLewo, ruchPrawo, zejscieWDol; // Flagi sterowania (czy klawisz jest wciśnięty)
bool skok, podwojnySkok; // Zmienne do obsługi logiki skakania
int kierunek = 1; // 1 = prawo, -1 = lewo (do orientacji pocisków)
DateTime ostatniSkok = DateTime.MinValue; // Potrzebne do wykrycia czasu między kliknięciami (double jump)
// ==========================================================
// WROGOWIE - Statystyki (HP i pozycje)
// ==========================================================
double czerwonyDolX = 900; // Start poza ekranem z prawej
double czerwonyGoraX = 1300;
int hpCzerwonyDol = 4; // 4 hity do zabicia
int hpCzerwonyGora = 4;
double zoltyDolX = -300; // Start poza ekranem z lewej (idą w prawo)
double zoltyGoraX = -700;
int hpZoltyDol = 8; // 8 hitów do zabicia (silniejszy wróg)
int hpZoltyGora = 8;
// ==========================================================
// UFO - Przeciwnik specjalny (Level 8+)
// ==========================================================
double ufoX = 1100;
double ufoFaza = 0; // Zmienna do obliczeń funkcji sinus (ruch falisty)
double ufoAmplituda = 60; // Jak wysoko/nisko lata UFO
// ==========================================================
// BONUSY
// ==========================================================
double ammoX = 900;
bool ammoAktywne = false;
double zycieX = 1200;
bool zycieAktywne = false;
// ==========================================================
// STAN GRY - Zmienne HUD
// ==========================================================
int poziom = 1;
int punkty = 0;
int zycia = 30;
int ammo = 100;
double predkosc = 6; // Globalna prędkość przesuwu obiektów
int nietykalnosc = 0; // Licznik klatek po otrzymaniu obrażeń
// ==========================================================
// POCISKI - Zarządzanie obiektami dynamicznymi
// ==========================================================
class Pocisk
{
public Rectangle Ksztalt; // Wygląd pocisku na ekranie
public int Kierunek; // W którą stronę leci
}
List<Pocisk> pociski = new(); // Kolekcja wszystkich pocisków w locie
public MainWindow()
{
InitializeComponent();
Plansza.Focus(); // Ustawienie fokusu, aby okno przechwytywało klawisze od razu
// Konfiguracja głównego zegara (ok. 60 klatek na sekundę)
zegarGry.Interval = TimeSpan.FromMilliseconds(16);
zegarGry.Tick += PetlaGry;
// Konfiguracja timerów dla bonusów
zegarMagazynku.Interval = TimeSpan.FromSeconds(30);
zegarMagazynku.Tick += (s, e) => AktywujAmmo();
zegarZycia.Interval = TimeSpan.FromSeconds(45);
zegarZycia.Tick += (s, e) => AktywujZycie();
// Pierwsze uruchomienie gry
RestartGry();
}
// Metoda inicjalizująca stan początkowy (idealna do nauki resetowania obiektów)
void RestartGry()
{
// Reset parametrów gracza i punktacji
poziom = 1; punkty = 0; punktyCalkowite = 0; zycia = 30; ammo = 100; predkosc = 6;
graczX = 150; graczY = Y_DOL; grawitacja = 0;
czasRozpoczecia = DateTime.Now;
// Czyszczenie pocisków z ekranu (XAML) i z pamięci (Lista)
foreach (var p in pociski) Plansza.Children.Remove(p.Ksztalt);
pociski.Clear();
// Przywrócenie wrogów i bonusów do stanu początkowego
czerwonyDolX = 900; zoltyDolX = -300; ufoX = 1100;
hpCzerwonyDol = 4; hpZoltyDol = 8;
ammoAktywne = false; zycieAktywne = false;
BonusAmmo.Visibility = Visibility.Collapsed;
BonusZycie.Visibility = Visibility.Collapsed;
zegarGry.Start();
zegarMagazynku.Start();
zegarZycia.Start();
AktualizujHUD();
}
// ==========================================================
// GŁÓWNA PĘTLA GRY - Wykonywana co 16ms
// ==========================================================
void PetlaGry(object sender, EventArgs e)
{
// ---- RUCH GRACZA ----
if (ruchLewo) { graczX -= 8; kierunek = -1; }
if (ruchPrawo) { graczX += 8; kierunek = 1; }
// ---- GRAWITACJA I SKOK ----
// Gracz zawsze "spada", chyba że grawitacja jest ujemna (wtedy leci w górę - skok)
graczY += grawitacja;
grawitacja += 1.6; // Przyciąganie ziemskie (zwiększa prędkość spadania)
// Kolizja z dolną podłogą
if (graczY >= Y_DOL)
{
graczY = Y_DOL;
grawitacja = 0;
skok = false;
podwojnySkok = false;
}
// Obsługa górnej platformy (od poziomu 4)
if (poziom >= 4)
{
PodlogaGora.Visibility = Visibility.Visible;
// Warunek lądowania na górnej linii:
// Gracz musi spadać (grawitacja > 0) i być blisko wysokości platformy
if (!zejscieWDol && graczY >= Y_GORA - 60 && graczY < Y_GORA && grawitacja > 0)
{
graczY = Y_GORA - 60;
grawitacja = 0;
skok = false;
podwojnySkok = false;
}
}
// Szybkie schodzenie w dół (klawisz DOWN)
if (zejscieWDol && graczY < Y_DOL)
grawitacja = 6;
// Aktualizacja pozycji grafiki gracza na ekranie
TransformGracz.X = graczX;
TransformGracz.Y = graczY;
// ---- LOGIKA PRZECIWNIKÓW (Przesuwanie i resetowanie pozycji) ----
czerwonyDolX -= predkosc;
if (czerwonyDolX < -60) { czerwonyDolX = 900; hpCzerwonyDol = 4; }
TransformCzerwonyDol.X = czerwonyDolX;
TransformCzerwonyDol.Y = Y_DOL;
if (poziom >= 4)
{
czerwonyGoraX -= predkosc;
if (czerwonyGoraX < -60) { czerwonyGoraX = 1100; hpCzerwonyGora = 4; }
TransformCzerwonyGora.X = czerwonyGoraX;
TransformCzerwonyGora.Y = Y_GORA - 60;
WrogCzerwonyGora.Visibility = Visibility.Visible;
}
if (poziom >= 2)
{
zoltyDolX += predkosc * 0.8;
if (zoltyDolX > 900) { zoltyDolX = -300; hpZoltyDol = 8; }
TransformZoltyDol.X = zoltyDolX;
TransformZoltyDol.Y = Y_DOL - 10;
WrogZoltyDol.Visibility = Visibility.Visible;
}
if (poziom >= 4)
{
zoltyGoraX += predkosc * 0.8;
if (zoltyGoraX > 1100) { zoltyGoraX = -600; hpZoltyGora = 8; }
TransformZoltyGora.X = zoltyGoraX;
TransformZoltyGora.Y = Y_GORA - 70;
WrogZoltyGora.Visibility = Visibility.Visible;
}
// ---- LOGIKA BONUSÓW ----
if (ammoAktywne)
{
ammoX -= predkosc;
TransformBonusAmmo.X = ammoX;
TransformBonusAmmo.Y = Y_DOL - 40;
if (ammoX < -60) { ammoAktywne = false; BonusAmmo.Visibility = Visibility.Collapsed; }
}
if (zycieAktywne)
{
zycieX -= predkosc;
TransformBonusZycie.X = zycieX;
TransformBonusZycie.Y = Y_DOL - 40;
if (zycieX < -60) { zycieAktywne = false; BonusZycie.Visibility = Visibility.Collapsed; }
}
// ---- LOGIKA UFO (Level 8+) ----
if (poziom >= 8)
{
UFO.Visibility = Visibility.Visible;
ufoX -= predkosc;
if (ufoX < -100) ufoX = 1100;
ufoFaza += 0.05; // Zmiana kąta dla sinusa
TransformUFO.X = ufoX;
// Math.Sin tworzy płynny ruch góra-dół (wartości od -1 do 1)
TransformUFO.Y = 220 + Math.Sin(ufoFaza) * ufoAmplituda;
}
// Wywołanie metod pomocniczych
ObsluzPociski();
SprawdzKolizje();
AktualizujNietykalnosc();
}
// ==========================================================
// POCISKI - Naprawiona punktacja i HP
// ==========================================================
void ObsluzPociski()
{
// Iterujemy od końca listy, bo będziemy usuwać elementy w trakcie pętli (INF.04 - kolekcje)
for (int i = pociski.Count - 1; i >= 0; i--)
{
var p = pociski[i];
double x = Canvas.GetLeft(p.Ksztalt) + 22 * p.Kierunek;
Canvas.SetLeft(p.Ksztalt, x);
// Tworzymy prostokąt kolizji dla pocisku
Rect r = new Rect(x, Canvas.GetTop(p.Ksztalt), 20, 6);
// Trafienie Czerwonego (Dół) - 4 HP, 1 Punkt
if (r.IntersectsWith(new Rect(czerwonyDolX, Y_DOL, 40, 60)))
{
hpCzerwonyDol--;
UsunPocisk(i);
if (hpCzerwonyDol <= 0) { DodajPunkty(1); czerwonyDolX = 900; hpCzerwonyDol = 4; }
continue; // Pocisk zniknął, sprawdzamy następny
}
// Trafienie Czerwonego (Góra) - 4 HP, 1 Punkt
if (poziom >= 4 && r.IntersectsWith(new Rect(czerwonyGoraX, Y_GORA - 60, 40, 60)))
{
hpCzerwonyGora--;
UsunPocisk(i);
if (hpCzerwonyGora <= 0) { DodajPunkty(1); czerwonyGoraX = 1100; hpCzerwonyGora = 4; }
continue;
}
// Trafienie Żółtego (Dół) - 8 HP, 2 Punkty
if (poziom >= 2 && r.IntersectsWith(new Rect(zoltyDolX, Y_DOL - 10, 40, 70)))
{
hpZoltyDol--;
UsunPocisk(i);
if (hpZoltyDol <= 0) { DodajPunkty(2); zoltyDolX = -300; hpZoltyDol = 8; }
continue;
}
// Trafienie Żółtego (Góra) - 8 HP, 2 Punkty
if (poziom >= 4 && r.IntersectsWith(new Rect(zoltyGoraX, Y_GORA - 70, 40, 70)))
{
hpZoltyGora--;
UsunPocisk(i);
if (hpZoltyGora <= 0) { DodajPunkty(2); zoltyGoraX = -600; hpZoltyGora = 8; }
continue;
}
// Usuwanie pocisków poza ekranem (optymalizacja)
if (x < -40 || x > 900) UsunPocisk(i);
}
}
// Zarządzanie punktami i wywołanie sprawdzenia poziomu
void DodajPunkty(int ile)
{
punkty += ile;
punktyCalkowite += ile;
SprawdzLevel();
AktualizujHUD();
}
void UsunPocisk(int i)
{
Plansza.Children.Remove(pociski[i].Ksztalt); // Usuń z ekranu (XAML)
pociski.RemoveAt(i); // Usuń z pamięci (Lista)
}
// ==========================================================
// KOLIZJE GRACZA - Czy gracz dotknął wroga lub bonusu?
// ==========================================================
void SprawdzKolizje()
{
Rect g = new Rect(graczX, graczY, 40, 60);
// Kolizje z przeciwnikami (zróżnicowane DMG)
if (g.IntersectsWith(new Rect(czerwonyDolX, Y_DOL, 40, 60))) Traf(1);
if (poziom >= 4 && g.IntersectsWith(new Rect(czerwonyGoraX, Y_GORA - 60, 40, 60))) Traf(1);
if (poziom >= 2 && g.IntersectsWith(new Rect(zoltyDolX, Y_DOL - 10, 40, 70))) Traf(2);
if (poziom >= 4 && g.IntersectsWith(new Rect(zoltyGoraX, Y_GORA - 70, 40, 70))) Traf(2);
// Kolizja z UFO - pobieramy pozycję bezpośrednio z Transformacji
if (poziom >= 8)
{
if (g.IntersectsWith(new Rect(TransformUFO.X, TransformUFO.Y, 50, 30))) Traf(4);
}
// Zbieranie amunicji
if (ammoAktywne && g.IntersectsWith(new Rect(ammoX, Y_DOL - 40, 40, 40)))
{
ammo += 50;
ammoAktywne = false;
BonusAmmo.Visibility = Visibility.Collapsed;
AktualizujHUD();
}
// Zbieranie życia (teraz dodaje 5 HP, by uczeń poczuł różnicę)
if (zycieAktywne && g.IntersectsWith(new Rect(zycieX, Y_DOL - 40, 40, 40)))
{
zycia += 5;
zycieAktywne = false;
BonusZycie.Visibility = Visibility.Collapsed;
AktualizujHUD();
}
}
// Reakcja na otrzymanie obrażeń
void Traf(int dmg)
{
if (nietykalnosc > 0) return; // Jeśli gracz miga, nie traci HP
zycia -= dmg;
nietykalnosc = 60; // Okres ochronny (ok. 1 sekunda przy 60 FPS)
AktualizujHUD();
if (zycia <= 0) WyswietlKoniecGry();
}
// Ekran Końcowy (INF.04 - Podsumowanie pracy programu, ranking i czas)
void WyswietlKoniecGry()
{
zegarGry.Stop(); zegarMagazynku.Stop(); zegarZycia.Stop();
TimeSpan czasGry = DateTime.Now - czasRozpoczecia;
// Generowanie statycznego rankingu z udziałem gracza
string[] nicki = { "CyberMistrz", "BitBuster", "KernelPan", "WPF_Pro", "NullPtr", "INF_04_King", "Admin", "User1", "PlayerZero", "Anon" };
Random rnd = new Random();
var ranking = nicki.Select(imie => new { Imie = imie, Wynik = rnd.Next(10, 500) })
.Append(new { Imie = "TY", Wynik = punktyCalkowite })
.OrderByDescending(x => x.Wynik)
.Take(10)
.ToList();
string rankingTekst = "TOP 10 GRACZY:\n";
for (int i = 0; i < ranking.Count; i++)
rankingTekst += $"{i + 1}. {ranking[i].Imie} - {ranking[i].Wynik} pkt\n";
string podsumowanie = $"KONIEC GRY!\n\n" +
$"Czas przetrwania: {czasGry.Minutes}m {czasGry.Seconds}s\n" +
$"Zdobyte punkty łącznie: {punktyCalkowite}\n\n" +
rankingTekst + "\n" +
"Czy chcesz zagrać ponownie?";
var decyzja = MessageBox.Show(podsumowanie, "CyberRunner - Wynik", MessageBoxButton.YesNo, MessageBoxImage.Information);
if (decyzja == MessageBoxResult.Yes) RestartGry();
else Application.Current.Shutdown();
}
// Wizualny efekt nietykalności (miganie Opacity)
void AktualizujNietykalnosc()
{
if (nietykalnosc > 0)
{
nietykalnosc--;
Gracz.Opacity = nietykalnosc % 10 < 5 ? 0.3 : 1; // Naprzemienna zmiana przezroczystości
}
else Gracz.Opacity = 1;
}
// ==========================================================
// PROGRESJA - Zmiana poziomu trudności
// ==========================================================
void SprawdzLevel()
{
if (punkty >= PUNKTY_NA_POZIOM)
{
punkty = 0;
poziom++;
predkosc *= 1.05; // Gra przyspiesza o 5% z każdym poziomem
}
AktualizujHUD();
}
// Aktywacja bonusów przez Timery
void AktywujAmmo() { ammoAktywne = true; ammoX = 900; BonusAmmo.Visibility = Visibility.Visible; }
void AktywujZycie() { zycieAktywne = true; zycieX = 1200; BonusZycie.Visibility = Visibility.Visible; }
// ==========================================================
// STEROWANIE - Obsługa klawiatury
// ==========================================================
void Plansza_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Left) ruchLewo = true;
if (e.Key == Key.Right) ruchPrawo = true;
if (e.Key == Key.Down) zejscieWDol = true;
if (e.Key == Key.Up)
{
DateTime teraz = DateTime.Now;
// Pierwszy skok (możliwy tylko gdy gracz stoi na ziemi/platformie)
if (!skok)
{
grawitacja = -22; // Nadajemy pęd w górę
skok = true;
podwojnySkok = true;
}
// Drugi skok (double jump) - jeśli kliknięto szybko drugi raz
else if ((teraz - ostatniSkok).TotalMilliseconds < 250 && podwojnySkok)
{
grawitacja = -32; // Silniejszy wybicie
podwojnySkok = false;
}
ostatniSkok = teraz;
}
if (e.Key == Key.Space) Strzel();
}
void Plansza_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Left) ruchLewo = false;
if (e.Key == Key.Right) ruchPrawo = false;
if (e.Key == Key.Down) zejscieWDol = false;
}
// ==========================================================
// MECHANIKA STRZAŁU
// ==========================================================
void Strzel()
{
if (ammo <= 0) return;
ammo--;
// Tworzenie dynamicznie kontrolki pocisku
Rectangle r = new Rectangle { Width = 20, Height = 6, Fill = Brushes.Cyan };
// Ustawienie pozycji startowej pocisku względem gracza
Canvas.SetLeft(r, graczX + (kierunek == 1 ? 40 : -20));
Canvas.SetTop(r, graczY + 25);
Plansza.Children.Add(r); // Dodanie do Canvasa
pociski.Add(new Pocisk { Ksztalt = r, Kierunek = kierunek }); // Dodanie do listy logiki
AktualizujHUD();
}
// Odświeżanie napisów na ekranie (HUD)
void AktualizujHUD()
{
TekstPoziom.Text = $"POZIOM: {poziom}";
TekstPunkty.Text = $"PUNKTY: {punkty}/{PUNKTY_NA_POZIOM}";
TekstZycia.Text = $"ŻYCIA: {zycia}";
TekstAmmo.Text = $"AMMO: {ammo}";
}
}
}
<Window x:Class="CyberRunner.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CyberRunner"
Width="800" Height="450"
Background="#050505"
WindowStartupLocation="CenterScreen">
<Canvas Name="Plansza"
Focusable="True"
KeyDown="Plansza_KeyDown"
KeyUp="Plansza_KeyUp">
<!-- ================= PODŁOGI ================= -->
<!-- Dolna linia – od początku gry -->
<Rectangle Width="800" Height="5"
Fill="#00f2ff"
Canvas.Top="370"/>
<!-- Górna linia – aktywna od levelu 4 -->
<Rectangle Name="PodlogaGora"
Width="800" Height="5"
Fill="#7CFF00"
Canvas.Top="170"
Visibility="Collapsed"/>
<!-- ================= GRACZ ================= -->
<Rectangle Name="Gracz"
Width="40" Height="60"
Fill="#00d2ff">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformGracz"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- ================= WROGOWIE – DÓŁ ================= -->
<!-- Czerwony wróg – dół -->
<Rectangle Name="WrogCzerwonyDol"
Width="40" Height="60"
Fill="#ff0058">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformCzerwonyDol"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- Żółty wróg – dół -->
<Rectangle Name="WrogZoltyDol"
Width="40" Height="70"
Fill="Gold"
Visibility="Collapsed">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformZoltyDol"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- ================= WROGOWIE – GÓRA ================= -->
<!-- Czerwony wróg – góra -->
<Rectangle Name="WrogCzerwonyGora"
Width="40" Height="60"
Fill="#ff0058"
Visibility="Collapsed">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformCzerwonyGora"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- Żółty wróg – góra -->
<Rectangle Name="WrogZoltyGora"
Width="40" Height="70"
Fill="Gold"
Visibility="Collapsed">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformZoltyGora"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- ================= KOLCE / SOPL E ================= -->
<Rectangle Name="Kolce"
Width="60" Height="30"
Fill="#ff3333"
Visibility="Collapsed">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformKolce"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- ================= UFO ================= -->
<Rectangle Name="UFO"
Width="50" Height="30"
Fill="#baff00"
Visibility="Collapsed">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformUFO"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- ================= BONUSY ================= -->
<!-- Biały magazynek – +50 ammo -->
<Rectangle Name="BonusAmmo"
Width="40" Height="40"
Fill="White"
Visibility="Collapsed">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformBonusAmmo"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- Fioletowe życie – +1 HP -->
<Rectangle Name="BonusZycie"
Width="40" Height="40"
Fill="Purple"
Visibility="Collapsed">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TransformBonusZycie"/>
</Rectangle.RenderTransform>
</Rectangle>
<!-- ================= HUD ================= -->
<StackPanel Canvas.Left="20" Canvas.Top="20">
<TextBlock Name="TekstPoziom"
Foreground="White"
FontSize="22"/>
<TextBlock Name="TekstPunkty"
Foreground="#00d2ff"
FontSize="18"/>
<TextBlock Name="TekstZycia"
Foreground="#ff4d4d"
FontSize="18"/>
<TextBlock Name="TekstAmmo"
Foreground="White"
FontSize="18"/>
</StackPanel>
</Canvas>
</Window>

