4.8 KiB
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 samopass, - każda klasa pochodna nadpisuje przynajmniej jedną metodę i woła w niej
super().<metoda>(...), - wszystkie obiekty tej hierarchii trzymasz w jednej
SpriteListi aktualizujesz jednymlista.update(delta_time)— bezif-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:
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).
@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:
from src.settings import SZEROKOSC, WYSOKOSC
from src.entities import Player, Chaser
Uruchamiasz z katalogu dzien5_projekt/:
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
- Najpierw narysuj na kartce, jakie masz klasy i co która trzyma.
- Zrób
settings.pyimain.py, które otwierają puste okno. Commit. - Dodaj
MenuViewiGameViewz pustą planszą. Commit. - Dodaj gracza, ruch. Commit.
- Dodaj hierarchię wrogów. Commit.
- Dodaj
property, kolizje, punkty,GameOverView. Commit. - Dopiero na końcu — dźwięki, grafika z https://kenney.nl/assets, efekty.
Checklista przed oddaniem
python main.pyuruchamia się bez błędów (PerformanceWarning: draw_text is an extremely slow functionto nie błąd — arcade sugeruje obiektyarcade.Textzamiastarcade.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()isuper() - wrogowie w jednej
SpriteList, aktualizowani bezif-a po typie propertyz działającą walidacją (pokaż, że odrzuca złą wartość)- 3 widoki
arcade.View, przełączanie działa w obie strony - kod w
src/,main.pyma 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.pysą OK — one się nie zmieniają)