# Dni 3–4 — platformówka wg oficjalnego tutoriala Podstawa: **Simple Platformer** — 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 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` |