""" 3.5 Вставка рисунков. Управление рисунков https://stepik.org/lesson/937437/step/2?unit=943412 Задача: Попробуйте сделать управление объектом, который есть не просто прямоугольник, а, например, рисунок. Зависимости: pygame pillow """ import os import shutil import tempfile import urllib.request from abc import ABC, abstractmethod from contextlib import contextmanager from typing import NamedTuple, Optional import PIL import pygame from PIL import ImageDraw def download_asset(asset, path): """ Загрузка картинок из репозитория """ prefix = "https://gitea.b4tman.ru/temp/py_stepik/raw/branch/master/assets/" print("Качаю картинку", asset, end=" ... ", flush=True) try: urllib.request.urlretrieve(prefix + asset, path) except Exception: print("не смог :(") return False print("скачал!") return True def make_stub_image(path, name): """Создание пустой картинки, на случай если скачать не получилось""" img = PIL.Image.new("RGBA", (200, 200)) draw = ImageDraw.Draw(img) draw.rectangle([(50, 50), (150, 150)], outline="black", width=2) draw.line((50, 50, 150, 150), fill="red", width=2) draw.line((50, 150, 150, 50), fill="red", width=2) draw.text((50, 170), name, fill="blue") img.save(path) @contextmanager def get_assets(names): """Получение соответствия с расположением файлов картинок Размер картинок нужно менять поэтому они всегда сохраняются во временные файлы. """ assets_dir = "assets" files = {} # поиск файлов (загрузка если их нет) и создание временных for asset in names: _, ext = os.path.splitext(asset) temppath = tempfile.mktemp(suffix=ext) filepath = os.path.join(assets_dir, asset) if os.path.isfile(filepath): shutil.copyfile(filepath, temppath) else: if not download_asset(asset, temppath): make_stub_image(temppath, asset) files[asset] = temppath # передача управления yield files # очистка for _, filename in files.items(): try: os.remove(filename) except FileNotFoundError: pass class Coords(NamedTuple): """ Вспомогательный класс для упрощения работы с координатами """ x: int | float y: int | float def __add__(self, other): if isinstance(other, self.__class__): return self.__class__(self.x + other.x, self.y + other.y) if isinstance(other, (int, float)): return self.__class__(self.x + other, self.y + other) return NotImplemented def __sub__(self, other): if isinstance(other, self.__class__): return self.__class__(self.x - other.x, self.y - other.y) if isinstance(other, (int, float)): return self.__class__(self.x - other, self.y - other) return NotImplemented def __floordiv__(self, other): if isinstance(other, self.__class__): return self.__class__(self.x // other.x, self.y // other.y) if isinstance(other, (int, float)): return self.__class__(self.x // other, self.y // other) return NotImplemented def __mul__(self, other): if isinstance(other, self.__class__): return self.__class__(self.x * other.x, self.y * other.y) if isinstance(other, (int, float)): return self.__class__(self.x * other, self.y * other) return NotImplemented def transform(self, ref: "Coords"): return self * ref @classmethod def zero(cls): return cls(0, 0) def resize_img(assets: dict, name: str, sz: Coords): """ Изменение размера картинки и сохранение в файл """ img = PIL.Image.open(assets[name]) if img.size != sz: img = img.resize(sz) img.save(assets[name]) def choose_plural(amount, declensions): """Возвращает количество объектов в виде строки например 5 копеек, 1 копейка и т.д. """ a = amount % 100 i = 2 if 10 < a < 20: pass elif a % 10 == 1: i = 0 elif 1 < a % 10 < 5: i = 1 return f"{amount} {declensions[i]}" class GameObject(ABC): """обобщение игрового элемента""" coords = property( lambda self: self.get_coords(), lambda self, c: self.set_coords(c) ) def __init__( self, coords: Coords, parent: Optional["GameObject"] = None, assets: dict | None = None, ): self.parent = parent self._coords = coords self.assets = assets or (parent.assets if parent else None) self._surface = None @property def surface(self) -> pygame.Surface | None: return self._surface or (self.parent.surface if self.parent else None) def get_coords(self) -> Coords: return self._coords def set_coords(self, coords: Coords): if self.parent: if self.parent.surface: if not ( coords.x >= 0 and coords.x < self.parent.surface.get_width() and coords.y >= 0 and coords.y < self.parent.surface.get_height() ): return self._coords = coords @abstractmethod def draw(self): pass @abstractmethod def handle_event(self, event: pygame.event.Event): pass class Hero(GameObject): """объект главного героя""" def __init__( self, coords: Coords, parent: GameObject, assets: dict | None = None, ): super().__init__(coords, parent, assets) screen_sz = Coords(*parent.surface.get_size()) ghost_sz = screen_sz // 10 resize_img(self.assets, "ghost.png", ghost_sz) self._surface = pygame.image.load(self.assets["ghost.png"]) self.rect = self.surface.get_rect() self.coords = coords def set_coords(self, coords: Coords): super().set_coords(coords) self.rect.topleft = self.coords def draw(self): self.parent.surface.blit(self.surface, self.rect) def handle_event(self, event: pygame.event.Event): delta = 10 if event.type == pygame.KEYDOWN: match event.key: case pygame.K_UP | pygame.K_w: self.coords += Coords(0, -1) * delta case pygame.K_DOWN | pygame.K_s: self.coords += Coords(0, 1) * delta case pygame.K_LEFT | pygame.K_a: self.coords += Coords(-1, 0) * delta case pygame.K_RIGHT | pygame.K_d: self.coords += Coords(1, 0) * delta class Scene(GameObject): """основной игровой объект""" # кнопки для выхода из игры exit_keys = (pygame.K_ESCAPE, pygame.K_q) def __init__(self, assets: dict, sz: Coords): super().__init__(Coords.zero(), None, assets) self._surface = pygame.display.set_mode(sz) self.hero = Hero(Coords(100, 100), self) resize_img(assets, "bg1k.png", sz) self.background = pygame.image.load(self.assets["bg1k.png"]) self.done = False def draw(self): if self.done: return self.surface.blit(self.background, self.coords) self.hero.draw() def handle_event(self, event: pygame.event.Event): if self.done: return if ( event.type == pygame.QUIT or event.type == pygame.KEYDOWN and event.key in self.exit_keys ): self.done = True if not self.done: self.hero.handle_event(event) def event_loop(self): while not self.done: event = pygame.event.wait() self.handle_event(event) self.draw() pygame.display.flip() def game(assets): pygame.init() screen_sz = Coords(1000, 1000) pygame.display.set_caption("Движение рисунка на Pygame") scene = Scene(assets, screen_sz) scene.event_loop() pygame.quit() def main(): assets = [ "bg1k.png", "ghost.png", ] with get_assets(assets) as assets: game(assets) main()