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ą)
|