136 lines
4.8 KiB
Markdown
136 lines
4.8 KiB
Markdown
|
|
# 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ą)
|