259 lines
9.9 KiB
Python
259 lines
9.9 KiB
Python
|
|
"""
|
||
|
|
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 ją 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ż są 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 są 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()
|