From 7e1f80636d36e68f2e46801ccc47a1728a3f5edb Mon Sep 17 00:00:00 2001 From: Tipragot Date: Tue, 24 Oct 2023 17:02:10 +0200 Subject: [PATCH 1/8] =?UTF-8?q?Ajout=20du=20syst=C3=A8me=20de=20fen=C3=AAt?= =?UTF-8?q?re=20et=20d'input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/math.py | 33 +++++++ engine/plugins/pygame.py | 208 +++++++++++++++++++++++++++++++++++++++ main.py | 34 +++---- requirements.txt | 3 +- 4 files changed, 258 insertions(+), 20 deletions(-) create mode 100644 engine/math.py create mode 100644 engine/plugins/pygame.py diff --git a/engine/math.py b/engine/math.py new file mode 100644 index 0000000..a767cf1 --- /dev/null +++ b/engine/math.py @@ -0,0 +1,33 @@ +""" +Définis des classes utiles. +""" + + +class Vec2: + """ + Un vecteur 2D + """ + + def __init__(self, x: float, y: float) -> None: + self.x = x + self.y = y + + def __add__(self, other: "Vec2") -> "Vec2": + return Vec2(self.x + other.x, self.y + other.y) + + def __sub__(self, other: "Vec2") -> "Vec2": + return Vec2(self.x - other.x, self.y - other.y) + + def __mul__(self, other: "Vec2") -> "Vec2": + return Vec2(self.x * other.x, self.y * other.y) + + def __div__(self, other: "Vec2") -> "Vec2": + return Vec2(self.x / other.x, self.y / other.y) + + def __eq__(self, other: object) -> bool: + if isinstance(other, Vec2): + return self.x == other.x and self.y == other.y + return False + + def __repr__(self) -> str: + return f"Vec2({self.x}, {self.y})" diff --git a/engine/plugins/pygame.py b/engine/plugins/pygame.py new file mode 100644 index 0000000..0b0806d --- /dev/null +++ b/engine/plugins/pygame.py @@ -0,0 +1,208 @@ +""" +Définit un plugin qui gère les évenements pygame. +""" + +from engine import * +import pygame + +from engine.math import Vec2 + + +class PygamePlugin(Plugin): + """ + Plugin qui gère les évenements pygame. + """ + + @staticmethod + def _initialize_pygame(world: World) -> None: + pygame.init() + + # Initialisation des ressources + world.set(Display(pygame.display.set_mode((640, 480)))) + world.set(Keyboard(set(), set(), set())) + world.set(Mouse(set(), set(), set(), Vec2(0.0, 0.0))) + + @staticmethod + def _check_events(world: World) -> None: + keys = world[Keyboard]._keys.copy() + keys_pressed: set[str] = set() + keys_released: set[str] = set() + + buttons = world[Mouse]._buttons.copy() + buttons_pressed: set[int] = set() + buttons_released: set[int] = set() + mouse_position = world[Mouse]._position + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + world[Game].stop() + elif event.type == pygame.KEYDOWN: + key_name = pygame.key.name(event.key) + keys.add(key_name) + keys_pressed.add(key_name) + elif event.type == pygame.KEYUP: + key_name = pygame.key.name(event.key) + keys.remove(key_name) + keys_released.add(key_name) + elif event.type == pygame.MOUSEBUTTONDOWN: + buttons.add(event.button) + buttons_pressed.add(event.button) + elif event.type == pygame.MOUSEBUTTONUP: + buttons.remove(event.button) + buttons_released.add(event.button) + elif event.type == pygame.MOUSEMOTION: + mouse_position = Vec2(float(event.pos[0]), float(event.pos[1])) + + world.set(Keyboard(keys, keys_pressed, keys_released)) + world.set(Mouse(buttons, buttons_pressed, buttons_released, mouse_position)) + + @staticmethod + def _terminate_pygame(world: World) -> None: + pygame.quit() + + def apply(self, game: Game) -> None: + """ + Applique le plugin a un jeu. + + Paramètres: + game: Le jeu auquel appliquer le plugin. + """ + game.add_startup_tasks(self._initialize_pygame) + game.add_pre_update_tasks(self._check_events) + game.add_shutdown_tasks(self._terminate_pygame) + + +class Display: + """ + Ressource qui represente la fenetre du jeu. + """ + + def __init__(self, surface: pygame.Surface) -> None: + self.surface = surface + + @property + def width(self) -> int: + """ + Renvoie la largeur de la fenetre + + Retourne: + La largeur de la fenetre + """ + return self.surface.get_width() + + @property + def height(self) -> int: + """ + Renvoie la hauteur de la fenetre + + Retourne: + La hauteur de la fenetre + """ + return self.surface.get_height() + + +class Keyboard: + """ + Ressource qui représente les entrées utilisateurs sur le clavier à la frame actuelle. + """ + + def __init__(self, keys: set[str], pressed: set[str], released: set[str]) -> None: + self._keys = keys + self._pressed = pressed + self._released = released + + def is_key_pressed(self, key_name: str) -> bool: + """ + Renvoie True si la touche *key_name* a commencé a être appuyée pendant la frame actuelle. + + Paramètres: + key_name: Le nom de la touche à tester. + + Retourne: + True si la touche *key_name* a commencé a être appuyée pendant la frame actuelle. + """ + return key_name in self._pressed + + def is_key(self, key_name: str) -> bool: + """ + Renvoie True si la touche *key_name* est actuellement appuyée. + + Paramètres: + key_name: Le nom de la touche à tester. + + Retourne: + True si la touche *key_name* est actuellement appuyée. + """ + return key_name in self._keys + + def is_key_released(self, key_name: str) -> bool: + """ + Renvoie True si la touche *key_name* a été relachée pendant la frame actuelle. + + Paramètres: + key_name: Le nom de la touche à tester. + + Retourne: + True si la touche *key_name* a été relachée pendant la frame actuelle. + """ + return key_name in self._released + + +class Mouse: + """ + Ressource qui représente l'état de la souris à la frame actuelle. + """ + + def __init__( + self, buttons: set[int], pressed: set[int], released: set[int], position: Vec2 + ) -> None: + self._buttons = buttons + self._pressed = pressed + self._released = released + self._position = position + + def is_button_pressed(self, button: int) -> bool: + """ + Renvoie True si le bouton *button* a commencé a être appuyée pendant la frame actuelle. + + Paramètres: + button: Le numéro du bouton à tester. + + Retourne: + True si le bouton *button* a commencé a être appuyée pendant la frame actuelle. + """ + return button in self._pressed + + def is_button(self, button: int) -> bool: + """ + Renvoie True si le bouton *button* est actuellement appuyé. + + Paramètres: + button: Le numéro du bouton à tester. + + Retourne: + True si le bouton *button* est actuellement appuyé. + """ + return button in self._buttons + + def is_button_released(self, button: int) -> bool: + """ + Renvoie True si le bouton *button* a été relaché pendant la frame actuelle. + + Paramètres: + button: Le numéro du bouton à tester. + + Retourne: + True si le bouton *button* aLongrightarrow relaché pendant la frame actuelle. + """ + return button in self._released + + @property + def position(self) -> Vec2: + """ + Renvoie la position de la souris. + + Retourne: + La position de la souris. + """ + return self._position diff --git a/main.py b/main.py index c9814a8..f02dab0 100644 --- a/main.py +++ b/main.py @@ -4,32 +4,28 @@ Ceci est un exemple de comment l'on peut utiliser le moteur du jeu. from engine import * -from engine.plugins.timing import TimePlugin, Time +from engine.plugins.timing import TimePlugin +from engine.plugins.pygame import Keyboard, Mouse, PygamePlugin # Initialisation -game = Game(TimePlugin()) +game = Game(TimePlugin(), PygamePlugin()) -# Ajout de tache au démarage (l'ordre d'ajout est important) -game.add_startup_tasks(lambda world: print("Hello first")) -game.add_startup_tasks(lambda world: print("Hello second")) -game.add_startup_tasks(lambda world: print("Hello third")) -game.add_startup_tasks(lambda world: print("Hello last")) -# Ajoute de tache au mise à jour (malgré le world[Game].stop(), la boucle termine les taches suivantes) -game.add_pre_update_tasks(lambda world: print("Pre Update")) -game.add_pre_update_tasks(lambda world: print(world[Time])) -game.add_update_tasks(lambda world: world[Game].stop()) -game.add_post_update_tasks(lambda world: print("Post Update")) +# On créer une tache pour tester si les plugins fonctionnent +def salutations(world: World) -> None: + """ + Affiche "Bonjour" si la touche B est pressé et "Au revoir" si la touche B est relachée. + """ + if world[Keyboard].is_key_pressed("b"): + print("Bonjour") -# Ajout de tache au rendu -game.add_render_tasks(lambda world: print("Render task 1")) -game.add_render_tasks(lambda world: print("Render task 2")) -game.add_render_tasks(lambda world: print("Render task 3")) + if world[Keyboard].is_key_released("b"): + print("Au revoir") -# Ajout de tache à la fin -game.add_shutdown_tasks(lambda world: print("Bye first")) -game.add_shutdown_tasks(lambda world: print("Bye second")) + +# On ajoute la tache de test +game.add_update_tasks(salutations) # On lance la boucle game.run() diff --git a/requirements.txt b/requirements.txt index 8120074..969ddee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ mypy -pylint \ No newline at end of file +pylint +pygame \ No newline at end of file From 7a21731bfa213104f416d28d735d25d701b40aec Mon Sep 17 00:00:00 2001 From: Tipragot Date: Tue, 24 Oct 2023 17:08:39 +0200 Subject: [PATCH 2/8] Changement de la methode de mise a jour des inputs --- engine/plugins/pygame.py | 58 +++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/engine/plugins/pygame.py b/engine/plugins/pygame.py index 0b0806d..dd009b6 100644 --- a/engine/plugins/pygame.py +++ b/engine/plugins/pygame.py @@ -18,43 +18,37 @@ class PygamePlugin(Plugin): pygame.init() # Initialisation des ressources - world.set(Display(pygame.display.set_mode((640, 480)))) - world.set(Keyboard(set(), set(), set())) - world.set(Mouse(set(), set(), set(), Vec2(0.0, 0.0))) + world.set(Display(pygame.display.set_mode((640, 480))), Keyboard(), Mouse()) @staticmethod def _check_events(world: World) -> None: - keys = world[Keyboard]._keys.copy() - keys_pressed: set[str] = set() - keys_released: set[str] = set() + keyboard = world[Keyboard] + keyboard._pressed.clear() + keyboard._released.clear() - buttons = world[Mouse]._buttons.copy() - buttons_pressed: set[int] = set() - buttons_released: set[int] = set() - mouse_position = world[Mouse]._position + mouse = world[Mouse] + mouse._pressed.clear() + mouse._released.clear() for event in pygame.event.get(): if event.type == pygame.QUIT: world[Game].stop() elif event.type == pygame.KEYDOWN: key_name = pygame.key.name(event.key) - keys.add(key_name) - keys_pressed.add(key_name) + keyboard._keys.add(key_name) + keyboard._pressed.add(key_name) elif event.type == pygame.KEYUP: key_name = pygame.key.name(event.key) - keys.remove(key_name) - keys_released.add(key_name) + keyboard._keys.remove(key_name) + keyboard._released.add(key_name) elif event.type == pygame.MOUSEBUTTONDOWN: - buttons.add(event.button) - buttons_pressed.add(event.button) + mouse._buttons.add(event.button) + mouse._pressed.add(event.button) elif event.type == pygame.MOUSEBUTTONUP: - buttons.remove(event.button) - buttons_released.add(event.button) + mouse._buttons.remove(event.button) + mouse._released.add(event.button) elif event.type == pygame.MOUSEMOTION: - mouse_position = Vec2(float(event.pos[0]), float(event.pos[1])) - - world.set(Keyboard(keys, keys_pressed, keys_released)) - world.set(Mouse(buttons, buttons_pressed, buttons_released, mouse_position)) + mouse._position = Vec2(float(event.pos[0]), float(event.pos[1])) @staticmethod def _terminate_pygame(world: World) -> None: @@ -106,10 +100,10 @@ class Keyboard: Ressource qui représente les entrées utilisateurs sur le clavier à la frame actuelle. """ - def __init__(self, keys: set[str], pressed: set[str], released: set[str]) -> None: - self._keys = keys - self._pressed = pressed - self._released = released + def __init__(self) -> None: + self._keys: set[str] = set() + self._pressed: set[str] = set() + self._released: set[str] = set() def is_key_pressed(self, key_name: str) -> bool: """ @@ -153,13 +147,11 @@ class Mouse: Ressource qui représente l'état de la souris à la frame actuelle. """ - def __init__( - self, buttons: set[int], pressed: set[int], released: set[int], position: Vec2 - ) -> None: - self._buttons = buttons - self._pressed = pressed - self._released = released - self._position = position + def __init__(self) -> None: + self._buttons: set[int] = set() + self._pressed: set[int] = set() + self._released: set[int] = set() + self._position: Vec2 = Vec2(0.0, 0.0) def is_button_pressed(self, button: int) -> bool: """ From b07814c440301b8d946c454f873b2b8e7c9f1b30 Mon Sep 17 00:00:00 2001 From: Tipragot Date: Tue, 24 Oct 2023 17:10:25 +0200 Subject: [PATCH 3/8] Make public keys and buttons --- engine/plugins/pygame.py | 62 +++++++++++++++++----------------------- engine/plugins/sprite.py | 30 +++++++++++++++++++ 2 files changed, 56 insertions(+), 36 deletions(-) create mode 100644 engine/plugins/sprite.py diff --git a/engine/plugins/pygame.py b/engine/plugins/pygame.py index dd009b6..d07e5f1 100644 --- a/engine/plugins/pygame.py +++ b/engine/plugins/pygame.py @@ -23,32 +23,32 @@ class PygamePlugin(Plugin): @staticmethod def _check_events(world: World) -> None: keyboard = world[Keyboard] - keyboard._pressed.clear() - keyboard._released.clear() + keyboard.pressed.clear() + keyboard.released.clear() mouse = world[Mouse] - mouse._pressed.clear() - mouse._released.clear() + mouse.pressed.clear() + mouse.released.clear() for event in pygame.event.get(): if event.type == pygame.QUIT: world[Game].stop() elif event.type == pygame.KEYDOWN: key_name = pygame.key.name(event.key) - keyboard._keys.add(key_name) - keyboard._pressed.add(key_name) + keyboard.keys.add(key_name) + keyboard.pressed.add(key_name) elif event.type == pygame.KEYUP: key_name = pygame.key.name(event.key) - keyboard._keys.remove(key_name) - keyboard._released.add(key_name) + keyboard.keys.remove(key_name) + keyboard.released.add(key_name) elif event.type == pygame.MOUSEBUTTONDOWN: - mouse._buttons.add(event.button) - mouse._pressed.add(event.button) + mouse.buttons.add(event.button) + mouse.pressed.add(event.button) elif event.type == pygame.MOUSEBUTTONUP: - mouse._buttons.remove(event.button) - mouse._released.add(event.button) + mouse.buttons.remove(event.button) + mouse.released.add(event.button) elif event.type == pygame.MOUSEMOTION: - mouse._position = Vec2(float(event.pos[0]), float(event.pos[1])) + mouse.position = Vec2(float(event.pos[0]), float(event.pos[1])) @staticmethod def _terminate_pygame(world: World) -> None: @@ -101,9 +101,9 @@ class Keyboard: """ def __init__(self) -> None: - self._keys: set[str] = set() - self._pressed: set[str] = set() - self._released: set[str] = set() + self.keys: set[str] = set() + self.pressed: set[str] = set() + self.released: set[str] = set() def is_key_pressed(self, key_name: str) -> bool: """ @@ -115,7 +115,7 @@ class Keyboard: Retourne: True si la touche *key_name* a commencé a être appuyée pendant la frame actuelle. """ - return key_name in self._pressed + return key_name in self.pressed def is_key(self, key_name: str) -> bool: """ @@ -127,7 +127,7 @@ class Keyboard: Retourne: True si la touche *key_name* est actuellement appuyée. """ - return key_name in self._keys + return key_name in self.keys def is_key_released(self, key_name: str) -> bool: """ @@ -139,7 +139,7 @@ class Keyboard: Retourne: True si la touche *key_name* a été relachée pendant la frame actuelle. """ - return key_name in self._released + return key_name in self.released class Mouse: @@ -148,10 +148,10 @@ class Mouse: """ def __init__(self) -> None: - self._buttons: set[int] = set() - self._pressed: set[int] = set() - self._released: set[int] = set() - self._position: Vec2 = Vec2(0.0, 0.0) + self.buttons: set[int] = set() + self.pressed: set[int] = set() + self.released: set[int] = set() + self.position: Vec2 = Vec2(0.0, 0.0) def is_button_pressed(self, button: int) -> bool: """ @@ -163,7 +163,7 @@ class Mouse: Retourne: True si le bouton *button* a commencé a être appuyée pendant la frame actuelle. """ - return button in self._pressed + return button in self.pressed def is_button(self, button: int) -> bool: """ @@ -175,7 +175,7 @@ class Mouse: Retourne: True si le bouton *button* est actuellement appuyé. """ - return button in self._buttons + return button in self.buttons def is_button_released(self, button: int) -> bool: """ @@ -187,14 +187,4 @@ class Mouse: Retourne: True si le bouton *button* aLongrightarrow relaché pendant la frame actuelle. """ - return button in self._released - - @property - def position(self) -> Vec2: - """ - Renvoie la position de la souris. - - Retourne: - La position de la souris. - """ - return self._position + return button in self.released diff --git a/engine/plugins/sprite.py b/engine/plugins/sprite.py new file mode 100644 index 0000000..ef42a0b --- /dev/null +++ b/engine/plugins/sprite.py @@ -0,0 +1,30 @@ +""" +Définis un plugin permettant d'afficher des sprites a l'écran. +""" + +from engine import * + + +class SpritePlugin(Plugin): + """ + Plugin permettant d'afficher des sprites a l'écran. + """ + + def apply(self, game: Game) -> None: + """ + Applique le plugin a un jeu. + + Paramarters: + game: Le jeu auquel appliquer le plugin. + """ + pass + + +class Sprite: + """ + Composant qui represente un sprite. + """ + + def __init__(self, texture: str, order: int) -> None: + self._texture = texture + self._order = order From f56fe137fae462055b617b262817c65fd6d4a75d Mon Sep 17 00:00:00 2001 From: Tipragot Date: Wed, 25 Oct 2023 15:36:29 +0200 Subject: [PATCH 4/8] =?UTF-8?q?Am=C3=A9lioration=20des=20syst=C3=A8mes=20d?= =?UTF-8?q?e=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/__init__.py | 100 +++++++++++++++++++++++++++++++++++++++ engine/math.py | 62 +++++++++++++++++++----- engine/plugins/pygame.py | 51 ++++++++++++-------- engine/plugins/timing.py | 2 +- 4 files changed, 182 insertions(+), 33 deletions(-) diff --git a/engine/__init__.py b/engine/__init__.py index f2384c1..8dd071e 100644 --- a/engine/__init__.py +++ b/engine/__init__.py @@ -264,15 +264,29 @@ class Game: *plugins: Les plugins a ajouter au jeu. """ self._running = False + self._pre_startup_tasks: list[Callable[[World], None]] = [] self._startup_tasks: list[Callable[[World], None]] = [] + self._post_startup_tasks: list[Callable[[World], None]] = [] self._pre_update_tasks: list[Callable[[World], None]] = [] self._update_tasks: list[Callable[[World], None]] = [] self._post_update_tasks: list[Callable[[World], None]] = [] + self._pre_render_tasks: list[Callable[[World], None]] = [] self._render_tasks: list[Callable[[World], None]] = [] + self._post_render_tasks: list[Callable[[World], None]] = [] + self._pre_shutdown_tasks: list[Callable[[World], None]] = [] self._shutdown_tasks: list[Callable[[World], None]] = [] + self._post_shutdown_tasks: list[Callable[[World], None]] = [] for plugin in plugins: plugin.apply(self) + def add_pre_startup_tasks(self, *tasks: Callable[[World], None]) -> None: + """ + Ajoute des taches qui s'executeront en premier avant le démarrage du jeu. + """ + if self._running: + raise RuntimeError("Cannot add a task while the loop is running") + self._pre_startup_tasks.extend(tasks) + def add_startup_tasks(self, *tasks: Callable[[World], None]) -> None: """ Ajoute des taches qui s'executeront au démarrage du jeu. @@ -284,6 +298,14 @@ class Game: raise RuntimeError("Cannot add a task while the loop is running") self._startup_tasks.extend(tasks) + def add_post_startup_tasks(self, *tasks: Callable[[World], None]) -> None: + """ + Ajoute des taches qui s'executeront en dernier après le démarrage du jeu. + """ + if self._running: + raise RuntimeError("Cannot add a task while the loop is running") + self._post_startup_tasks.extend(tasks) + def add_pre_update_tasks(self, *tasks: Callable[[World], None]) -> None: """ Ajoute des taches qui s'executeront au debut de chaque mise à jour du jeu. @@ -317,6 +339,17 @@ class Game: raise RuntimeError("Cannot add a task while the loop is running") self._post_update_tasks.extend(tasks) + def add_pre_render_tasks(self, *tasks: Callable[[World], None]) -> None: + """ + Ajoute des taches qui s'executeront au début de chaque mise à jour du jeu pour le rendu. + + Paramètres: + *tasks: Les taches à ajouter. + """ + if self._running: + raise RuntimeError("Cannot add a task while the loop is running") + self._pre_render_tasks.extend(tasks) + def add_render_tasks(self, *tasks: Callable[[World], None]) -> None: """ Ajoute des taches qui s'executeront après chaque mise à jour du jeu pour le rendu. @@ -328,6 +361,25 @@ class Game: raise RuntimeError("Cannot add a task while the loop is running") self._render_tasks.extend(tasks) + def add_post_render_tasks(self, *tasks: Callable[[World], None]) -> None: + """ + Ajoute des taches qui s'executeront après chaque mise à jour du jeu pour le rendu. + + Paramètres: + *tasks: Les taches à ajouter. + """ + if self._running: + raise RuntimeError("Cannot add a task while the loop is running") + self._post_render_tasks.extend(tasks) + + def add_pre_shutdown_tasks(self, *tasks: Callable[[World], None]) -> None: + """ + Ajoute des taches qui s'executeront au début de la fin de la boucle de jeu. + """ + if self._running: + raise RuntimeError("Cannot add a task while the loop is running") + self._pre_shutdown_tasks.extend(tasks) + def add_shutdown_tasks(self, *tasks: Callable[[World], None]) -> None: """ Ajoute des taches qui s'executeront à la fin de la boucle de jeu. @@ -339,6 +391,14 @@ class Game: raise RuntimeError("Cannot add a task while the loop is running") self._shutdown_tasks.extend(tasks) + def add_post_shutdown_tasks(self, *tasks: Callable[[World], None]) -> None: + """ + Ajoute des taches qui s'executeront à la fin de la fin de la boucle de jeu. + """ + if self._running: + raise RuntimeError("Cannot add a task while the loop is running") + self._post_shutdown_tasks.extend(tasks) + def run(self, world: World = World()) -> World: """ Lance la boucle de jeu. @@ -351,6 +411,14 @@ class Game: world.set(self) world.apply() + # On execute les taches de pré initialisation du monde + for task in self._pre_startup_tasks: + try: + task(world) + except Exception as e: + error(f"Error during pre-startup task: {e}") + world.apply() + # On execute les taches d'initialisation du monde for task in self._startup_tasks: try: @@ -359,6 +427,14 @@ class Game: error(f"Error during startup task: {e}") world.apply() + # On execute les taches de post initialisation du monde + for task in self._post_startup_tasks: + try: + task(world) + except Exception as e: + error(f"Error during post-startup task: {e}") + world.apply() + while self._running: # On execute les taches de pré mise à jour du monde for task in self._pre_update_tasks: @@ -392,6 +468,22 @@ class Game: error(f"Error during render task: {e}") world.apply() + # On execute les taches de fin de rendu du jeu + for task in self._post_render_tasks: + try: + task(world) + except Exception as e: + error(f"Error during post-render task: {e}") + world.apply() + + # On execute les taches de pré fin de boucle + for task in self._pre_shutdown_tasks: + try: + task(world) + except Exception as e: + error(f"Error during pre-shutdown task: {e}") + world.apply() + # On exécute les taches de fin du monde for task in self._shutdown_tasks: try: @@ -400,6 +492,14 @@ class Game: error(f"Error during shutdown task: {e}") world.apply() + # On execute les taches de post fin de boucle + for task in self._post_shutdown_tasks: + try: + task(world) + except Exception as e: + error(f"Error during post-shutdown task: {e}") + world.apply() + # On retourne le monde return world diff --git a/engine/math.py b/engine/math.py index a767cf1..3aca6dc 100644 --- a/engine/math.py +++ b/engine/math.py @@ -3,31 +3,71 @@ Définis des classes utiles. """ +import math + + class Vec2: """ Un vecteur 2D """ - def __init__(self, x: float, y: float) -> None: - self.x = x - self.y = y + def __init__(self, x: float = 0, y: float = 0) -> None: + self.x = float(x) + self.y = float(y) - def __add__(self, other: "Vec2") -> "Vec2": - return Vec2(self.x + other.x, self.y + other.y) + def __add__(self, other: object) -> "Vec2": + if isinstance(other, Vec2): + return Vec2(self.x + other.x, self.y + other.y) + elif isinstance(other, float): + return Vec2(self.x + other, self.y + other) + return Vec2(float("nan"), float("nan")) - def __sub__(self, other: "Vec2") -> "Vec2": - return Vec2(self.x - other.x, self.y - other.y) + def __sub__(self, other: object) -> "Vec2": + if isinstance(other, Vec2): + return Vec2(self.x - other.x, self.y - other.y) + elif isinstance(other, float): + return Vec2(self.x - other, self.y - other) + return Vec2(float("nan"), float("nan")) - def __mul__(self, other: "Vec2") -> "Vec2": - return Vec2(self.x * other.x, self.y * other.y) + def __mul__(self, other: object) -> "Vec2": + if isinstance(other, Vec2): + return Vec2(self.x * other.x, self.y * other.y) + elif isinstance(other, float): + return Vec2(self.x * other, self.y * other) + return Vec2(float("nan"), float("nan")) - def __div__(self, other: "Vec2") -> "Vec2": - return Vec2(self.x / other.x, self.y / other.y) + def __truediv__(self, other: object) -> "Vec2": + if isinstance(other, Vec2): + return Vec2(self.x / other.x, self.y / other.y) + elif isinstance(other, float): + return Vec2(self.x / other, self.y / other) + return Vec2(float("nan"), float("nan")) def __eq__(self, other: object) -> bool: if isinstance(other, Vec2): return self.x == other.x and self.y == other.y return False + def __hash__(self) -> int: + return hash((self.x, self.y)) + + def __neg__(self) -> "Vec2": + return Vec2(-self.x, -self.y) + + @property + def length(self) -> float: + """ + Retourne la longueur du vecteur. + """ + return math.sqrt(self.x**2 + self.y**2) + + @property + def normalized(self) -> "Vec2": + """ + Retourne une version normalisé du vecteur. + """ + length = self.length + return Vec2(self.x / length, self.y / length) + def __repr__(self) -> str: return f"Vec2({self.x}, {self.y})" diff --git a/engine/plugins/pygame.py b/engine/plugins/pygame.py index d07e5f1..8211549 100644 --- a/engine/plugins/pygame.py +++ b/engine/plugins/pygame.py @@ -3,9 +3,8 @@ Définit un plugin qui gère les évenements pygame. """ from engine import * -import pygame - from engine.math import Vec2 +import pygame class PygamePlugin(Plugin): @@ -14,14 +13,20 @@ class PygamePlugin(Plugin): """ @staticmethod - def _initialize_pygame(world: World) -> None: + def _initialize(world: World) -> None: pygame.init() # Initialisation des ressources - world.set(Display(pygame.display.set_mode((640, 480))), Keyboard(), Mouse()) + world.set( + Display(pygame.display.set_mode((800, 600), pygame.RESIZABLE)), + Keyboard(), + Mouse(), + ) @staticmethod def _check_events(world: World) -> None: + world[Display].size = Vec2(*pygame.display.get_surface().get_size()) + keyboard = world[Keyboard] keyboard.pressed.clear() keyboard.released.clear() @@ -51,7 +56,16 @@ class PygamePlugin(Plugin): mouse.position = Vec2(float(event.pos[0]), float(event.pos[1])) @staticmethod - def _terminate_pygame(world: World) -> None: + def _flip_display(world: World) -> None: + """ + Met a jour le rendu de l'écran. + """ + display = world[Display] + pygame.display.flip() + pygame.display.get_surface().fill((0, 0, 0)) + + @staticmethod + def _terminate(world: World) -> None: pygame.quit() def apply(self, game: Game) -> None: @@ -61,9 +75,10 @@ class PygamePlugin(Plugin): Paramètres: game: Le jeu auquel appliquer le plugin. """ - game.add_startup_tasks(self._initialize_pygame) + game.add_pre_startup_tasks(self._initialize) game.add_pre_update_tasks(self._check_events) - game.add_shutdown_tasks(self._terminate_pygame) + game.add_post_render_tasks(self._flip_display) + game.add_post_shutdown_tasks(self._terminate) class Display: @@ -71,28 +86,22 @@ class Display: Ressource qui represente la fenetre du jeu. """ - def __init__(self, surface: pygame.Surface) -> None: - self.surface = surface + def __init__(self, display: pygame.Surface) -> None: + self.size: Vec2 = Vec2(*display.get_size()) @property - def width(self) -> int: + def width(self) -> float: """ - Renvoie la largeur de la fenetre - - Retourne: - La largeur de la fenetre + Retourne la largeur de la fenetre. """ - return self.surface.get_width() + return self.size.x @property - def height(self) -> int: + def height(self) -> float: """ - Renvoie la hauteur de la fenetre - - Retourne: - La hauteur de la fenetre + Retourne la hauteur de la fenetre. """ - return self.surface.get_height() + return self.size.y class Keyboard: diff --git a/engine/plugins/timing.py b/engine/plugins/timing.py index cadc4be..c1b2117 100644 --- a/engine/plugins/timing.py +++ b/engine/plugins/timing.py @@ -24,7 +24,7 @@ class TimePlugin(Plugin): """ Applique le plugin a un jeu. """ - game.add_startup_tasks(self._initialize_time) + game.add_pre_startup_tasks(self._initialize_time) game.add_pre_update_tasks(self._update_time) From 8e20b61e2f1d75c6005499b47ee7a8a4148d39cc Mon Sep 17 00:00:00 2001 From: Tipragot Date: Wed, 25 Oct 2023 17:37:15 +0200 Subject: [PATCH 5/8] =?UTF-8?q?Ajout=20d'un=20syst=C3=A8me=20de=20rendu=20?= =?UTF-8?q?de=20sprites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/__init__.py | 18 ++++++++ engine/math.py | 42 +++++++++++++++--- engine/plugins/pygame.py | 63 +++++++++++++++++---------- engine/plugins/render.py | 92 +++++++++++++++++++++++++++++++++++++++ engine/plugins/sprite.py | 30 ------------- main.py | 47 ++++++++++++++++++-- textures/test.png | Bin 0 -> 7006 bytes textures/test2.png | Bin 0 -> 11652 bytes 8 files changed, 230 insertions(+), 62 deletions(-) create mode 100644 engine/plugins/render.py delete mode 100644 engine/plugins/sprite.py create mode 100644 textures/test.png create mode 100644 textures/test2.png diff --git a/engine/__init__.py b/engine/__init__.py index 8dd071e..05f7af2 100644 --- a/engine/__init__.py +++ b/engine/__init__.py @@ -147,6 +147,15 @@ class World: resource: _T = self._resources[resource_type] # type: ignore[assignment] return resource + def __delitem__(self, resource_type: Type[_T]) -> None: + """ + Supprime la ressource de type *resource_type. + + Paramètres: + resource_type: Le type de ressource. + """ + self.remove(resource_type) + def __contains__(self, *resource_types: Type[_T]) -> bool: """ Renvoie True si le monde contient toutes les ressources de *resource_types. @@ -219,6 +228,15 @@ class Entity: """ return self._components[component_type] + def __delitem__(self, component_type: Type[_T]) -> None: + """ + Supprime le composant de type *component_type. + + Paramètres: + component_type: Le type du composant. + """ + self.remove(component_type) + def __contains__(self, *component_types: Type[_T]) -> bool: """ Renvoie True si l'entité contient tous les composants de *component_types. diff --git a/engine/math.py b/engine/math.py index 3aca6dc..9098685 100644 --- a/engine/math.py +++ b/engine/math.py @@ -3,6 +3,7 @@ Définis des classes utiles. """ +from typing import SupportsFloat, Union, overload import math @@ -11,37 +12,64 @@ class Vec2: Un vecteur 2D """ - def __init__(self, x: float = 0, y: float = 0) -> None: - self.x = float(x) - self.y = float(y) + def __init__(self, *args: Union[SupportsFloat, "Vec2"]) -> None: + if ( + len(args) == 2 + and isinstance(args[0], SupportsFloat) + and isinstance(args[1], SupportsFloat) + ): + self.x = float(args[0]) + self.y = float(args[1]) + elif len(args) == 1: + if isinstance(args[0], Vec2): + self.x = args[0].x + self.y = args[0].y + elif isinstance(args[0], SupportsFloat): + self.x = float(args[0]) + self.y = float(args[0]) + else: + raise ValueError("Invalid argument") + elif len(args) == 0: + self.x = 0.0 + self.y = 0.0 + else: + raise ValueError("Invalid number of arguments") def __add__(self, other: object) -> "Vec2": if isinstance(other, Vec2): return Vec2(self.x + other.x, self.y + other.y) elif isinstance(other, float): return Vec2(self.x + other, self.y + other) - return Vec2(float("nan"), float("nan")) + raise ValueError( + f"Unsupported operand type(s) for +: 'Vec2' and '{type(other)}'" + ) def __sub__(self, other: object) -> "Vec2": if isinstance(other, Vec2): return Vec2(self.x - other.x, self.y - other.y) elif isinstance(other, float): return Vec2(self.x - other, self.y - other) - return Vec2(float("nan"), float("nan")) + raise ValueError( + f"Unsupported operand type(s) for -: 'Vec2' and '{type(other)}'" + ) def __mul__(self, other: object) -> "Vec2": if isinstance(other, Vec2): return Vec2(self.x * other.x, self.y * other.y) elif isinstance(other, float): return Vec2(self.x * other, self.y * other) - return Vec2(float("nan"), float("nan")) + raise ValueError( + f"Unsupported operand type(s) for *: 'Vec2' and '{type(other)}'" + ) def __truediv__(self, other: object) -> "Vec2": if isinstance(other, Vec2): return Vec2(self.x / other.x, self.y / other.y) elif isinstance(other, float): return Vec2(self.x / other, self.y / other) - return Vec2(float("nan"), float("nan")) + raise ValueError( + f"Unsupported operand type(s) for /: 'Vec2' and '{type(other)}'" + ) def __eq__(self, other: object) -> bool: if isinstance(other, Vec2): diff --git a/engine/plugins/pygame.py b/engine/plugins/pygame.py index 8211549..0097e1e 100644 --- a/engine/plugins/pygame.py +++ b/engine/plugins/pygame.py @@ -12,21 +12,34 @@ class PygamePlugin(Plugin): Plugin qui gère les évenements pygame. """ + @staticmethod + def _find_surface_rect() -> tuple[float, float, float, float]: + width, height = pygame.display.get_surface().get_size() + if width / height < Display.RATIO: + target_height = width * (Display.INVERT_RATIO) + offset = (height - target_height) / 2 + rect = (0.0, offset, float(width), target_height) + else: + target_width = height * (Display.RATIO) + offset = (width - target_width) / 2 + rect = (offset, 0.0, target_width, float(height)) + return rect + @staticmethod def _initialize(world: World) -> None: + # Initialisation de pygame pygame.init() + pygame.display.set_mode((800, 600), pygame.RESIZABLE | pygame.SCALED) # Initialisation des ressources world.set( - Display(pygame.display.set_mode((800, 600), pygame.RESIZABLE)), + Display(), Keyboard(), Mouse(), ) @staticmethod def _check_events(world: World) -> None: - world[Display].size = Vec2(*pygame.display.get_surface().get_size()) - keyboard = world[Keyboard] keyboard.pressed.clear() keyboard.released.clear() @@ -40,10 +53,14 @@ class PygamePlugin(Plugin): world[Game].stop() elif event.type == pygame.KEYDOWN: key_name = pygame.key.name(event.key) + if key_name == "f11": + pygame.display.toggle_fullscreen() keyboard.keys.add(key_name) keyboard.pressed.add(key_name) elif event.type == pygame.KEYUP: key_name = pygame.key.name(event.key) + if key_name == "f11": + continue keyboard.keys.remove(key_name) keyboard.released.add(key_name) elif event.type == pygame.MOUSEBUTTONDOWN: @@ -53,16 +70,27 @@ class PygamePlugin(Plugin): mouse.buttons.remove(event.button) mouse.released.add(event.button) elif event.type == pygame.MOUSEMOTION: - mouse.position = Vec2(float(event.pos[0]), float(event.pos[1])) + rect = PygamePlugin._find_surface_rect() + mouse.position = Vec2( + ((event.pos[0] - rect[0]) / rect[2]) * Display.WIDTH, + ((event.pos[1] - rect[1]) / rect[3]) * Display.HEIGHT, + ) @staticmethod - def _flip_display(world: World) -> None: + def _update_display(world: World) -> None: """ Met a jour le rendu de l'écran. """ display = world[Display] + rect = PygamePlugin._find_surface_rect() + pygame.transform.set_smoothscale_backend("MMX") + pygame.transform.smoothscale( + display._surface, + (rect[2], rect[3]), + pygame.display.get_surface().subsurface(rect), + ) pygame.display.flip() - pygame.display.get_surface().fill((0, 0, 0)) + display._surface.fill((0, 0, 0)) @staticmethod def _terminate(world: World) -> None: @@ -77,7 +105,7 @@ class PygamePlugin(Plugin): """ game.add_pre_startup_tasks(self._initialize) game.add_pre_update_tasks(self._check_events) - game.add_post_render_tasks(self._flip_display) + game.add_post_render_tasks(self._update_display) game.add_post_shutdown_tasks(self._terminate) @@ -86,22 +114,13 @@ class Display: Ressource qui represente la fenetre du jeu. """ - def __init__(self, display: pygame.Surface) -> None: - self.size: Vec2 = Vec2(*display.get_size()) + WIDTH = 1080.0 + HEIGHT = 810.0 + RATIO = WIDTH / HEIGHT + INVERT_RATIO = HEIGHT / WIDTH - @property - def width(self) -> float: - """ - Retourne la largeur de la fenetre. - """ - return self.size.x - - @property - def height(self) -> float: - """ - Retourne la hauteur de la fenetre. - """ - return self.size.y + def __init__(self) -> None: + self._surface = pygame.Surface((Display.WIDTH, Display.HEIGHT)) class Keyboard: diff --git a/engine/plugins/render.py b/engine/plugins/render.py new file mode 100644 index 0000000..a5b4802 --- /dev/null +++ b/engine/plugins/render.py @@ -0,0 +1,92 @@ +""" +Définis un plugin permettant d'afficher des choses a l'écran. +""" + +from engine import * +from engine.math import Vec2 +import pygame, os + +from engine.plugins.pygame import Display +from engine.plugins.timing import Time + + +class RenderPlugin(Plugin): + """ + Plugin permettant d'afficher des choses a l'écran. + """ + + @staticmethod + def _initialize(world: World) -> None: + """ + Initialize le système de rendu. + """ + world.set(TextureManager(world[Display])) + + @staticmethod + def _render(world: World) -> None: + """ + Fais le rendu des sprites. + """ + display = world[Display] + textures = world[TextureManager] + + # Rendu de toutes les objects de rendu + entities = sorted(world.query(Order, Position), key=lambda e: e[Order]) + for entity in entities: + # Récupération de la position des entitées + position = entity[Position] + + # Affichage de la texture + if Texture in entity: + display._surface.blit( + textures[entity[Texture]], (position.x, position.y) + ) + + def apply(self, game: Game) -> None: + """ + Applique le plugin a un jeu. + + Paramarters: + game: Le jeu auquel appliquer le plugin. + """ + game.add_post_startup_tasks(self._initialize) + game.add_render_tasks(self._render) + + +class Position(Vec2): + """ + Composant qui représente la position d'une entité. + """ + + +class Order(int): + """ + Composant qui represente l'ordre d'affichage d'un objet. + """ + + +class TextureManager: + """ + Ressource qui contient les textures du jeu. + """ + + def __init__(self, display: Display) -> None: + self._textures: dict[str, pygame.Surface] = {} + for file in os.listdir("textures"): + self._textures[file] = pygame.image.load(f"textures/{file}").convert( + display._surface + ) + error_texture = pygame.Surface((256, 256)) + error_texture.fill((0, 0, 0)) + pygame.draw.rect(error_texture, (255, 0, 255), (0, 0, 128, 128)) + pygame.draw.rect(error_texture, (255, 0, 255), (128, 128, 128, 128)) + self._error_texture = error_texture.convert(display._surface) + + def __getitem__(self, key: str) -> pygame.Surface: + return self._textures.get(key, self._error_texture) + + +class Texture(str): + """ + Composant qui represente la texture d'un sprite. + """ diff --git a/engine/plugins/sprite.py b/engine/plugins/sprite.py deleted file mode 100644 index ef42a0b..0000000 --- a/engine/plugins/sprite.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Définis un plugin permettant d'afficher des sprites a l'écran. -""" - -from engine import * - - -class SpritePlugin(Plugin): - """ - Plugin permettant d'afficher des sprites a l'écran. - """ - - def apply(self, game: Game) -> None: - """ - Applique le plugin a un jeu. - - Paramarters: - game: Le jeu auquel appliquer le plugin. - """ - pass - - -class Sprite: - """ - Composant qui represente un sprite. - """ - - def __init__(self, texture: str, order: int) -> None: - self._texture = texture - self._order = order diff --git a/main.py b/main.py index f02dab0..4a86ec4 100644 --- a/main.py +++ b/main.py @@ -4,12 +4,53 @@ Ceci est un exemple de comment l'on peut utiliser le moteur du jeu. from engine import * -from engine.plugins.timing import TimePlugin -from engine.plugins.pygame import Keyboard, Mouse, PygamePlugin +from engine.math import Vec2 +from engine.plugins.render import Order, RenderPlugin, Position, Texture +from engine.plugins.timing import Delta, Time, TimePlugin +from engine.plugins.pygame import Display, Keyboard, Mouse, PygamePlugin +from random import random # Initialisation -game = Game(TimePlugin(), PygamePlugin()) +game = Game(TimePlugin(), PygamePlugin(), RenderPlugin()) + + +# On créer une tache pour afficher des sprites +def spawn_sprites(world: World) -> None: + """ + Ajoute des sprites au monde. + """ + for i in range(100): + red = random() < 0.1 + world.create_entity( + Position(random() * Display.WIDTH, random() * Display.HEIGHT), + Texture("test2.png") if red else Texture("test.png"), + Order(1 if red else 0), + ) + + +# On ajoutant la tache +game.add_startup_tasks(spawn_sprites) + + +def move_sprites(world: World) -> None: + """ + Change la position des sprites. + """ + move = Vec2( + (-1.0 if world[Keyboard].is_key("q") else 0.0) + + (1.0 if world[Keyboard].is_key("d") else 0.0), + (-1.0 if world[Keyboard].is_key("z") else 0.0) + + (1.0 if world[Keyboard].is_key("s") else 0.0), + ) + for entity in world.query(Position): + if entity[Order] == 1: + continue + entity.set(Position(entity[Position] + (move * world[Delta] * 1000.0))) + + +# On ajoute la tache +game.add_update_tasks(move_sprites) # On créer une tache pour tester si les plugins fonctionnent diff --git a/textures/test.png b/textures/test.png new file mode 100644 index 0000000000000000000000000000000000000000..39b449aa03450133dacd285a4885b5b683ffd5e8 GIT binary patch literal 7006 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 literal 0 HcmV?d00001 diff --git a/textures/test2.png b/textures/test2.png new file mode 100644 index 0000000000000000000000000000000000000000..91b876e213594efd23ab67e6f9fa7b301eab774a GIT binary patch literal 11652 zcmV-~Eql_5P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0AFcTSaefvX>d(t zbYWy+bYTDh0001cB4cc6a8Pe)Zgg`ZI$L`pcp^G5EFyU#Ixu}Kdm?xuIx;daEFyU# zIxu}Kdm?xuIx;daEFyU#Iyo?XEPEn&B04ZEB6%V@IWT=)eJ6UqyZ`_!u1Q2eRCr$P z-3OQ@O;rZqomm4W6wIiYF)J!&#eiTS7(m4c2#A0I1w;{HQ9uv{3@D%y3?u_8pdbeH zmndRDL=g}ZW-%ZNBG>cw%s2np+P*WhGqYiK`+J@<-CbQ(-M70=omf2^&pmNsb(Iq* zPW)TZI}}~5=--R}WBd4Rx3?X?RP^CRZ&UQ@qW3F;|0?>6qJJ&AYSHV8KCbBbMeknp zyzS$0tB?0B`jw&}=dV-rs_kRvUF)5T{h(etZ9Si{7nB`x_R$Sy7OC zeE*`C6kWThnfykUF$CfnN)|5vnGw3?2#_Ti0ftF80)ZF8(` zN3M~2C#Rz=sJl@#mghRTF65rn5B!e)8yu%iwy87L?Qp%EcQ4v1L-zZq*Uyi2|F=ke?>*1NGeTpV+-vIFYVVwV*GnDuYCLL-&o&=We;!`+ ztwldkWRO0#NcmHWUR*TdONyRRWWZv+;O~nbQ}k^`4=d6KJg`W=U$|!bQ$=p@Um9T^ zTTJ_(MSubRyU0U$ZBg2&`G0}BaFi16(~I=| zmPHFLU;J~CN${Pfqcib8MKm2-;6#@rol`BbK(!S zb|<`Id#mkV75&+Cw8hxk5?byN+~ev0Ki2((qE9ROnCWN>`~V~rP#bTwwL7)FR*~@xZ&jqN zdx^G}djyh?Uw}kD=L~1&@WbDw=*C4D&&54J+cz%)I`OTGetw%iw&>%x_wQ8%k1YE9 zqH7j?X^|i3JoWUBMRzNLZ!S{ioa-#Obie^&!u{nUDj!k?*#_!8WNUZAowv8p_u;dP zKC0-Z)6o_lUxdj=XnWtHTTMq>s-w2QQS^NZ3K^GRTz*5*cNT4YR;j0VuN^w-Ivb9T ziZL(^oQ(~4jiMJ6jka61pIM|Ix;YVziAX&&gnOccYfU2j=Ih_VB><8+L&Z-Z(>~o6rJGYG&kdBU7-OD42 z^n>iPA>k*B{;)_}vzZ6&ymr4;^s7biYemYOb59+98`aZ&eTRZwKsx`(*6yAlVHX@6 z-WN%+4w%Py@s5qFA1hMdILmv_Ht_C^=|H=n?*pNr|8BDx|4f?^Y%`3AX~8(euy`W< zvK{$3z?fxo{&CSpsb(uTMkl3f(lc?kf%zdtBm^l#vc!ACX>yw)o=2R0lOj-0T#Tfm z_L-5Q=ACblPK;O*tn1B@!t3I29bU3N{La1LH}4g%TX6Bh1e}=L+ZTE7%%5yKFdB`I zz+tD^{(2FK$v44NA}mbUc%#SRHfp9=5!1voQE2Pp-FQ5XFq?3s@Q_k31F8sT&Gn0( zT=Zo{3oblN@&>$#a1Nvh&B@ke{yRlNN}L%aU71GHWN#ao@J*_G0npEnb$Qk?7qf}a zfam^pZQG%Y>M8l+uG`!D6shN4ZdT;}o>26IMWitSzyvy3hTf?s)eg~1y~E+pEc%Qh z9zo1V%;Mb4^qe2;8lC9)gNlF%W@!F&ks05QqPyO{NNnP^Gg~tR>8bxm6)`ryw@4qP z>(>+!-`6YRrKIPx=@`u(&Q}(_SJ5vP{bmub5-+85uK#&Oam;`hrauy$D>qVm4j!I9 zd%R}BxdD@cjkC^%!;G{O_Be?{`@neGXlrctF$$y#8$7YSetxX`Nkum(`jjF(KuYy( zhcen9$DVU0bmNns!MJjd_VJMWQ{5fFhoG++ho0?riY zbjAZ2P~8}`7|2cFXl>Qkes0?%^V(>uoLEntcp8twONkM0QRJn4t%6-(QrXQ_z2N8& z4Qm}R8(WB-#Uvmk*h%7E${^bSQ<927KR?#}mLik;J*T59ZTC4FUYYEugrP_{ZP|7| zqo6>Gy2IA)-z*{w5^1z2q|r8ON1BX9^vbY?ovdf+bF5B zG84FQ>YXi*%6Aoge-Wnw)yfPM_b93M14RPMJuCp2q=SyFj0Bt8c9(tHh6z(gANErY z_LX7iey+C}`wx_-p6xoMPkk-e9o_>8M0HxQ9w$p}JH_ir{Lx7K3z5&8kTnCehK1a9kf-_-^99L!&+Ty}DZkZ`kx_G&brEEKt;Q@}u*q!_t?0w@Z z0eNFNp(2ix$!WqZE@;PCWYh3Ea!$a{6;TtYjolW)smd6#LI?pnkf)OEBOvaE+!(_Z z!^~|l3$Wj`!Bm(?+tk5`&N=o##0@bdxV~}>m>?&HO550@LGDqxHoJj`%k>sqoM8HJ zRQ&jKbmjJJVQd*Ih@Sv5ma^?oX6%@-fset2Z!$OD0dE+`0cRosHK~+hQkcYSPa+tQ zT$%!W7!h}346b~@q(T8CjUW8cML;5vRwRO1!7>Vc;(GnM=(shT=to;yvjT~vo%U(V zXeQCzw7!*rb|ka%v@zS-Pyb!D%so<0A^9SS4xHQ3_eq^BUdc-GvO^a4vEc!>0Cb%3_1^L0h{-#)g4 zsKcrOjlcj)GlplUG5RwV^X_78=B#!$i7)#ppF#5mADp26iyTzYbFV_ zQN~-@x!;VV4^9<&tUmQek6pJVZGg1V#tgun+uNFToO3+RHttb*+JIRlZM*#jxkuO2 zW;b|YX4M539>$xbVp$wK=fUZmC`DTEDL^<5+Nfz%j-g%KN8+#>VUW>d=HR8%>I@BIkR( zKpBb4ZsVE7aO_9z*mE=}+8;g%Zy0YFaihfE3dj0xgDANHssn?JhtdFdP6rw-IREgq zGVcZ0l$-%nW|NeOF)&M0v3bzs1YYhij-OQ|9T0xBNVW&`{U;Vt`2fRNww3d}UZBkL z_na+8Jypclfx)Dl5jdC*qmL49Gr5p%PR2CDF2eC4#}=@k17z37*z~Ep2w?4I4eIJZA>N7&p3HLBf$nfiV{JeU2BQ0fY}A=X<>X zqj8W3KLLlIwgTOxUOeY0z-QCBQo~lh$FN|*g8e~SBQuCsbgxCsBTm)~FxhZi`Lda* zjdL~}8_&uDIF~kDVtSla*!JpEivKz9dK)68khfS2s4Um|tfa+T`GwZ*(5O(c^FOxtn>Fgk6C?4Bg+8fFa!GEp5T z5bzLDOR2R9uqQ3Zv1>A9*MSO)(zZh%jAQ*@Eb#n1OX~TYR1Q2-&xu(eOCl+Ihk@s0 zj7S4qupXO9;FasePVxehkW8gA%k$#&6dCrJGU;A(UK|gfB+)m9Ouutm3pgl%m&oSi;3C5N(#@28oV1kTlS~A zdDn-9$!^KY*&CZbyS*d7kXzFLx0VNl0|b$M(?l*~K#!Ljh7sn}Dt40Ck-mi)((l|h zFWk$g9tPT)G1ZYTDJN5#zBHbr#|K(m%ZggZ9daw3vkD^TNCAs$UDxN`cY}##9ke$g zoo~1%W-*cV2@2Sg;&4*ob_07>Fjw6qGaMEsS(?mILuS6!{eiI;$a@sJ3i>%P0rcG4 ze!lMuM+s4Ja-@9Rb|_0Q09VCozn(!KW z5!5#77nRV$R6#rEfI3AL6eJWEn)B3=4m^Q8id2_)BgZYTqL>np82L1{7pSc|uH_!7 zKJG1%e|4x%2AXTgU&{xL!2Rn_8~5s*nV8gaZ{3+P2NO^4uY*QFI#YAcC&VqiXupFN z(HX{oS5BSJbDV?UMLY5P^>-BO*gR`}<)f=d1!jTCIKPL9lW*>L4zv~T7Wz5xe$>gA z!20>VPvUzXJoBEP@|++01ZDU^zrKY@P`F2ghm2_-xr%CUobs;Is<_Tqb&*w7TGh$b zGm0LvUG|uwVz$-K6qRm1m7%(^)f0;Tq^LM}r+lHymugV0UNw{*1(oSCjk6s4Z%^4T z4Qo|QK6?%o1FT-uj&okSUbi$j*;cF4pmxf~Z6{@=;rX8H)W5c&vX576Y20V^k@9hh5)u+jt6HcT_1;XHKQ0PFS{1rByCbH0p6+mT^vnQdivKk zq%Yc{^Xj=jWS==R{eL*_VS|tOwvU;pQkak(MNMGG`Nkux``-eA8}X2$@(`!`mCiM8 z^doqlS79x=P*{zVOTSbNkN~NGxB}U;|gQ1 z$5?4O9VX%KRLOaokFnk1vmyjT>Wtf?!oC9EBuGz`B{eu67~RgTgP0KJ({|mK)B-os zXR6+ovFX8L@H{|cc@Z=j=Hyptg|K9VQbKNnf20 z)aMSz>Zq&DQJ`#_9f@+kH1 zMP=)rR?j=<^y=^WuvCn@diYSb(3!O1C0Ukp%OdueeX5wXacm+WeYkx|G*I3}+_v|f>yPz2sJ(2>Ih?CpN_MP1Bl*{X{mRiK z6cTV|ab!8y*U24cu)}FFH=mA9#}!4!LK4<}qsI6Rm>V2%#x~@yJ($`%kE?U@h0j=5Pfdz{{R3_j(_G6_)n6&i*6L;N@^3EQI4ZeNYlra`;R91 z?~@(5$DHHH@sC0{h6K%b18>$ku>ZOT?5H4avlp`gHMuhzhznuXONH5hN@twR!C37R z=3M4aFU!8|Sl9SB?t7iS?J(Zd#YoQMn@-^&=W>tsm2J8Z(05Tz>dbLE=(d{usqyx9 z4$4z6{myOm54rikef9ox^Zi&`$I2b&w{)aU&gDAnP^$@cgKU9y82=yig7Xi_6TU_e zjPJ|j{XL3a zxqW=~BL73K8tmwyS1Ec#(G^7{Id;lBJ*?<0i%Oc*;a0zMTfuhq1qTy^NAAl9j(&3c z`1VEG+`6cY{#8k?)zgYzR`j<;2{fjCsMM>;-paYx6bV)ePQG}%>{Uf2`A%!M(D5hZ zq*U~sW5H_M2NacBxKoZ^TlD%Owy5KA+o_jn<2s$*P-J|mXCK*DhWU;asFq1=W~=aw z@z&9oxq(MW?@=e`*q_?kY7dU|NGKS$9lcB&-t|r-C5fq?ePln{;=a&VS^Ez= z0>_NNb;+;Jb2zXyhW6`WCuVw7;FOT>YK*PR zcQs*V(gg3d!yxZYt^OZRFBCSORFj~u8d9~TtB-=TAnQY5#nimLc#DPcfjK)nIix5&F}W{ zRx1cRmwd<-d`g5($os53b+hg0WgK!@T@!fLoOQG&nN#l3)<0`JSrXecv76AMp?Rvyr-8X)@TzG)&vp$+Dz2I|BA zv7;^|~}4b=|-6)Xlb|m*D}z zDF2rL;TIa4RVo7Sk~rL>y7c|Y_; zjgFp~goMK3W)O8Vg8+L9M@t=I@XFU?L+O{@WsB3*p>wk%_F8$pU$}O^7m#zweQQ>8 zjtZ4TXpR)?n%Duc6&D-~Mw+};s_IdI6!FfyD`Si_J1l5TzR5zx6!_k40kSo3;+)xb z^fF_7Tw!mFi}TXRa^IvKb#?QG)8UrR?DOJONFDmIjs>ohARKU37`QqegTDs2a&4|C zVh^TYV%Fd{xQSs7lcQobm_HbAsOP%S-fa7L!b07Ia+TIgd^v0a?pHsMhi9 zVT7KjD`9~b(=$+)x_uO$O3(8sKO!Vk!@DynKUl#&F#Zk`s^1ypbBFmSARTNy&o6ys z_4?c@_FhJa7~h?Yb<5e*!;Il6rp(D=tm(Uy3_VM9c)DbG@Jh-qF<40<3>c>gGnCtW@K}c&VB=z zEeFwYgAW0r*t}rY_U!$(Ol+RFMCV9wvo7gQ+N)#2W49bu!h4RM^E$8f*@6bdrjlSD zdvh;fux@W9!8=+{&a?UT-|J!u_M+dRJaxxDp_fUoGnNS(V7)spZT16Zb z>e|=GQ8L@*27|CO8+bo72hX+-(7tZ~cyHdfx>TOSG8@nod~V>V{;56g zY8|Bg^~oP&kdzr`IZuk|-&hSe|1dSda4;A5Kl1s5W@*Js>iOtxb#={5w}0FN?kS65WCi{$snN9V%z}Zj{n_s z!GcQ-n{-^D>m%sf?03wS=lDiNY$Tsez(8VO0+d9sgkK_7Fq69a1w{do+y6t)f(6F` zoN!F01pPytFcwG?I2==aA<8#O!mT(G*lkW8RHY=j*lxU;bYaG%GIkqpnt!T+DV=wY zUdtUHM+|kA)Wnb=aLxdys_IvWg3FntWNY$*vIhmsxsG{**bzr%Lo8UZA263<3fT`F zGn4R@aqn0r*XF(J4^Z)VA2C_qQU|SV?Pqa2aim+I>AYGNf{T@ zxm+?XXA&&|QRlUBGf9g945^Q#24M)E9wZ{GyoFC-9c~6%THm+Y|nmZT7%Y+3;CQcArwiVYW#$b0XxO6}*|K1`K z*gX_nM4&Cvy3S@bgXKZp55Q#c0u8pDphh- zD&Dk}!&a*2s+e`kDJzLL^&|J~DMc2AmTI?i?S;;ceBt-0Lz(9(%u@#Y>i*khHL#`y zm%UyMpp{i3-odqtKDVf!zZj@&`YE4x?%X;mCOPfcm@RE=r@U{+1;;t}*F`?!G`3ks z`nP!c)kUvpR7^gdFYRd}3sXyDT5VWSon`23HNiTz6+6wANauy2BC?TgS|o(P2|`U| zPst$Rz>tw5V|>Aa1@qYKxn^>|Fc5Vz+vMRDw@$}$CpjvJdm@`tg4>g$MEZ)gLcEe< zb;PQV{8(mzVL?)}dTcNrPGDED_w*N7Mx$&J!v$)AW5zW7UR!3jo&pUpDtK%uCR%50t40n=1I(!J01*7#|AIP@ z1jZMLXPA@h3l^LY?06{wb1|StFJ;{NR7nABF|rUl63Axxwe>__mX$AdnsL`dlpGU6mO`BKtGO2#&}VW zn>W($C8c?U^ZQJ6Jg{iNl`v-Hnf5ItHyE;X$EO!L4)a;aly}hJj9s5>h(;&3Sc1om zi-fE2!X1i6oGcaVl*}O>-K1m8~NhEfk zc04#do8n%W7BeVE1_(yepVb4HH*1H0CkaDQZP}ILd)hy+$O9pzTu0(4#v!frV`+O& z>LvX!emtzaav0vqT=yp_)`OZ`X>c*aOQpt}Y)ojLqop^VS$~cTw#L$R+7^@0dU{fF zz7*hlFn#sN_yqwF9?N%DBm_fUMWKu`n%rt4!p-(qY(!!S&`EgWA4jK;z)BADCO&WJBx@}R1 z2P4fa_>Llu9}>+hM%s1v@NognfXm#f4h}5v;vHY^uq+Hr){+{GCFtAi2WeycV9X%p z9n2ZN3GgxD+ZRdk@*b?=^W95{7&H%Mgn=b*%$hS84x{*92$5-P)&vaam`%h^!idyK z5>BkajEf0L6m}bCQ%`;EvzA0`#J90UI%i_H0tNZdO&zEvA}t4 z9Fu4{PCI4J?X)QsYbS{JsG_osR%Q6@Y`?U~xoS8a7n4kZZ2wuuzk;=2%rOPROWN%_ z@O_)21is5YJMAMyNZ{5zJhP~b(y0$YUE5L>R>j1tmlu`jp04SmEycJy_ZzyRNKih< zrKU~+{*vIUGHR!QWT~U4wK)uxx;k6F7yWUOdzm|Tp~<_eK{R{rVkG0lwluD3yyf2e zcKkj4T*hiI?^KMlQzy^H7LW8}=as3SHX8~t-f;!bDzQ(6|GsBRU*C_H2`PIoxQ3mAnf5d9Q@BCi^63A!u zavx~F60WydXd#reHsi!N&2hF;-NyedkaPaGUYSB?ly|C$diuW>_8(p3e=9zr$k=>j z(cOz4T7+km3B4KL&nViEGg*Zb%=B4?!#0y;!l7}yBKl`B(+&Xgw*fn${aZ@**oP(vbdygT34jteTr^S z^pK)!7JX;Y&5G_;^t$b`Cl{5PyLx=l$y*f9iDQ9$Lvb>3F2Q5=4{7BScV+NRo+=AM z5!WXVj9|F{tZ)L?sHi&#YKu{nIyo&K^?3ynt{%YTCbfXH)Xd3d@gF69$*X63_{aW2 z2fnSMZ{H>qa1?x(o5|+UkD#`21nHc>H~TCQ0~DAiLBVIug(mbx@{-H~`LXd10)c(f z-Tk_6l6%3yAvjI5ngiGRUN%{R)wDA2gw4)A7VEV&#(Bm2JI^_O0DaQ6Ng2LWz=Lm0i(C5#G_*nE`(!c1DQQ>Wo09(V14jfVlhq;4R$M;a zyuh+^NKo2CDUP0uQ;Z`cgd;*+LR`*w9WX9t(N<`{r}Jb*$nJ2X+E6Kl3VKSCABOSu zqteCW_3Qnnd8U%T^<0*CdJH*bx@Gd)N&yS51%Tr;Nb94 zR(Xksc_-%*)J*CnDVQ`x($09ojLy-zNHS)cecF%m%TB_vA?f7uD@i5{&+fWq(Lo{i z&L_~4?z7<12A_qAo7qL9;Ui#TJ+;Uem`POs1Dufh#v-}8#s=m*P-@q428m4NqkCf# z((W+K2HG#oc4^|CMG1#Jr${0)3C9@%mpOc^Il+H45MC7a$lHx3aX z8c71n7@|s21ziw$V;GP!pm|`Zmkd!C;J}c;{M@3zXB>!CBGx$`P}v=B&?;^-gscF4 za;Erj5d+jL!TV{EC{L$_rkp&SIaz;4Jrv*b!>Dy;o`B(O#^pT~U47{NLh7bJW02OfQQWw?YwZB$%ECJFDy~rsWPpR2 z>xZ{?C%c7;ccH<6#bi{PP|G63kiggEJCFpQxWOd3l9L$ebiRoFzBERchk^Spm%8~fp6(1v6rR#O+{b9cOwm@HCT4ECC}b~i;N%}rF4)4Hvq>PhPt@uN zA4#?9=xx2XB)-ofbx8@y=Ig**;Z+15@}Z%DF&O6{kExU`81H{PmVnRUr#$yac<9gM zd-o#OA<3edr4ROA9C*eWb7#{BSOWu(ml9Dk&HQnq0DCLvNeA|keR?y83#_x@tOx)% zeYQ3nxE>}MB^+tlCEO^jNMZwEgg*37!ujCrejr$$8Ee57=Kk4+cx?YhjXM5sU%p`z z^tL8m$-nDBO9EqHH^$_^i2dll@MbbKK4cbwzAy}=-(WnZGV%{7XA05Aj49)h#%KJy zo{2c7S^6-x;!`kAG3d#=o;YzT%Sp`Og4iS!0}&ZyyfZ)z6T5O=;9KYb7>;Z-51Qj8 z|C-8dBvTt%A)p@V#7#=v60!O;_=#NVrhBlQK_FV#K{+s1#rp)3-BTcMQ9Zewq_MUg zUXXa0d+YEp+>3kiTNWG)bE_z`JOCc<&h8x%3;%9n<`bG1rus;dkiE_;)|9WgOuhs%7uP{aC8wo{ig+ z^>9ulf!G=~K}B5Y0l0|csC*=Xc$o8;(SsFIaO?+QOm2A(Qox)$)l7#lj$@4GoSSxo zVsl=OHrlGQPOUr$sJ$bzfqVBLE=SNFOlVYX8jN7A*}Iz;=NrZartFCp9p0DmVzQQ8 zur9IL7{Uk`R~x2}v1Q!#RMg3}2j zu$4$7<;YE%G5kO(9ea^FNYs;N27>FU8;J>2U^7K#Q0j#lh1jRAnkdXjfq|r7>0%w) zxIfqBne8gjrH-VrnXPFzVn-K}*}#BZ$82Dnfibb$DgwVLQqne36oarMnFO{}JmcCT zC?6aNpK&rV#_Qr6f2 O0000 Date: Wed, 25 Oct 2023 19:37:53 +0200 Subject: [PATCH 6/8] Ajout de docstrings --- engine/plugins/pygame.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/engine/plugins/pygame.py b/engine/plugins/pygame.py index 0097e1e..cc5d5fd 100644 --- a/engine/plugins/pygame.py +++ b/engine/plugins/pygame.py @@ -27,7 +27,9 @@ class PygamePlugin(Plugin): @staticmethod def _initialize(world: World) -> None: - # Initialisation de pygame + """ + Initialize pygame et les ressources. + """ pygame.init() pygame.display.set_mode((800, 600), pygame.RESIZABLE | pygame.SCALED) @@ -40,6 +42,10 @@ class PygamePlugin(Plugin): @staticmethod def _check_events(world: World) -> None: + """ + Met a jour les ressources avec les evenements pygame. + """ + keyboard = world[Keyboard] keyboard.pressed.clear() keyboard.released.clear() @@ -94,6 +100,9 @@ class PygamePlugin(Plugin): @staticmethod def _terminate(world: World) -> None: + """ + Ferme pygame. + """ pygame.quit() def apply(self, game: Game) -> None: From 0fc9975eecb9e050db267221b96fb1d1b062da7b Mon Sep 17 00:00:00 2001 From: Tipragot Date: Wed, 25 Oct 2023 19:41:03 +0200 Subject: [PATCH 7/8] Suppression des imports inutiles --- engine/math.py | 2 +- engine/plugins/render.py | 1 - main.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/engine/math.py b/engine/math.py index 9098685..0675128 100644 --- a/engine/math.py +++ b/engine/math.py @@ -3,7 +3,7 @@ Définis des classes utiles. """ -from typing import SupportsFloat, Union, overload +from typing import SupportsFloat, Union import math diff --git a/engine/plugins/render.py b/engine/plugins/render.py index a5b4802..41ff061 100644 --- a/engine/plugins/render.py +++ b/engine/plugins/render.py @@ -7,7 +7,6 @@ from engine.math import Vec2 import pygame, os from engine.plugins.pygame import Display -from engine.plugins.timing import Time class RenderPlugin(Plugin): diff --git a/main.py b/main.py index 4a86ec4..2910b1c 100644 --- a/main.py +++ b/main.py @@ -6,8 +6,8 @@ Ceci est un exemple de comment l'on peut utiliser le moteur du jeu. from engine import * from engine.math import Vec2 from engine.plugins.render import Order, RenderPlugin, Position, Texture -from engine.plugins.timing import Delta, Time, TimePlugin -from engine.plugins.pygame import Display, Keyboard, Mouse, PygamePlugin +from engine.plugins.timing import Delta, TimePlugin +from engine.plugins.pygame import Display, Keyboard, PygamePlugin from random import random From 6f9aced2c8f9ffbfeee308c918aa9f549f7bf6fb Mon Sep 17 00:00:00 2001 From: Tipragot Date: Wed, 25 Oct 2023 19:46:15 +0200 Subject: [PATCH 8/8] =?UTF-8?q?R=C3=A9glage=20de=20la=20redimmention=20de?= =?UTF-8?q?=20la=20fen=C3=AAtre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/plugins/pygame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/plugins/pygame.py b/engine/plugins/pygame.py index cc5d5fd..a0b916c 100644 --- a/engine/plugins/pygame.py +++ b/engine/plugins/pygame.py @@ -31,7 +31,7 @@ class PygamePlugin(Plugin): Initialize pygame et les ressources. """ pygame.init() - pygame.display.set_mode((800, 600), pygame.RESIZABLE | pygame.SCALED) + pygame.display.set_mode((800, 600), pygame.RESIZABLE) # Initialisation des ressources world.set(