Compare commits
3 commits
0acb692a18
...
b76ef75556
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b76ef75556 | ||
|
|
2098f77a23 | ||
|
|
7c33b6bb63 |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
87
README.md
87
README.md
|
|
@ -0,0 +1,87 @@
|
||||||
|
# OOP w Pythonie przez bibliotekę arcade — kurs 5-dniowy
|
||||||
|
|
||||||
|
Kurs dla osoby, która dobrze zna proceduralnego Pythona (pętle, funkcje, listy,
|
||||||
|
słowniki, algorytmy), ale nie pisała jeszcze klas. Przez pięć dni budujemy gry
|
||||||
|
w bibliotece [arcade](https://api.arcade.academy/) i przy okazji poznajemy
|
||||||
|
obiektowość: klasy, konstruktory, dziedziczenie, nadpisywanie metod, `property`.
|
||||||
|
|
||||||
|
Uwaga: kurs korzysta z **arcade 3.x** (zweryfikowano na 3.3.3). API 3.x różni się
|
||||||
|
od starych tutoriali z wersji 2.x — patrz sekcja „Pułapki API” na dole.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Wirtualne środowisko (w katalogu projektu)
|
||||||
|
python3 -m venv .venv
|
||||||
|
|
||||||
|
# 2. Aktywacja
|
||||||
|
source .venv/bin/activate # Linux / macOS
|
||||||
|
# .venv\Scripts\activate # Windows (PowerShell / cmd)
|
||||||
|
|
||||||
|
# 3. Instalacja zależności
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 4. Sprawdzenie, czy działa
|
||||||
|
python -c "import arcade; print(arcade.VERSION)"
|
||||||
|
python dzien1/01_okno.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Jeśli po `python dzien1/01_okno.py` otworzy się okno z napisem — środowisko jest
|
||||||
|
gotowe. Zamykamy okno krzyżykiem albo `Esc` (tam, gdzie jest obsłużony).
|
||||||
|
|
||||||
|
## Plan dni
|
||||||
|
|
||||||
|
| Katalog | Dzień | Temat | Co ćwiczymy z OOP |
|
||||||
|
| ------------------------ | ----- | ------------------------------------------------- | ------------------------------------------------------------ |
|
||||||
|
| `dzien1/` | 1 | Pierwsze okno, rysowanie, ruch sprite'a | klasa, `__init__`, `super()`, dziedziczenie po `arcade.Window`, nadpisywanie metod |
|
||||||
|
| `dzien2/` | 2 | Zbieranie monet + własna hierarchia wrogów | klasa bazowa i klasy pochodne, nadpisywanie `update()`, polimorfizm |
|
||||||
|
| `dzien3_4_platformowka/` | 3–4 | Platformówka wg oficjalnego tutoriala | kompozycja obiektów (silnik fizyki, kamera, sceny), `property` z walidacją |
|
||||||
|
| `dzien5_projekt/` | 5 | Własna gra od zera | projektowanie klas, podział na pliki, `arcade.View` jako stany gry |
|
||||||
|
| `assets/` | — | Grafika i dźwięki (do pobrania samodzielnie) | — |
|
||||||
|
|
||||||
|
## Zasady pracy
|
||||||
|
|
||||||
|
1. **Uzupełniaj `TODO`.** Każdy plik startowy *działa* od razu — uruchom go
|
||||||
|
najpierw bez zmian, zobacz co robi, dopiero potem szukaj `TODO:` i dopisuj kod.
|
||||||
|
2. **Uruchamiaj często.** Po każdej małej zmianie (5–15 linii) odpal program.
|
||||||
|
Łatwiej znaleźć błąd w pięciu nowych liniach niż w stu.
|
||||||
|
3. **Commit po każdym działającym kroku.** Nie po każdym dniu — po każdym
|
||||||
|
momencie, w którym gra znowu się uruchamia:
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "dzien2: klasa Enemy odbija się od krawędzi"
|
||||||
|
```
|
||||||
|
Dzięki temu zawsze można wrócić do ostatniej wersji, która działała.
|
||||||
|
4. **Czytaj komunikaty błędów od dołu.** Ostatnia linia traceback mówi *co* się
|
||||||
|
stało, linie wyżej mówią *gdzie*.
|
||||||
|
5. **Nie kopiuj kodu, którego nie rozumiesz.** Jeśli coś w przykładzie jest
|
||||||
|
niejasne — zmień to i zobacz, co się zepsuje. To najszybszy sposób nauki.
|
||||||
|
|
||||||
|
## Materiały
|
||||||
|
|
||||||
|
- Primer (szybkie wprowadzenie do arcade): <https://realpython.com/arcade-python-game-framework/>
|
||||||
|
- Kurs „Learn Arcade” (długi, po angielsku, z zadaniami): <https://learn.arcade.academy/>
|
||||||
|
- Tutorial platformówki (podstawa dni 3–4): <https://api.arcade.academy/en/latest/tutorials/platform_tutorial/index.html>
|
||||||
|
- Przykłady kodu (kopalnia gotowych rozwiązań): <https://api.arcade.academy/en/latest/example_code/index.html>
|
||||||
|
- Darmowe assety (grafika, dźwięki): <https://kenney.nl/assets>
|
||||||
|
|
||||||
|
## Pułapki API — arcade 3.x kontra stare tutoriale
|
||||||
|
|
||||||
|
Duża część poradników w internecie opisuje arcade 2.x. Najczęstsze różnice:
|
||||||
|
|
||||||
|
| Stare (2.x) | Aktualne (3.x) |
|
||||||
|
| -------------------------------------- | ----------------------------------------------------------- |
|
||||||
|
| `arcade.start_render()` w `on_draw` | `self.clear()` |
|
||||||
|
| `sprite_list.on_update(delta_time)` | `sprite_list.update(delta_time)` — `SpriteList` **nie ma** `on_update` |
|
||||||
|
| `def update(self):` w klasie sprite'a | `def update(self, delta_time=1/60, *args, **kwargs):` |
|
||||||
|
| `arcade.draw_rectangle_filled(...)` | `arcade.draw_lrbt_rectangle_filled(...)` lub `draw_lbwh_rectangle_filled(...)` |
|
||||||
|
| `arcade.set_background_color(...)` | `self.background_color = ...` (atrybut okna / widoku) |
|
||||||
|
| `arcade.Camera(...)` | `arcade.Camera2D(...)` |
|
||||||
|
|
||||||
|
Jeżeli tutorial nie działa — najpierw sprawdź tę tabelę, potem oficjalną
|
||||||
|
dokumentację dla 3.x.
|
||||||
|
|
||||||
|
Ostrzeżenie `PerformanceWarning: draw_text is an extremely slow function`
|
||||||
|
pojawia się normalnie i **nie jest błędem** — arcade sugeruje, żeby przy wielu
|
||||||
|
napisach tworzyć obiekty `arcade.Text` raz w `__init__` i tylko je rysować.
|
||||||
|
Przy kilku napisach na ekranie można je zignorować.
|
||||||
1
assets/README.md
Normal file
1
assets/README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Grafikę i dźwięki do gier pobierz z <https://kenney.nl/assets> (darmowe, licencja CC0) i wypakuj tutaj.
|
||||||
Binary file not shown.
Binary file not shown.
BIN
docs/wyop.pdf
BIN
docs/wyop.pdf
Binary file not shown.
123
dzien1/01_okno.py
Normal file
123
dzien1/01_okno.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
"""
|
||||||
|
Dzień 1, krok 1 — pierwsze okno.
|
||||||
|
|
||||||
|
Cel: zobaczyć, że "klasa" to nic strasznego — to przepis na obiekt, który
|
||||||
|
biblioteka arcade sama za nas uruchomi i sama będzie wołać jego metody.
|
||||||
|
|
||||||
|
Uruchom:
|
||||||
|
python dzien1/01_okno.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import arcade
|
||||||
|
|
||||||
|
# Stałe piszemy WIELKIMI literami. To zwykłe zmienne globalne — po to,
|
||||||
|
# żeby rozmiar okna zmieniać w jednym miejscu, a nie w pięciu.
|
||||||
|
SZEROKOSC = 800
|
||||||
|
WYSOKOSC = 600
|
||||||
|
TYTUL = "Dzień 1 — moje pierwsze okno"
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# KLASA = przepis na obiekt.
|
||||||
|
#
|
||||||
|
# Zapis `class MyGame(arcade.Window)` znaczy: "MyGame jest rodzajem
|
||||||
|
# arcade.Window". To DZIEDZICZENIE. Nasza klasa dostaje za darmo wszystko,
|
||||||
|
# co potrafi okno z biblioteki: otwiera się, reaguje na mysz, na klawiaturę,
|
||||||
|
# odświeża się 60 razy na sekundę. My dopisujemy tylko to, co ma być NASZE.
|
||||||
|
#
|
||||||
|
# arcade.Window nazywamy klasą BAZOWĄ (albo rodzicem/nadklasą),
|
||||||
|
# a MyGame — klasą POCHODNĄ (albo dzieckiem/podklasą).
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
class MyGame(arcade.Window):
|
||||||
|
"""Okno naszej gry."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
KONSTRUKTOR — metoda wołana automatycznie w momencie tworzenia
|
||||||
|
obiektu, czyli gdy niżej piszemy `MyGame()`.
|
||||||
|
|
||||||
|
`self` to sam tworzony obiekt. Każda metoda w klasie dostaje go jako
|
||||||
|
pierwszy argument — dzięki temu wie, na czyich danych pracuje.
|
||||||
|
W wywołaniu `self` się NIE podaje: piszemy `okno.on_draw()`,
|
||||||
|
a Python sam podstawia `okno` pod `self`.
|
||||||
|
"""
|
||||||
|
# super() to "mój rodzic", czyli arcade.Window.
|
||||||
|
# Tu mówimy: "rodzicu, wykonaj swój konstruktor z takimi parametrami".
|
||||||
|
# Bez tej linii okno w ogóle by się nie utworzyło — to rodzic zna
|
||||||
|
# wszystkie szczegóły otwierania okna w systemie.
|
||||||
|
super().__init__(SZEROKOSC, WYSOKOSC, TYTUL)
|
||||||
|
|
||||||
|
# Atrybuty obiektu — zmienne, które "żyją" wewnątrz obiektu i są
|
||||||
|
# widoczne we WSZYSTKICH metodach tej klasy (przez self.).
|
||||||
|
# W kodzie proceduralnym trzymalibyśmy je w zmiennych globalnych.
|
||||||
|
self.background_color = arcade.color.DARK_BLUE
|
||||||
|
self.licznik_klatek = 0
|
||||||
|
|
||||||
|
def on_draw(self):
|
||||||
|
"""
|
||||||
|
NADPISANIE METODY: arcade.Window ma już metodę on_draw(), ale pustą.
|
||||||
|
Definiując ją u siebie, podmieniamy tamtą wersję na własną.
|
||||||
|
|
||||||
|
Tej metody NIGDY nie wołamy sami — arcade woła ją za nas przy każdej
|
||||||
|
klatce (~60 razy na sekundę). To jest tzw. odwrócenie sterowania:
|
||||||
|
my nie piszemy pętli głównej, my tylko wypełniamy gotowe miejsca.
|
||||||
|
"""
|
||||||
|
# W arcade 3.x czyścimy ekran przez self.clear().
|
||||||
|
# (W starych tutorialach do 2.x było arcade.start_render() — to inna
|
||||||
|
# funkcja o zupełnie innym znaczeniu, nie używaj jej tutaj.)
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
arcade.draw_text(
|
||||||
|
"Cześć! To jest obiekt klasy MyGame.",
|
||||||
|
x=50,
|
||||||
|
y=WYSOKOSC / 2,
|
||||||
|
color=arcade.color.WHITE,
|
||||||
|
font_size=22,
|
||||||
|
)
|
||||||
|
arcade.draw_text(
|
||||||
|
f"Klatka nr {self.licznik_klatek}",
|
||||||
|
x=50,
|
||||||
|
y=WYSOKOSC / 2 - 40,
|
||||||
|
color=arcade.color.GOLD,
|
||||||
|
font_size=16,
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO 1: dorysuj koło na środku ekranu.
|
||||||
|
# arcade.draw_circle_filled(środek_x, środek_y, promień, kolor)
|
||||||
|
# np. arcade.draw_circle_filled(SZEROKOSC / 2, 120, 40, arcade.color.RED)
|
||||||
|
|
||||||
|
# TODO 2: dorysuj prostokąt (np. "podłogę" na dole ekranu).
|
||||||
|
# W arcade 3.x są dwie wygodne wersje:
|
||||||
|
# arcade.draw_lrbt_rectangle_filled(left, right, bottom, top, kolor)
|
||||||
|
# arcade.draw_lbwh_rectangle_filled(left, bottom, width, height, kolor)
|
||||||
|
# Wybierz jedną i narysuj pas o wysokości 40 pikseli przy dolnej krawędzi.
|
||||||
|
|
||||||
|
def on_update(self, delta_time: float):
|
||||||
|
"""
|
||||||
|
Druga metoda wołana automatycznie ~60 razy na sekundę — tu zmieniamy
|
||||||
|
stan gry (a w on_draw tylko rysujemy to, co już wyliczone).
|
||||||
|
|
||||||
|
delta_time to czas w sekundach, jaki minął od poprzedniej klatki
|
||||||
|
(zwykle ok. 0.016 s). Przyda się w dniu 2, żeby ruch był równy
|
||||||
|
niezależnie od szybkości komputera.
|
||||||
|
"""
|
||||||
|
self.licznik_klatek += 1
|
||||||
|
|
||||||
|
def on_key_press(self, symbol: int, modifiers: int):
|
||||||
|
"""Kolejna nadpisana metoda — arcade woła ją po wciśnięciu klawisza."""
|
||||||
|
if symbol == arcade.key.ESCAPE:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# TU powstaje obiekt: MyGame() uruchamia __init__ i zwraca gotowe okno.
|
||||||
|
# Zmienna `okno` to jedna konkretna INSTANCJA klasy MyGame.
|
||||||
|
okno = MyGame()
|
||||||
|
|
||||||
|
# arcade.run() startuje pętlę główną: w kółko woła on_update() i on_draw()
|
||||||
|
# naszego obiektu, aż okno zostanie zamknięte.
|
||||||
|
arcade.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
135
dzien1/02_sprite_ruch.py
Normal file
135
dzien1/02_sprite_ruch.py
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
"""
|
||||||
|
Dzień 1, krok 2 — sprite i sterowanie z klawiatury.
|
||||||
|
|
||||||
|
Nowe pojęcia:
|
||||||
|
* sprite — obiekt gry, który ma pozycję, wygląd i prędkość
|
||||||
|
* SpriteList — pojemnik na sprite'y; rysuje i aktualizuje je wszystkie naraz
|
||||||
|
* change_x / change_y — prędkość sprite'a (o ile pikseli przesuwa się w klatce)
|
||||||
|
|
||||||
|
Uruchom:
|
||||||
|
python dzien1/02_sprite_ruch.py
|
||||||
|
|
||||||
|
Sterowanie: strzałki. Wyjście: Esc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import arcade
|
||||||
|
|
||||||
|
SZEROKOSC = 800
|
||||||
|
WYSOKOSC = 600
|
||||||
|
TYTUL = "Dzień 1 — ruch sprite'a"
|
||||||
|
|
||||||
|
PROMIEN_GRACZA = 25
|
||||||
|
PREDKOSC_GRACZA = 5 # w pikselach na klatkę (arcade przelicza to przez delta_time)
|
||||||
|
|
||||||
|
|
||||||
|
class MyGame(arcade.Window):
|
||||||
|
"""Okno gry — znowu dziedziczymy po arcade.Window."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(SZEROKOSC, WYSOKOSC, TYTUL)
|
||||||
|
self.background_color = arcade.color.DARK_SLATE_GRAY
|
||||||
|
|
||||||
|
# Atrybuty deklarujemy w __init__, nawet jeśli na razie są puste.
|
||||||
|
# Dzięki temu z jednego miejsca widać, jakie dane ma obiekt.
|
||||||
|
self.player_list = arcade.SpriteList()
|
||||||
|
self.player = None
|
||||||
|
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""
|
||||||
|
Metoda pomocnicza — nie jest częścią arcade, sami ją wymyśliliśmy.
|
||||||
|
Ustawia grę w stan początkowy. Trzymamy to osobno od __init__, bo
|
||||||
|
później (np. po przegranej) będziemy chcieli zrestartować grę bez
|
||||||
|
tworzenia nowego okna — wystarczy zawołać setup() jeszcze raz.
|
||||||
|
"""
|
||||||
|
self.player_list = arcade.SpriteList()
|
||||||
|
|
||||||
|
# SpriteCircle to gotowy sprite w kształcie koła — klasa z biblioteki.
|
||||||
|
# Tworzymy jej instancję: SpriteCircle(promień, kolor).
|
||||||
|
self.player = arcade.SpriteCircle(PROMIEN_GRACZA, arcade.color.AMAZON)
|
||||||
|
|
||||||
|
# center_x / center_y to atrybuty każdego sprite'a — jego pozycja.
|
||||||
|
self.player.center_x = SZEROKOSC / 2
|
||||||
|
self.player.center_y = WYSOKOSC / 2
|
||||||
|
|
||||||
|
# Wkładamy sprite'a do listy. Od teraz wystarczy operować na liście.
|
||||||
|
self.player_list.append(self.player)
|
||||||
|
|
||||||
|
def on_draw(self):
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
# SpriteList sam rysuje wszystkie swoje sprite'y — jedno wywołanie.
|
||||||
|
self.player_list.draw()
|
||||||
|
|
||||||
|
arcade.draw_text(
|
||||||
|
"Strzałki — ruch, Esc — wyjście",
|
||||||
|
x=10,
|
||||||
|
y=10,
|
||||||
|
color=arcade.color.WHITE,
|
||||||
|
font_size=14,
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_update(self, delta_time: float):
|
||||||
|
"""
|
||||||
|
UWAGA na API arcade 3.x:
|
||||||
|
SpriteList NIE MA metody on_update(). Listę odświeżamy metodą
|
||||||
|
update(delta_time), a ona woła update() na każdym sprite'cie.
|
||||||
|
|
||||||
|
Domyślne Sprite.update() dodaje change_x/change_y do pozycji — czyli
|
||||||
|
samo przesuwanie mamy za darmo. W dniu 2 nadpiszemy update() we
|
||||||
|
własnej klasie, żeby sprite robił coś więcej.
|
||||||
|
"""
|
||||||
|
self.player_list.update(delta_time)
|
||||||
|
|
||||||
|
# TODO 1: nie pozwól graczowi wyjechać poza okno.
|
||||||
|
# Każdy sprite ma atrybuty left, right, bottom, top (krawędzie).
|
||||||
|
# Przypisanie do nich przesuwa sprite'a, np.:
|
||||||
|
# if self.player.left < 0:
|
||||||
|
# self.player.left = 0
|
||||||
|
# Dopisz analogiczne warunki dla right (> SZEROKOSC),
|
||||||
|
# bottom (< 0) i top (> WYSOKOSC).
|
||||||
|
|
||||||
|
def on_key_press(self, symbol: int, modifiers: int):
|
||||||
|
"""
|
||||||
|
Wciśnięcie klawisza USTAWIA prędkość — nie przesuwa gracza od razu.
|
||||||
|
Samo przesuwanie dzieje się w on_update, klatka po klatce.
|
||||||
|
Dzięki temu ruch jest płynny, a nie skokowy.
|
||||||
|
"""
|
||||||
|
if symbol == arcade.key.LEFT:
|
||||||
|
self.player.change_x = -PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.RIGHT:
|
||||||
|
self.player.change_x = PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.UP:
|
||||||
|
self.player.change_y = PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.DOWN:
|
||||||
|
self.player.change_y = -PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.ESCAPE:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
# TODO 2: dodaj obsługę klawisza SPACE (arcade.key.SPACE).
|
||||||
|
# Wymyśl efekt i zapisz go w atrybucie obiektu, np.:
|
||||||
|
# - zmiana koloru gracza: self.player.color = arcade.color.RED
|
||||||
|
# - "dopalacz": ustaw self.szybki = True i w on_key_press mnóż
|
||||||
|
# PREDKOSC_GRACZA razy 2 (pamiętaj o wyłączeniu w on_key_release)
|
||||||
|
# - teleport na środek ekranu
|
||||||
|
# Ważne: stan trzymamy w self.<coś>, żeby inne metody też go widziały.
|
||||||
|
|
||||||
|
def on_key_release(self, symbol: int, modifiers: int):
|
||||||
|
"""
|
||||||
|
Puszczenie klawisza ZERUJE prędkość w danej osi.
|
||||||
|
Bez tej metody gracz po jednym naciśnięciu jechałby w nieskończoność.
|
||||||
|
"""
|
||||||
|
if symbol in (arcade.key.LEFT, arcade.key.RIGHT):
|
||||||
|
self.player.change_x = 0
|
||||||
|
elif symbol in (arcade.key.UP, arcade.key.DOWN):
|
||||||
|
self.player.change_y = 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
MyGame()
|
||||||
|
arcade.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
258
dzien2/01_monety_start.py
Normal file
258
dzien2/01_monety_start.py
Normal file
|
|
@ -0,0 +1,258 @@
|
||||||
|
"""
|
||||||
|
Dzień 2 — GŁÓWNE ZADANIE: własna hierarchia klas.
|
||||||
|
|
||||||
|
Ten plik DZIAŁA od razu: jest gracz, 10 losowych monet, licznik punktów
|
||||||
|
i wykrywanie kolizji. Uruchom go najpierw bez zmian:
|
||||||
|
|
||||||
|
python dzien2/01_monety_start.py
|
||||||
|
|
||||||
|
Sterowanie: strzałki. Wyjście: Esc. Restart: R.
|
||||||
|
|
||||||
|
===========================================================================
|
||||||
|
ZADANIE
|
||||||
|
===========================================================================
|
||||||
|
|
||||||
|
1. Napisz klasę bazową `Enemy` dziedziczącą po `arcade.SpriteCircle`.
|
||||||
|
Wróg ma:
|
||||||
|
* konstruktor `__init__`, który przyjmuje promień, kolor i pozycję
|
||||||
|
startową, woła `super().__init__(...)` i ustawia prędkość
|
||||||
|
(`self.change_x`, `self.change_y`),
|
||||||
|
* nadpisaną metodę `update(self, delta_time=1/60, *args, **kwargs)`,
|
||||||
|
w której najpierw wołasz `super().update(delta_time)` (to przesuwa
|
||||||
|
sprite'a zgodnie z prędkością), a potem sprawdzasz krawędzie okna
|
||||||
|
i ODBIJASZ wroga: przy zderzeniu z lewą/prawą krawędzią zmieniasz
|
||||||
|
znak `change_x`, przy górnej/dolnej — znak `change_y`.
|
||||||
|
|
||||||
|
2. Napisz DWIE klasy pochodne po `Enemy`, o RÓŻNYM zachowaniu. Każda musi
|
||||||
|
nadpisywać `update()` i wołać w nim `super().update(delta_time)`
|
||||||
|
(żeby odbijanie z klasy bazowej dalej działało), a potem dokładać coś
|
||||||
|
swojego. Przykładowe pomysły — wybierz dwa albo wymyśl własne:
|
||||||
|
* `Chaser` — powoli przyspiesza w stronę gracza (dostaje gracza
|
||||||
|
w konstruktorze i zapamiętuje go w atrybucie),
|
||||||
|
* `ZigZag` — co pewną liczbę klatek zmienia `change_y` na przeciwny,
|
||||||
|
* `Grower` — pulsuje: rośnie i maleje (odczyt: `self.scale_x`,
|
||||||
|
zapis: `self.scale = 1.2`; uwaga — `self.scale` przy
|
||||||
|
odczycie zwraca PARĘ wartości (x, y), nie jedną liczbę),
|
||||||
|
* `Sniper` — stoi w miejscu i co jakiś czas skacze w losowe miejsce.
|
||||||
|
|
||||||
|
3. Dodaj do `setup()` listę `self.enemies = arcade.SpriteList()`, wstaw do
|
||||||
|
niej po kilka wrogów obu klas, rysuj ją w `on_draw()` i aktualizuj
|
||||||
|
w `on_update()` przez `self.enemies.update(delta_time)`.
|
||||||
|
|
||||||
|
4. Obsłuż kolizję gracza z wrogiem (`arcade.check_for_collision_with_list`):
|
||||||
|
odejmij punkt / zakończ grę / cofnij gracza na środek — Twój wybór.
|
||||||
|
Napis "KONIEC GRY" i restart klawiszem R już są przygotowane
|
||||||
|
(patrz atrybut `self.koniec_gry`).
|
||||||
|
|
||||||
|
DLACZEGO TAK: `self.enemies.update()` woła `update()` na każdym wrogu, ale
|
||||||
|
KAŻDY wróg wykona SWOJĄ wersję tej metody — mimo że lista jest jedna i nie
|
||||||
|
ma w niej żadnego `if`. To jest POLIMORFIZM: ten sam kod, różne zachowania,
|
||||||
|
bo obiekty są różnych klas. Bez OOP musiałbyś trzymać typ wroga w polu
|
||||||
|
`rodzaj` i pisać `if rodzaj == "chaser": ... elif rodzaj == "zigzag": ...`
|
||||||
|
w każdym miejscu, które dotyka wrogów.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
import arcade
|
||||||
|
|
||||||
|
SZEROKOSC = 800
|
||||||
|
WYSOKOSC = 600
|
||||||
|
TYTUL = "Dzień 2 — monety i wrogowie"
|
||||||
|
|
||||||
|
PROMIEN_GRACZA = 20
|
||||||
|
PREDKOSC_GRACZA = 5
|
||||||
|
|
||||||
|
PROMIEN_MONETY = 10
|
||||||
|
LICZBA_MONET = 10
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# PODPOWIEDZIANY ZARYS KLASY Enemy — odkomentuj i uzupełnij.
|
||||||
|
#
|
||||||
|
# Zwróć uwagę na sygnaturę update() w arcade 3.x: MUSI przyjmować
|
||||||
|
# delta_time oraz *args/**kwargs, bo SpriteList woła update(delta_time)
|
||||||
|
# na każdym elemencie. Stare tutoriale (arcade 2.x) pokazują `def update(self)`
|
||||||
|
# bez argumentów — u nas to wysypie program.
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
# class Enemy(arcade.SpriteCircle):
|
||||||
|
# """Klasa BAZOWA wroga: porusza się i odbija od krawędzi okna."""
|
||||||
|
#
|
||||||
|
# def __init__(self, promien, kolor, center_x, center_y, change_x, change_y):
|
||||||
|
# # super() woła konstruktor SpriteCircle — bez tego sprite nie ma
|
||||||
|
# # ani tekstury, ani rozmiaru.
|
||||||
|
# super().__init__(promien, kolor)
|
||||||
|
# self.center_x = center_x
|
||||||
|
# self.center_y = center_y
|
||||||
|
# self.change_x = change_x
|
||||||
|
# self.change_y = change_y
|
||||||
|
#
|
||||||
|
# def update(self, delta_time=1 / 60, *args, **kwargs):
|
||||||
|
# # Najpierw zachowanie odziedziczone: przesunięcie o change_x/change_y.
|
||||||
|
# super().update(delta_time, *args, **kwargs)
|
||||||
|
#
|
||||||
|
# # TODO: odbijanie od krawędzi.
|
||||||
|
# # if self.left < 0 or self.right > SZEROKOSC:
|
||||||
|
# # self.change_x = -self.change_x
|
||||||
|
# # ... analogicznie dla bottom / top i change_y
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Chaser(Enemy):
|
||||||
|
# """Klasa POCHODNA nr 1 — dopisuje własne zachowanie do bazowego."""
|
||||||
|
#
|
||||||
|
# def __init__(self, center_x, center_y, player):
|
||||||
|
# super().__init__(15, arcade.color.RED, center_x, center_y, 2, 2)
|
||||||
|
# self.player = player # zapamiętujemy CUDZY obiekt — to kompozycja
|
||||||
|
#
|
||||||
|
# def update(self, delta_time=1 / 60, *args, **kwargs):
|
||||||
|
# super().update(delta_time, *args, **kwargs) # ruch + odbijanie
|
||||||
|
# # TODO: delikatnie skręcaj w stronę gracza, np.
|
||||||
|
# # if self.player.center_x > self.center_x:
|
||||||
|
# # self.change_x += 0.05
|
||||||
|
# # else:
|
||||||
|
# # self.change_x -= 0.05
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class ZigZag(Enemy):
|
||||||
|
# """Klasa POCHODNA nr 2 — inne zachowanie, ta sama klasa bazowa."""
|
||||||
|
#
|
||||||
|
# def __init__(self, center_x, center_y):
|
||||||
|
# super().__init__(12, arcade.color.PURPLE, center_x, center_y, 3, 0)
|
||||||
|
# self.licznik = 0
|
||||||
|
#
|
||||||
|
# def update(self, delta_time=1 / 60, *args, **kwargs):
|
||||||
|
# super().update(delta_time, *args, **kwargs)
|
||||||
|
# # TODO: co 30 klatek zmień kierunek w pionie
|
||||||
|
# # self.licznik += 1
|
||||||
|
# # if self.licznik % 30 == 0:
|
||||||
|
# # self.change_y = -self.change_y if self.change_y else 3
|
||||||
|
|
||||||
|
|
||||||
|
class MyGame(arcade.Window):
|
||||||
|
"""Okno gry."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(SZEROKOSC, WYSOKOSC, TYTUL)
|
||||||
|
self.background_color = arcade.color.DARK_BLUE
|
||||||
|
|
||||||
|
self.player_list = arcade.SpriteList()
|
||||||
|
self.coin_list = arcade.SpriteList()
|
||||||
|
# TODO (punkt 3 zadania): self.enemies = arcade.SpriteList()
|
||||||
|
|
||||||
|
self.player = None
|
||||||
|
self.punkty = 0
|
||||||
|
self.koniec_gry = False
|
||||||
|
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""Ustawia grę w stan początkowy. Wołane też przy restarcie (klawisz R)."""
|
||||||
|
self.punkty = 0
|
||||||
|
self.koniec_gry = False
|
||||||
|
|
||||||
|
# Gracz
|
||||||
|
self.player_list = arcade.SpriteList()
|
||||||
|
self.player = arcade.SpriteCircle(PROMIEN_GRACZA, arcade.color.AMAZON)
|
||||||
|
self.player.center_x = SZEROKOSC / 2
|
||||||
|
self.player.center_y = WYSOKOSC / 2
|
||||||
|
self.player_list.append(self.player)
|
||||||
|
|
||||||
|
# Monety w losowych miejscach
|
||||||
|
self.coin_list = arcade.SpriteList()
|
||||||
|
for _ in range(LICZBA_MONET):
|
||||||
|
moneta = arcade.SpriteCircle(PROMIEN_MONETY, arcade.color.GOLD)
|
||||||
|
moneta.center_x = random.randint(PROMIEN_MONETY, SZEROKOSC - PROMIEN_MONETY)
|
||||||
|
moneta.center_y = random.randint(PROMIEN_MONETY, WYSOKOSC - PROMIEN_MONETY)
|
||||||
|
self.coin_list.append(moneta)
|
||||||
|
|
||||||
|
# TODO (punkt 3 zadania): utwórz self.enemies i dodaj do niej wrogów,
|
||||||
|
# np. kilku Chaserów i kilka ZigZagów w losowych miejscach.
|
||||||
|
# Chaser potrzebuje referencji do gracza: Chaser(x, y, self.player)
|
||||||
|
|
||||||
|
def on_draw(self):
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
self.coin_list.draw()
|
||||||
|
self.player_list.draw()
|
||||||
|
# TODO (punkt 3 zadania): self.enemies.draw()
|
||||||
|
|
||||||
|
arcade.draw_text(
|
||||||
|
f"Punkty: {self.punkty}",
|
||||||
|
x=10,
|
||||||
|
y=WYSOKOSC - 30,
|
||||||
|
color=arcade.color.WHITE,
|
||||||
|
font_size=18,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.koniec_gry:
|
||||||
|
arcade.draw_text(
|
||||||
|
"KONIEC GRY — naciśnij R",
|
||||||
|
x=SZEROKOSC / 2,
|
||||||
|
y=WYSOKOSC / 2,
|
||||||
|
color=arcade.color.RED,
|
||||||
|
font_size=32,
|
||||||
|
anchor_x="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_update(self, delta_time: float):
|
||||||
|
if self.koniec_gry:
|
||||||
|
return
|
||||||
|
|
||||||
|
# arcade 3.x: lista sprite'ów odświeża się metodą update(delta_time).
|
||||||
|
# SpriteList nie ma metody on_update — to częsty błąd ze starych kursów.
|
||||||
|
self.player_list.update(delta_time)
|
||||||
|
|
||||||
|
# Trzymamy gracza w granicach okna
|
||||||
|
if self.player.left < 0:
|
||||||
|
self.player.left = 0
|
||||||
|
if self.player.right > SZEROKOSC:
|
||||||
|
self.player.right = SZEROKOSC
|
||||||
|
if self.player.bottom < 0:
|
||||||
|
self.player.bottom = 0
|
||||||
|
if self.player.top > WYSOKOSC:
|
||||||
|
self.player.top = WYSOKOSC
|
||||||
|
|
||||||
|
# Kolizje z monetami: funkcja zwraca LISTĘ trafionych sprite'ów.
|
||||||
|
zebrane = arcade.check_for_collision_with_list(self.player, self.coin_list)
|
||||||
|
for moneta in zebrane:
|
||||||
|
# remove_from_sprite_lists() usuwa sprite'a ze WSZYSTKICH list,
|
||||||
|
# do których należy — nie trzeba pamiętać, gdzie go włożyliśmy.
|
||||||
|
moneta.remove_from_sprite_lists()
|
||||||
|
self.punkty += 1
|
||||||
|
|
||||||
|
# TODO (punkty 3 i 4 zadania):
|
||||||
|
# 1) self.enemies.update(delta_time)
|
||||||
|
# — każdy wróg wykona SWOJĄ wersję update(), bo są różnych klas
|
||||||
|
# 2) trafieni = arcade.check_for_collision_with_list(self.player, self.enemies)
|
||||||
|
# if trafieni:
|
||||||
|
# self.koniec_gry = True # albo: odejmij punkt, cofnij gracza...
|
||||||
|
|
||||||
|
def on_key_press(self, symbol: int, modifiers: int):
|
||||||
|
if symbol == arcade.key.LEFT:
|
||||||
|
self.player.change_x = -PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.RIGHT:
|
||||||
|
self.player.change_x = PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.UP:
|
||||||
|
self.player.change_y = PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.DOWN:
|
||||||
|
self.player.change_y = -PREDKOSC_GRACZA
|
||||||
|
elif symbol == arcade.key.R:
|
||||||
|
self.setup()
|
||||||
|
elif symbol == arcade.key.ESCAPE:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def on_key_release(self, symbol: int, modifiers: int):
|
||||||
|
if symbol in (arcade.key.LEFT, arcade.key.RIGHT):
|
||||||
|
self.player.change_x = 0
|
||||||
|
elif symbol in (arcade.key.UP, arcade.key.DOWN):
|
||||||
|
self.player.change_y = 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
MyGame()
|
||||||
|
arcade.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
140
dzien3_4_platformowka/README.md
Normal file
140
dzien3_4_platformowka/README.md
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Dni 3–4 — platformówka wg oficjalnego tutoriala
|
||||||
|
|
||||||
|
Podstawa: **Simple Platformer** —
|
||||||
|
<https://api.arcade.academy/en/latest/tutorials/platform_tutorial/index.html>
|
||||||
|
|
||||||
|
Tutorial jest po angielsku i ma 14 kroków. Robimy go po kolei, ale **nie kopiuj
|
||||||
|
całych plików** — przepisuj kod ręcznie do `gra.py` i po każdym kroku uruchamiaj:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python dzien3_4_platformowka/gra.py
|
||||||
|
git add -A && git commit -m "dzien3: krok 5 — grawitacja"
|
||||||
|
```
|
||||||
|
|
||||||
|
Assety (grafika, dźwięki) — tutorial używa paczki Kenney'a, pobierz z
|
||||||
|
<https://kenney.nl/assets> do katalogu `assets/`. Arcade ma też wbudowane
|
||||||
|
zasoby dostępne przez ścieżki `":resources:images/..."` — na start to wystarczy
|
||||||
|
i nic nie trzeba pobierać.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dzień 3 — gracz, platformy, fizyka, skok, kamera
|
||||||
|
|
||||||
|
Kroki 1–7 tutoriala.
|
||||||
|
|
||||||
|
- [ ] **Krok 1 — Install and Open a Window.** Klasa okna, `__init__` z `super()`,
|
||||||
|
`on_draw()` z `self.clear()`. To już umiesz z dnia 1 — przelatujemy szybko.
|
||||||
|
- [ ] **Krok 2 — Textures and Sprites.** `arcade.Sprite(ścieżka, scale)`.
|
||||||
|
Zwróć uwagę: teraz sprite ma obrazek, a nie kolorowe koło.
|
||||||
|
- [ ] **Krok 3 — Many Sprites with SpriteList.** Osobne listy: `player_list`,
|
||||||
|
`wall_list`. Ułóż podłogę w pętli `for x in range(0, 1250, 64)`.
|
||||||
|
- [ ] **Krok 4 — Add User Control.** `on_key_press` / `on_key_release`
|
||||||
|
ustawiają `change_x`.
|
||||||
|
- [ ] **Krok 5 — Add Gravity.** `arcade.PhysicsEnginePlatformer(player_sprite,
|
||||||
|
walls=wall_list, gravity_constant=1.0)`.
|
||||||
|
**To jest KOMPOZYCJA:** okno *ma* silnik fizyki (`self.physics_engine`),
|
||||||
|
a nie *jest* silnikiem fizyki. Dziedziczenie = „jest czymś”,
|
||||||
|
kompozycja = „ma coś”. Nie myl tych dwóch.
|
||||||
|
Skok: w `on_key_press` sprawdź `self.physics_engine.can_jump()` i ustaw
|
||||||
|
`change_y = PLAYER_JUMP_SPEED`.
|
||||||
|
W `on_update()` wołaj `self.physics_engine.update()` — silnik sam
|
||||||
|
przesuwa gracza i zatrzymuje go na ścianach (nie wołaj tu
|
||||||
|
`player_list.update()`, bo ruch policzyłby się dwa razy).
|
||||||
|
- [ ] **Krok 6 — Resetting.** Wydziel metodę `setup()` i zawołaj ją z `__init__`.
|
||||||
|
Dzięki temu restart gry to jedno wywołanie.
|
||||||
|
- [ ] **Krok 7 — Adding a Camera.** W arcade 3.x klasa nazywa się
|
||||||
|
`arcade.Camera2D` (stare tutoriale pokazują `arcade.Camera` — to 2.x).
|
||||||
|
Dwie kamery: jedna śledzi gracza (świat), druga stoi w miejscu (GUI/punkty).
|
||||||
|
|
||||||
|
**Koniec dnia 3:** gracz biega po platformach, skacze, kamera jedzie za nim.
|
||||||
|
Commit: `dzien3: platformowka - ruch, skok, kamera`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dzień 4 — monety, dźwięki, wrogowie, poziomy, ekran końca
|
||||||
|
|
||||||
|
Kroki 8–14 tutoriala.
|
||||||
|
|
||||||
|
- [ ] **Krok 8 — Collecting Coins.** `arcade.check_for_collision_with_list`
|
||||||
|
+ `remove_from_sprite_lists()` (znasz z dnia 2).
|
||||||
|
- [ ] **Krok 9 — Adding Sound.** `arcade.load_sound(...)` w `setup()`,
|
||||||
|
`arcade.play_sound(...)` przy zebraniu monety i przy skoku.
|
||||||
|
Dźwięki wczytuj RAZ w `setup()`, nie w `on_update()` — inaczej gra zacznie
|
||||||
|
zwalniać.
|
||||||
|
- [ ] **Krok 10 — Adding a Score.** Licznik punktów + `arcade.draw_text` na
|
||||||
|
kamerze GUI.
|
||||||
|
- [ ] **Krok 11 — Using a Scene.** `arcade.Scene` zamiast ręcznie trzymanych
|
||||||
|
list. To znowu kompozycja: scena *ma* listy sprite'ów i wie, w jakiej
|
||||||
|
kolejności je rysować.
|
||||||
|
- [ ] **Krok 12 — Loading a Map From a Map Editor.** Mapa z pliku `.tmx`
|
||||||
|
(edytor [Tiled](https://www.mapeditor.org/)), `arcade.load_tilemap`.
|
||||||
|
- [ ] **Krok 13 — More Types of Layers.** Warstwy: platformy ruchome, kolce,
|
||||||
|
tło.
|
||||||
|
- [ ] **Krok 14 — Multiple Levels.** Numer poziomu w atrybucie `self.level`,
|
||||||
|
wczytywanie pliku `mapa_{self.level}.tmx`.
|
||||||
|
|
||||||
|
### Zadania własne (poza tutorialem)
|
||||||
|
|
||||||
|
- [ ] **Wrogowie.** Wykorzystaj klasę `Enemy` z dnia 2 — przenieś ją tutaj
|
||||||
|
i dostosuj do platformówki (np. wróg chodzi po platformie i zawraca na
|
||||||
|
jej końcu). Kolizja gracza z wrogiem = utrata życia.
|
||||||
|
- [ ] **Ekran końca gry.** Zrób go przez `arcade.View`: klasa `GameView`
|
||||||
|
(rozgrywka) i klasa `GameOverView` (napis + „naciśnij ENTER”).
|
||||||
|
Przełączanie: `self.window.show_view(GameOverView())`.
|
||||||
|
To przedsmak dnia 5 — stany gry jako osobne klasy.
|
||||||
|
|
||||||
|
- [ ] **ZADANIE OOP DNIA — `property health` (0–100).**
|
||||||
|
|
||||||
|
Dodaj graczowi punkty życia tak, żeby **nie dało się** ustawić wartości
|
||||||
|
spoza zakresu 0–100 — nawet przez pomyłkę w innym miejscu kodu.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Player(arcade.Sprite):
|
||||||
|
def __init__(self, sciezka, scale):
|
||||||
|
super().__init__(sciezka, scale)
|
||||||
|
self._health = 100 # podkreślnik = "to jest wewnętrzne, nie ruszaj z zewnątrz"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def health(self):
|
||||||
|
"""Getter — działa jak zwykły odczyt atrybutu: print(gracz.health)"""
|
||||||
|
return self._health
|
||||||
|
|
||||||
|
@health.setter
|
||||||
|
def health(self, wartosc):
|
||||||
|
"""Setter — uruchamia się przy `gracz.health = 120` i pilnuje zakresu."""
|
||||||
|
# TODO: przytnij wartość do przedziału 0-100
|
||||||
|
# (podpowiedź: max(0, min(100, wartosc)))
|
||||||
|
# TODO: jeśli po przycięciu jest 0 — ustaw self.zywy = False
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Sens `property`: z zewnątrz `gracz.health -= 30` wygląda jak zwykłe działanie
|
||||||
|
na atrybucie, ale pod spodem wykonuje się TWÓJ kod z walidacją. Nie trzeba
|
||||||
|
pisać `gracz.set_health(gracz.get_health() - 30)` ani pamiętać o sprawdzaniu
|
||||||
|
zakresu w każdym miejscu, gdzie gracz obrywa.
|
||||||
|
|
||||||
|
Sprawdź, że działa:
|
||||||
|
```python
|
||||||
|
gracz.health = 150 # -> 100
|
||||||
|
gracz.health = -20 # -> 0, i gracz przestaje żyć
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Narysuj pasek życia (`arcade.draw_lbwh_rectangle_filled`) o szerokości
|
||||||
|
proporcjonalnej do `gracz.health`.
|
||||||
|
|
||||||
|
**Koniec dnia 4:** kompletna platformówka z monetami, dźwiękiem, wrogami,
|
||||||
|
paskiem życia i ekranem końca.
|
||||||
|
Commit: `dzien4: platformowka - monety, dzwieki, wrogowie, health property`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ściąga — API arcade 3.x
|
||||||
|
|
||||||
|
| Co | Jak |
|
||||||
|
| ---------------------- | --------------------------------------------------------------- |
|
||||||
|
| czyszczenie ekranu | `self.clear()` (nie `arcade.start_render()`) |
|
||||||
|
| odświeżenie listy | `sprite_list.update(delta_time)` (nie `on_update`) |
|
||||||
|
| metoda w klasie sprite | `def update(self, delta_time=1/60, *args, **kwargs):` |
|
||||||
|
| kamera | `arcade.Camera2D` (nie `arcade.Camera`) |
|
||||||
|
| prostokąt | `arcade.draw_lrbt_rectangle_filled` / `draw_lbwh_rectangle_filled` |
|
||||||
|
| kolor tła | `self.background_color = arcade.color.X` |
|
||||||
167
dzien3_4_platformowka/gra.py
Normal file
167
dzien3_4_platformowka/gra.py
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
"""
|
||||||
|
Dni 3-4 — platformówka. Szkielet do rozbudowy.
|
||||||
|
|
||||||
|
To jest CELOWO prawie pusty plik: przez dwa dni wypełniasz go krok po kroku
|
||||||
|
według oficjalnego tutoriala (lista kroków w README.md obok).
|
||||||
|
|
||||||
|
Uruchom teraz — otworzy się puste okno z podpowiedzią. To znaczy, że
|
||||||
|
środowisko działa i można zaczynać.
|
||||||
|
|
||||||
|
python dzien3_4_platformowka/gra.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import arcade
|
||||||
|
|
||||||
|
SZEROKOSC = 1280
|
||||||
|
WYSOKOSC = 720
|
||||||
|
TYTUL = "Platformówka"
|
||||||
|
|
||||||
|
# Skala i stałe fizyki — przydadzą się od kroku 4 tutoriala.
|
||||||
|
SKALA_KAFELKA = 0.5
|
||||||
|
SKALA_GRACZA = 1.0
|
||||||
|
PREDKOSC_GRACZA = 5
|
||||||
|
GRAWITACJA = 1
|
||||||
|
SILA_SKOKU = 20
|
||||||
|
|
||||||
|
|
||||||
|
class GameView(arcade.View):
|
||||||
|
"""
|
||||||
|
Widok rozgrywki.
|
||||||
|
|
||||||
|
Dziedziczymy po arcade.View, a nie po arcade.Window, bo w dniu 4 dojdzie
|
||||||
|
drugi widok (ekran końca gry) i będziemy się między nimi przełączać przez
|
||||||
|
self.window.show_view(...). Jedno okno, wiele widoków.
|
||||||
|
|
||||||
|
arcade.View ma te same metody-haki co Window: on_draw, on_update,
|
||||||
|
on_key_press, on_key_release — plus on_show_view (wołane, gdy widok
|
||||||
|
staje się aktywny).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.background_color = arcade.color.DARK_BLUE
|
||||||
|
|
||||||
|
# --- Miejsce na atrybuty gry ---------------------------------------
|
||||||
|
# Deklarujemy je (na razie jako None / puste listy) już teraz, żeby
|
||||||
|
# z jednego miejsca było widać, z czego składa się gra.
|
||||||
|
|
||||||
|
# Krok 3: listy sprite'ów (albo od kroku 11: self.scene = None)
|
||||||
|
self.player_list = None
|
||||||
|
self.wall_list = None
|
||||||
|
|
||||||
|
# Krok 2-3: gracz
|
||||||
|
self.player_sprite = None
|
||||||
|
|
||||||
|
# Krok 5: silnik fizyki — KOMPOZYCJA: widok MA silnik, nie JEST silnikiem
|
||||||
|
self.physics_engine = None
|
||||||
|
|
||||||
|
# Krok 7: kamery — jedna jedzie za graczem, druga stoi (napisy)
|
||||||
|
self.camera = None
|
||||||
|
self.camera_gui = None
|
||||||
|
|
||||||
|
# Krok 10: punkty
|
||||||
|
self.punkty = 0
|
||||||
|
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""
|
||||||
|
Ustawia grę w stan początkowy — wołane z __init__ oraz przy restarcie
|
||||||
|
(krok 6 tutoriala). Cała zawartość poziomu powstaje TUTAJ, nie w __init__.
|
||||||
|
"""
|
||||||
|
self.punkty = 0
|
||||||
|
|
||||||
|
# TODO krok 3: self.player_list = arcade.SpriteList()
|
||||||
|
# self.wall_list = arcade.SpriteList(use_spatial_hash=True)
|
||||||
|
|
||||||
|
# TODO krok 2: utwórz gracza, np.
|
||||||
|
# self.player_sprite = arcade.Sprite(
|
||||||
|
# ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png",
|
||||||
|
# scale=SKALA_GRACZA,
|
||||||
|
# )
|
||||||
|
# self.player_sprite.center_x = 64
|
||||||
|
# self.player_sprite.center_y = 128
|
||||||
|
# self.player_list.append(self.player_sprite)
|
||||||
|
|
||||||
|
# TODO krok 3: ułóż podłogę w pętli
|
||||||
|
# for x in range(0, 1250, 64):
|
||||||
|
# kafelek = arcade.Sprite(":resources:images/tiles/grassMid.png",
|
||||||
|
# scale=SKALA_KAFELKA)
|
||||||
|
# kafelek.center_x = x
|
||||||
|
# kafelek.center_y = 32
|
||||||
|
# self.wall_list.append(kafelek)
|
||||||
|
|
||||||
|
# TODO krok 5: self.physics_engine = arcade.PhysicsEnginePlatformer(
|
||||||
|
# self.player_sprite,
|
||||||
|
# walls=self.wall_list,
|
||||||
|
# gravity_constant=GRAWITACJA,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# TODO krok 7: self.camera = arcade.Camera2D()
|
||||||
|
# self.camera_gui = arcade.Camera2D()
|
||||||
|
|
||||||
|
def on_draw(self):
|
||||||
|
"""Rysowanie. Wołane automatycznie ~60 razy na sekundę."""
|
||||||
|
self.clear() # arcade 3.x — NIE arcade.start_render()
|
||||||
|
|
||||||
|
# TODO krok 7: self.camera.use() <- od tego miejsca rysujemy "w świecie"
|
||||||
|
# TODO krok 3: self.wall_list.draw()
|
||||||
|
# self.player_list.draw()
|
||||||
|
|
||||||
|
# TODO krok 7: self.camera_gui.use() <- napisy: nie jadą razem z kamerą
|
||||||
|
# TODO krok 10: arcade.draw_text(f"Punkty: {self.punkty}", 10, 10,
|
||||||
|
# arcade.color.WHITE, 18)
|
||||||
|
|
||||||
|
arcade.draw_text(
|
||||||
|
"Pusty szkielet — otwórz README.md obok i zacznij od kroku 1",
|
||||||
|
x=SZEROKOSC / 2,
|
||||||
|
y=WYSOKOSC / 2,
|
||||||
|
color=arcade.color.WHITE,
|
||||||
|
font_size=20,
|
||||||
|
anchor_x="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_update(self, delta_time: float):
|
||||||
|
"""Logika gry. Tu zmieniamy stan, w on_draw tylko go pokazujemy."""
|
||||||
|
# TODO krok 5: self.physics_engine.update()
|
||||||
|
# Uwaga: gdy używasz silnika fizyki, NIE wołaj player_list.update() —
|
||||||
|
# silnik sam przesuwa gracza. Inaczej ruch policzy się dwa razy.
|
||||||
|
|
||||||
|
# TODO krok 8: kolizje z monetami (check_for_collision_with_list)
|
||||||
|
# TODO krok 7: przesuń kamerę za graczem
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_key_press(self, symbol: int, modifiers: int):
|
||||||
|
# TODO krok 4: strzałki LEFT/RIGHT -> self.player_sprite.change_x
|
||||||
|
# TODO krok 5: skok
|
||||||
|
# if symbol == arcade.key.UP and self.physics_engine.can_jump():
|
||||||
|
# self.player_sprite.change_y = SILA_SKOKU
|
||||||
|
if symbol == arcade.key.ESCAPE:
|
||||||
|
self.window.close()
|
||||||
|
|
||||||
|
def on_key_release(self, symbol: int, modifiers: int):
|
||||||
|
# TODO krok 4: zeruj change_x po puszczeniu strzałki
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# TODO dzień 4: druga klasa widoku — ekran końca gry.
|
||||||
|
#
|
||||||
|
# class GameOverView(arcade.View):
|
||||||
|
# def on_draw(self):
|
||||||
|
# self.clear()
|
||||||
|
# arcade.draw_text("KONIEC GRY - ENTER", SZEROKOSC / 2, WYSOKOSC / 2,
|
||||||
|
# arcade.color.WHITE, 40, anchor_x="center")
|
||||||
|
#
|
||||||
|
# def on_key_press(self, symbol, modifiers):
|
||||||
|
# if symbol == arcade.key.ENTER:
|
||||||
|
# self.window.show_view(GameView()) # nowa gra od zera
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
okno = arcade.Window(SZEROKOSC, WYSOKOSC, TYTUL)
|
||||||
|
okno.show_view(GameView())
|
||||||
|
arcade.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
135
dzien5_projekt/WYMAGANIA.md
Normal file
135
dzien5_projekt/WYMAGANIA.md
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Dzień 5 — własna gra od zera
|
||||||
|
|
||||||
|
Projekt zaliczeniowy. Piszesz **własną** grę — nie przepisujesz tutoriala.
|
||||||
|
Pomysł jest Twój; poniżej są tylko wymagania techniczne, które kod musi spełnić.
|
||||||
|
|
||||||
|
## Zakres
|
||||||
|
|
||||||
|
Gra ma być mała i skończona. Lepiej prosta gra, która działa i ma ekran końca,
|
||||||
|
niż ambitna, która się nie uruchamia. Przykłady o dobrym rozmiarze:
|
||||||
|
|
||||||
|
- uciekaj przed ścigającymi Cię wrogami i zbieraj punkty,
|
||||||
|
- prosty shooter (statek strzela, przeciwnicy lecą z góry),
|
||||||
|
- wąż / snake z przeszkodami,
|
||||||
|
- gra zręcznościowa: unikaj spadających obiektów,
|
||||||
|
- mini-platformówka z jednym poziomem i bossem.
|
||||||
|
|
||||||
|
## Wymagania obowiązkowe
|
||||||
|
|
||||||
|
### 1. Minimum 3 własne klasy
|
||||||
|
|
||||||
|
Nie licząc klas widoków. Np. `Player`, `Enemy`, `Bullet`, `PowerUp`.
|
||||||
|
|
||||||
|
### 2. Jedna hierarchia dziedziczenia: baza + co najmniej 2 klasy pochodne
|
||||||
|
|
||||||
|
```
|
||||||
|
Enemy <- klasa bazowa: wspólne dane i wspólne zachowanie
|
||||||
|
/ \
|
||||||
|
Chaser Shooter <- klasy pochodne: każda nadpisuje update() inaczej
|
||||||
|
```
|
||||||
|
|
||||||
|
Warunki:
|
||||||
|
- klasa bazowa musi mieć **realną treść** — wspólny `__init__` i wspólną
|
||||||
|
implementację `update()`, a nie samo `pass`,
|
||||||
|
- każda klasa pochodna nadpisuje przynajmniej jedną metodę i woła w niej
|
||||||
|
`super().<metoda>(...)`,
|
||||||
|
- wszystkie obiekty tej hierarchii trzymasz w **jednej** `SpriteList`
|
||||||
|
i aktualizujesz jednym `lista.update(delta_time)` — bez `if`-a po typie.
|
||||||
|
To jest sens polimorfizmu: dodanie trzeciego rodzaju wroga nie wymaga
|
||||||
|
zmiany ani jednej linii w pętli gry.
|
||||||
|
|
||||||
|
Pamiętaj o sygnaturze z arcade 3.x:
|
||||||
|
```python
|
||||||
|
def update(self, delta_time=1 / 60, *args, **kwargs):
|
||||||
|
super().update(delta_time, *args, **kwargs)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Minimum jedno `property` z walidacją
|
||||||
|
|
||||||
|
Np. `health` (0–100), `ammo` (nie mniej niż 0), `poziom_trudnosci` (1–5),
|
||||||
|
`speed` (z górnym limitem).
|
||||||
|
|
||||||
|
```python
|
||||||
|
@property
|
||||||
|
def ammo(self):
|
||||||
|
return self._ammo
|
||||||
|
|
||||||
|
@ammo.setter
|
||||||
|
def ammo(self, wartosc):
|
||||||
|
if wartosc < 0:
|
||||||
|
raise ValueError("Amunicja nie może być ujemna")
|
||||||
|
self._ammo = min(wartosc, self.max_ammo)
|
||||||
|
```
|
||||||
|
|
||||||
|
Walidacja musi **coś robić** — przycinać zakres, rzucać wyjątek albo wywoływać
|
||||||
|
efekt uboczny (np. `health` spadające do 0 przełącza grę na ekran końca).
|
||||||
|
Samo `return self._x` bez settera nie liczy się jako walidacja.
|
||||||
|
|
||||||
|
### 4. Stany gry przez `arcade.View`
|
||||||
|
|
||||||
|
Minimum trzy widoki, każdy jako osobna klasa:
|
||||||
|
|
||||||
|
- `MenuView` — ekran startowy („naciśnij ENTER, żeby zacząć”),
|
||||||
|
- `GameView` — właściwa rozgrywka,
|
||||||
|
- `GameOverView` — wynik + możliwość powtórki.
|
||||||
|
|
||||||
|
Przełączanie: `self.window.show_view(GameView())`.
|
||||||
|
Nie rób stanów przez `if self.stan == "menu"` w jednym wielkim `on_draw()` —
|
||||||
|
o to właśnie chodzi w tym wymaganiu.
|
||||||
|
|
||||||
|
### 5. Kod w wielu plikach w `src/`
|
||||||
|
|
||||||
|
Struktura, do której dążymy:
|
||||||
|
|
||||||
|
```
|
||||||
|
dzien5_projekt/
|
||||||
|
├── WYMAGANIA.md
|
||||||
|
├── main.py # tylko uruchomienie: tworzy okno i pokazuje MenuView
|
||||||
|
└── src/
|
||||||
|
├── __init__.py
|
||||||
|
├── settings.py # stałe: rozmiar okna, prędkości, kolory
|
||||||
|
├── entities.py # Player, Enemy + klasy pochodne, Bullet...
|
||||||
|
└── views.py # MenuView, GameView, GameOverView
|
||||||
|
```
|
||||||
|
|
||||||
|
Import między plikami:
|
||||||
|
```python
|
||||||
|
from src.settings import SZEROKOSC, WYSOKOSC
|
||||||
|
from src.entities import Player, Chaser
|
||||||
|
```
|
||||||
|
|
||||||
|
Uruchamiasz z katalogu `dzien5_projekt/`:
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Podział na pliki jest sensowny wtedy, gdy `entities.py` **nie musi** nic
|
||||||
|
wiedzieć o `views.py`. Jeśli oba pliki importują się nawzajem — to znak, że
|
||||||
|
podział jest zły; przenieś wspólne rzeczy do `settings.py`.
|
||||||
|
|
||||||
|
## Sposób pracy
|
||||||
|
|
||||||
|
1. Najpierw narysuj na kartce, jakie masz klasy i co która trzyma.
|
||||||
|
2. Zrób `settings.py` i `main.py`, które otwierają puste okno. **Commit.**
|
||||||
|
3. Dodaj `MenuView` i `GameView` z pustą planszą. **Commit.**
|
||||||
|
4. Dodaj gracza, ruch. **Commit.**
|
||||||
|
5. Dodaj hierarchię wrogów. **Commit.**
|
||||||
|
6. Dodaj `property`, kolizje, punkty, `GameOverView`. **Commit.**
|
||||||
|
7. Dopiero na końcu — dźwięki, grafika z <https://kenney.nl/assets>, efekty.
|
||||||
|
|
||||||
|
## Checklista przed oddaniem
|
||||||
|
|
||||||
|
- [ ] `python main.py` uruchamia się bez błędów
|
||||||
|
(`PerformanceWarning: draw_text is an extremely slow function` to nie błąd —
|
||||||
|
arcade sugeruje obiekty `arcade.Text` zamiast `arcade.draw_text`;
|
||||||
|
przy naszej liczbie napisów można to zignorować)
|
||||||
|
- [ ] 3+ własne klasy (bez widoków)
|
||||||
|
- [ ] hierarchia: baza + 2 pochodne, każda z nadpisanym `update()` i `super()`
|
||||||
|
- [ ] wrogowie w jednej `SpriteList`, aktualizowani bez `if`-a po typie
|
||||||
|
- [ ] `property` z działającą walidacją (pokaż, że odrzuca złą wartość)
|
||||||
|
- [ ] 3 widoki `arcade.View`, przełączanie działa w obie strony
|
||||||
|
- [ ] kod w `src/`, `main.py` ma mniej niż 20 linii
|
||||||
|
- [ ] historia gita: kilka commitów, każdy z działającym stanem gry
|
||||||
|
- [ ] w kodzie nie ma zmiennych globalnych trzymających stan gry
|
||||||
|
(stałe z `settings.py` są OK — one się nie zmieniają)
|
||||||
0
dzien5_projekt/src/.gitkeep
Normal file
0
dzien5_projekt/src/.gitkeep
Normal file
3
how --stat HEAD
Normal file
3
how --stat HEAD
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[33m7c33b6b[m[33m ([m[1;36mHEAD[m[33m -> [m[1;32mmain[m[33m)[m scaffold: 5-dniowy kurs OOP + arcade
|
||||||
|
[33m0acb692[m[33m ([m[1;31morigin/main[m[33m)[m docs
|
||||||
|
[33mc5969bd[m first commit
|
||||||
135
plan.md
Normal file
135
plan.md
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Plan 5-dniowy (intensywny): OOP w Pythonie + Arcade
|
||||||
|
|
||||||
|
Dla ucznia znającego proceduralnego Pythona na poziomie Olimpiady Informatycznej Juniorów • ok. 6–8 h dziennie
|
||||||
|
|
||||||
|
Zasada: teoria OOP w małych dawkach rano, resztę dnia kodowanie w Arcade. Arcade jest obiektowe od podstaw, więc każdy dzień praktyki utrwala klasy, dziedziczenie i metody w naturalnym kontekście. Przed startem: `pip install arcade` (Python 3.9+).
|
||||||
|
|
||||||
|
## Dzień 1 — Podstawy OOP + pierwsze okno
|
||||||
|
|
||||||
|
**Rano (2–3 h): teoria**
|
||||||
|
* Corey Schafer — Python OOP Tutorials, odcinki 1–3 (klasy i instancje, zmienne klasowe, classmethod/staticmethod). Oglądać z pauzami i przepisywać kod samodzielnie.
|
||||||
|
[https://www.youtube.com/playlist?list=PL-osiE80TeTsqhIuOqKhwlXsIBIdSeYtc](https://www.youtube.com/playlist?list=PL-osiE80TeTsqhIuOqKhwlXsIBIdSeYtc)
|
||||||
|
|
||||||
|
**Po południu (4–5 h): praktyka**
|
||||||
|
* Real Python — „Arcade: A Primer on the Python Game Framework”: okno, rysowanie, sprite'y, pętla gry, klawiatura. Przejść cały tutorial, potem zmodyfikować grę po swojemu (inne prędkości, nowy typ obiektu).
|
||||||
|
[https://realpython.com/arcade-python-game-framework/](https://realpython.com/arcade-python-game-framework/)
|
||||||
|
|
||||||
|
**Wieczorem, do poduszki:**
|
||||||
|
* PyCon 2018, Paul Craven — „Easy 2D Game Creation With Arcade” (~30 min) — przegląd możliwości biblioteki od jej autora.
|
||||||
|
[https://www.youtube.com/watch?v=DAWHMHMPVHU](https://www.youtube.com/watch?v=DAWHMHMPVHU)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dzień 2 — Dziedziczenie + sprite'y, kolizje, zdarzenia
|
||||||
|
|
||||||
|
**Rano (1,5–2 h): teoria**
|
||||||
|
* Corey Schafer, odcinki 4–5 (dziedziczenie i klasy pochodne, dunder methods).
|
||||||
|
|
||||||
|
**Po południu (5–6 h): praktyka**
|
||||||
|
* Wybrane rozdziały kursu autora biblioteki Arcade Academy — Learn Python: sprite'y, kolizje, poruszanie się, dźwięki. Rozdziały czysto wstępne (zmienne, pętle) pominąć — to poziom dawno opanowany.
|
||||||
|
[https://learn.arcade.academy/](https://learn.arcade.academy/)
|
||||||
|
* Ćwiczenie na koniec dnia: własna mini-gra typu „zbieraj monety, unikaj wrogów” z klasą bazową Enemy i dwoma typami przeciwników dziedziczącymi po niej.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dzień 3 — Platformówka, część 1
|
||||||
|
|
||||||
|
* Oficjalny tutorial platformówki (dokumentacja Arcade, zgodny z aktualnym API): gracz, fizyka, grawitacja, skoki, kamera, mapa poziomu. Przejść mniej więcej pierwszą połowę kroków.
|
||||||
|
[https://api.arcade.academy/en/latest/tutorials/platform_tutorial/index.html](https://api.arcade.academy/en/latest/tutorials/platform_tutorial/index.html)
|
||||||
|
* Równolegle jako wideo-wsparcie: PyCon 2019, Paul Craven — „Build Your Own 2D Platformer Game”.
|
||||||
|
[https://www.youtube.com/watch?v=Djtm1DzWSvo](https://www.youtube.com/watch?v=Djtm1DzWSvo)
|
||||||
|
* Darmowe assety (grafiki, dźwięki): [kenney.nl/assets](https://kenney.nl/assets)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dzień 4 — Platformówka, część 2 + szlify OOP
|
||||||
|
|
||||||
|
* Dokończyć tutorial platformówki: wrogowie, punkty, wiele poziomów, ekran końca gry.
|
||||||
|
* Corey Schafer, odcinek 6 (property, gettery/settery) — 20 min teorii i zastosować w kodzie platformówki (np. property „health” z ograniczeniem 0–100).
|
||||||
|
* Alternatywne ujęcie platformówki (tekst): Real Python — „Build a Platform Game in Python With Arcade” — uwaga: pisany pod starsze API, przy rozbieżnościach wygrywa oficjalna dokumentacja.
|
||||||
|
[https://realpython.com/platformer-python-arcade/](https://realpython.com/platformer-python-arcade/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dzień 5 — Własny projekt
|
||||||
|
|
||||||
|
* Cały dzień na grę własnego pomysłu od zera: remake Tetrisa / Snake'a / Flappy Birda albo coś autorskiego. Wymóg projektowy: minimum 3 własne klasy, w tym jedna hierarchia dziedziczenia — tu OOP naprawdę „klika”.
|
||||||
|
* Kopalnia gotowych przykładów do podglądania (kod źródłowy dziesiątek mini-gier):
|
||||||
|
[https://api.arcade.academy/en/latest/example_code/index.html](https://api.arcade.academy/en/latest/example_code/index.html)
|
||||||
|
* Na koniec: wrzucić projekt na GitHuba — dobre domkniecie tygodnia.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Uwagi
|
||||||
|
|
||||||
|
* Arcade 3.x zmieniło część API względem starszych tutoriali (np. `arcade.start_render()` → `self.clear()`). Przy rozbieżnościach zawsze wygrywa aktualna dokumentacja: [api.arcade.academy](https://api.arcade.academy).
|
||||||
|
* Materiały są po angielsku — przy poziomie olimpijskim to dobra okazja do angielskiego technicznego.
|
||||||
|
* Jeśli tempo okaże się za szybkie, dzień 5 można poświęcić na dokończenie platformówki, a własny projekt przenieść na później.
|
||||||
|
* Skill Tree w dokumentacji ([api.arcade.academy](https://api.arcade.academy)) pokazuje, co zgłębiać po tych 5 dniach: shadery, GUI, sieć itd.
|
||||||
|
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
# Wersja PL: Plan 5-dniowy (intensywny)
|
||||||
|
|
||||||
|
Teoria OOP po polsku • praktyka w Arcade po angielsku (z tłumaczeniem przeglądarki) • ok. 6–8 h dziennie
|
||||||
|
|
||||||
|
Zasada: teoria OOP w małych dawkach rano — po polsku. Resztę dnia kodowanie w Arcade — tutoriale są po angielsku, ale tekstowe strony świetnie działają z tłumaczeniem przeglądarki. Przed startem: `pip install arcade` (Python 3.9+).
|
||||||
|
|
||||||
|
## Dzień 1 — Podstawy OOP (PL) + pierwsze okno
|
||||||
|
|
||||||
|
**Rano (2–3 h): teoria po polsku**
|
||||||
|
* Kacper Sieradziński — „Kurs programowania obiektowego: Podstawy, Metody specjalne, Dziedziczenie w Pythonie” (YouTube, PL): obejrzeć część o podstawach — klasy, obiekty, atrybuty, metody, konstruktor, self. Oglądać z pauzami i przepisywać kod.
|
||||||
|
[https://www.youtube.com/watch?v=XYmt6foUCFI](https://www.youtube.com/watch?v=XYmt6foUCFI)
|
||||||
|
* Kodologia — „Python. Programowanie obiektowe” (PL, interaktywny, kodowanie w przeglądarce, 22 automatycznie sprawdzane zadania): zrobić pierwsze rozdziały jako ćwiczenia.
|
||||||
|
[https://kodologia.pl/kursy/python-programowanie-obiektowe](https://kodologia.pl/kursy/python-programowanie-obiektowe)
|
||||||
|
|
||||||
|
**Po południu (4–5 h): praktyka w Arcade**
|
||||||
|
* Real Python — „Arcade: A Primer on the Python Game Framework” (ang., czytać z tłumaczeniem przeglądarki): okno, rysowanie, sprite'y, pętla gry, klawiatura. Przejść całość, potem zmodyfikować grę po swojemu.
|
||||||
|
[https://realpython.com/arcade-python-game-framework/](https://realpython.com/arcade-python-game-framework/)
|
||||||
|
* Alternatywnie/pomocniczo: polska (maszynowo tłumaczona) wersja tego primera:
|
||||||
|
[https://pl.python-3.com/?p=333](https://pl.python-3.com/?p=333)
|
||||||
|
|
||||||
|
## Dzień 2 — Dziedziczenie (PL) + sprite'y, kolizje, zdarzenia
|
||||||
|
|
||||||
|
**Rano (1,5–2 h): teoria po polsku**
|
||||||
|
* Sieradziński — część o dziedziczeniu i metodach specjalnych (dunder methods) z tego samego filmu; do tego odpowiednie rozdziały Kodologii (dziedziczenie) jako zadania.
|
||||||
|
|
||||||
|
**Po południu (5–6 h): praktyka w Arcade**
|
||||||
|
* Wybrane rozdziały kursu autora biblioteki Arcade Academy — Learn Python (ang., dobrze działa z tłumaczeniem przeglądarki): sprite'y, kolizje, poruszanie, dźwięki.
|
||||||
|
[https://learn.arcade.academy/](https://learn.arcade.academy/)
|
||||||
|
* Ćwiczenie na koniec dnia: mini-gra „zbieraj monety, unikaj wrogów” z klasą bazową Enemy i dwoma typami przeciwników dziedziczącymi po niej.
|
||||||
|
|
||||||
|
## Dzień 3 — Platformówka, część 1
|
||||||
|
|
||||||
|
* Oficjalny tutorial platformówki (dokumentacja Arcade, ang. + tłumaczenie przeglądarki; zgodny z aktualnym API): gracz, fizyka, grawitacja, skoki, kamera, mapa poziomu. Pierwsza połowa kroków.
|
||||||
|
[https://api.arcade.academy/en/latest/tutorials/platform_tutorial/index.html](https://api.arcade.academy/en/latest/tutorials/platform_tutorial/index.html)
|
||||||
|
* Wideo-wsparcie: PyCon 2019, Paul Craven — „Build Your Own 2D Platformer Game” (YouTube; włączyć automatyczne polskie napisy).
|
||||||
|
[https://www.youtube.com/watch?v=Djtm1DzWSvo](https://www.youtube.com/watch?v=Djtm1DzWSvo)
|
||||||
|
* Darmowe assety: [kenney.nl/assets](https://kenney.nl/assets)
|
||||||
|
|
||||||
|
## Dzień 4 — Platformówka, część 2 + szlify OOP (PL)
|
||||||
|
|
||||||
|
* Dokończyć tutorial platformówki: wrogowie, punkty, wiele poziomów, ekran końca gry.
|
||||||
|
* Teoria PL (30–45 min): rozdziały Kodologii o hermetyzacji i property (dekorator @property, gettery/settery) — i od razu zastosować w platformówce.
|
||||||
|
* Alternatywne ujęcie platformówki (ang., tekst): Real Python — „Build a Platform Game in Python With Arcade” — pisany pod starsze API.
|
||||||
|
[https://realpython.com/platformer-python-arcade/](https://realpython.com/platformer-python-arcade/)
|
||||||
|
|
||||||
|
## Dzień 5 — Własny projekt
|
||||||
|
|
||||||
|
* Cały dzień na grę własnego pomysłu od zera. Wymóg projektowy: minimum 3 własne klasy, w tym jedna hierarchia dziedziczenia.
|
||||||
|
* Kopalnia przykładów: [https://api.arcade.academy/en/latest/example_code/index.html](https://api.arcade.academy/en/latest/example_code/index.html)
|
||||||
|
* Na koniec: wrzucić projekt na GitHuba.
|
||||||
|
|
||||||
|
## Uwagi (PL)
|
||||||
|
|
||||||
|
* Płatna alternatywa PL dla teorii: „Programowanie obiektowe w Python” (strefakursow.pl) albo kurs Karola Kurka „50 zadań praktycznych z programowania obiektowego” (Videopoint/Helion).
|
||||||
|
* Arcade 3.x API różni się od wersji 2.x. Zawsze sprawdzaj [api.arcade.academy](https://api.arcade.academy).
|
||||||
|
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
# Inne dokumenty
|
||||||
|
|
||||||
|
## Wypowiedzenie umowy ubezpieczenia OC (z wyop.pdf)
|
||||||
|
*Uwaga: Ten dokument zawiera dane osobowe i dotyczy wypowiedzenia polisy OC dla pojazdu Mazda 6 (GA431JA) w InterRisk TU S.A. z dnia 07.06.2026.*
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
arcade>=3.0
|
||||||
Loading…
Reference in a new issue