python-arcade-oo/dzien2/01_monety_start.py

259 lines
9.9 KiB
Python
Raw Normal View History

"""
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 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ż 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 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()