From d5a565f1928cba961f8e252e8bae896e95909404 Mon Sep 17 00:00:00 2001 From: Tipragot Date: Wed, 1 Nov 2023 03:51:38 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20du=20syst=C3=A8me=20d'animations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/engine/__init__.py | 23 +++++++++--- src/plugins/animation.py | 79 +++++++++++++++++++++++++++++++++++++--- src/plugins/assets.py | 15 +++++++- src/plugins/defaults.py | 11 +++++- src/plugins/display.py | 4 +- src/plugins/inputs.py | 4 +- src/plugins/render.py | 14 +++---- src/plugins/timing.py | 52 ++++++++++++++++++++++++++ 8 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 src/plugins/timing.py diff --git a/src/engine/__init__.py b/src/engine/__init__.py index 7077e2e..8cc06a2 100644 --- a/src/engine/__init__.py +++ b/src/engine/__init__.py @@ -7,11 +7,11 @@ from typing import Callable, Optional from engine.ecs import World -class GlobalScene: +class GlobalPlugin: """ - Une scène globale du jeu. + Un plugin global du jeu. - La scène sera executé avant et après la scène actuelle du jeu. + Le plugin sera executé avant et après la scène courrante du jeu. Elle sera aussi executé au démarage et à l'arrêt du jeu. """ @@ -27,8 +27,8 @@ class GlobalScene: self.post_update_systems = post_update_systems self.stop_systems = stop_systems - def __add__(self, other: "GlobalScene") -> "GlobalScene": - return GlobalScene( + def __add__(self, other: "GlobalPlugin") -> "GlobalPlugin": + return GlobalPlugin( self.init_systems + other.init_systems, self.pre_update_systems + other.pre_update_systems, self.post_update_systems + other.post_update_systems, @@ -59,6 +59,17 @@ class Scene: ) +class Plugin(Scene): + """ + Un plugin peut être ajouté a une scène pour lui ajouter des fonctionnalitées. + + Techniquement, un `Plugin` est une simple `Scène` et le nom `Plugin` est + juste là pour indiquer qu'il s'agit d'un plugin. Et le fait de pouvoir ajouter + le `Plugin` à une scène est due au fait que l'on peut combiner des scènes entre + elles. + """ + + class KeepAlive: """ Composant qui marque une entité comme n'étant pas détruit lors @@ -82,7 +93,7 @@ class CurrentScene(KeepAlive): return False -def start_game(global_scene: GlobalScene, scene: Optional[Scene]): +def start_game(global_scene: GlobalPlugin, scene: Optional[Scene]): """ Lance un jeu. diff --git a/src/plugins/animation.py b/src/plugins/animation.py index 6738980..499435a 100644 --- a/src/plugins/animation.py +++ b/src/plugins/animation.py @@ -2,7 +2,14 @@ Un plugin qui permet de jouer des animations de sprites. """ -from engine import GlobalScene +from typing import Callable + +import pygame +from engine import GlobalPlugin +from engine.ecs import Entity, World +from plugins.assets import Assets +from plugins.render import Sprite +from plugins.timing import Delta class Animation: @@ -11,13 +18,75 @@ class Animation: sur l'entité. """ - def __init__(self) -> None: - pass + def __init__( + self, + name: str, + fps: float = 60.0, + loop: bool = False, + callback: Callable[[World, Entity], object] = lambda _w, _e: None, + ) -> None: + self.name = name + self.fps = fps + self.loop = loop + self.callback = callback + self.timer = 0.0 -PLUGIN = GlobalScene( - [], +def __update_sprite(entity: Entity, texture: pygame.Surface, assets: Assets): + """ + Change la texture de la `Sprite` d'une entité. + + Si l'entité n'as pas de `Sprite`, une nouvelle `Sprite` sera ajoutée. + Si la texture est la texture d'erreur, la texture de la `Sprite` ne + sera pas changée. + """ + if texture is assets.error_texture: + return + if Sprite not in entity: + entity[Sprite] = Sprite(texture) + else: + entity[Sprite].texture = texture + + +def __update_animations(world: World) -> None: + """ + Met à jour les sprites des animations. + """ + assets = world[Assets] + delta = world[Delta] + for entity in world.query(Animation): + animation = entity[Animation] + animation.timer += delta + + # On récupère la texture correspondante a la frame de l'animation + frame_index = int(animation.timer * animation.fps) + texture = assets.get_texture(f"{animation.name}/{frame_index:04d}") + + # Si la texture n'existe pas, l'animation est finie + if texture is assets.error_texture: + if animation.loop: + # Si l'animation est une boucle on met la première image de l'animation + animation.timer = 0.0 + __update_sprite( + entity, assets.get_texture(f"{animation.name}/0000"), assets + ) + + # Et on appelle la fonction de callback + animation.callback(world, entity) + else: + # Sinon on supprime le composant `Animation` + del entity[Animation] + + # Et on appelle la fonction de callback + animation.callback(world, entity) + else: + # Si l'animation n'est pas finie, on met à jour la texture + __update_sprite(entity, texture, assets) + + +PLUGIN = GlobalPlugin( [], + [__update_animations], [], [], ) diff --git a/src/plugins/assets.py b/src/plugins/assets.py index e72c80c..4c831ca 100644 --- a/src/plugins/assets.py +++ b/src/plugins/assets.py @@ -4,7 +4,7 @@ Un plugin qui gère les ressources du jeu. import glob import pygame -from engine import CurrentScene, GlobalScene, KeepAlive, Scene +from engine import CurrentScene, GlobalPlugin, KeepAlive, Scene from engine.ecs import World from plugins import render @@ -29,6 +29,15 @@ class Assets(KeepAlive): # Cache des ressources self.__textures: dict[str, pygame.Surface] = {} + @property + def error_texture(self) -> pygame.Surface: + """ + La texture d'erreur. + + Cette texture est utilisé lorsque la texture demandée n'existe pas. + """ + return self.__error_texture + @property def unloaded_texture(self) -> pygame.Surface: """ @@ -56,6 +65,8 @@ class Assets(KeepAlive): def get_texture(self, name: str) -> pygame.Surface: """ Renvoie la texture demandée. + + Si la texture n'existe pas dans le cache, la texture d'erreur sera renvoyée. """ return self.__textures.get(name, self.__error_texture) @@ -73,7 +84,7 @@ def __initialize(world: World): world.set(Assets()) -PLUGIN = GlobalScene( +PLUGIN = GlobalPlugin( [__initialize], [], [], diff --git a/src/plugins/defaults.py b/src/plugins/defaults.py index d262dc1..6496838 100644 --- a/src/plugins/defaults.py +++ b/src/plugins/defaults.py @@ -2,7 +2,14 @@ Plugin qui rassemple tous les plugins globaux. """ -from plugins import assets, display, inputs, render +from plugins import animation, assets, display, inputs, render, timing -PLUGIN = display.PLUGIN + assets.PLUGIN + inputs.PLUGIN + render.PLUGIN +PLUGIN = ( + display.PLUGIN + + timing.PLUGIN + + assets.PLUGIN + + inputs.PLUGIN + + animation.PLUGIN + + render.PLUGIN +) diff --git a/src/plugins/display.py b/src/plugins/display.py index 3c54842..579b5ba 100644 --- a/src/plugins/display.py +++ b/src/plugins/display.py @@ -5,7 +5,7 @@ Un plugin pour la gestion de la fenetre du jeu. import pygame from engine.ecs import World -from engine import GlobalScene +from engine import GlobalPlugin def __initialize(_world: World): @@ -23,7 +23,7 @@ def __terminate(_world: World): pygame.quit() -PLUGIN = GlobalScene( +PLUGIN = GlobalPlugin( [__initialize], [], [], diff --git a/src/plugins/inputs.py b/src/plugins/inputs.py index cf8f8a5..ffa6f08 100644 --- a/src/plugins/inputs.py +++ b/src/plugins/inputs.py @@ -3,7 +3,7 @@ Un plugin permettant de gérer les entrées utilisateur du jeu. """ import pygame -from engine import CurrentScene, GlobalScene, KeepAlive +from engine import CurrentScene, GlobalPlugin, KeepAlive from engine.ecs import World from engine.math import Vec2 from plugins import render @@ -78,7 +78,7 @@ def __update_input(world: World): ) -PLUGIN = GlobalScene( +PLUGIN = GlobalPlugin( [__initialize], [__update_input], [], diff --git a/src/plugins/render.py b/src/plugins/render.py index dfa118e..fc7e12b 100644 --- a/src/plugins/render.py +++ b/src/plugins/render.py @@ -4,7 +4,7 @@ Un plugin qui s'occupe de rendre des choses dans la fenetre. from typing import Optional import pygame -from engine import GlobalScene, KeepAlive +from engine import GlobalPlugin, KeepAlive from engine.ecs import World from engine.math import Vec2 @@ -42,20 +42,20 @@ class Sprite: def __init__( self, - surface: pygame.Surface, + texture: pygame.Surface, position: Vec2 = Vec2(0), order: float = -1.0, area: Optional[tuple[float, float, float, float]] = None, ): - self.surface = surface + self.texture = texture self.position = position self.order = order if area is None: self.area = ( 0.0, 0.0, - float(surface.get_width()), - float(surface.get_height()), + float(texture.get_width()), + float(texture.get_height()), ) else: self.area = area @@ -77,7 +77,7 @@ def __render(world: World): sprites = [entity[Sprite] for entity in world.query(Sprite)] for sprite in sorted(sprites, key=lambda sprite: sprite.order): surface.blit( - sprite.surface, + sprite.texture, (sprite.position.x, sprite.position.y), sprite.area, ) @@ -93,7 +93,7 @@ def __render(world: World): pygame.display.flip() -PLUGIN = GlobalScene( +PLUGIN = GlobalPlugin( [__initialize], [], [__render], diff --git a/src/plugins/timing.py b/src/plugins/timing.py new file mode 100644 index 0000000..a87eb27 --- /dev/null +++ b/src/plugins/timing.py @@ -0,0 +1,52 @@ +""" +Un plugin permettant de connaitre le temps depuis le +lancement du jeu et le temps depuis la dernière frame. +""" + + +from time import time +from engine import GlobalPlugin +from engine.ecs import World + + +class GlobalTime(float): + """ + Ressource qui représente le temps global de l'ordinateur sur lequel tourne le jeu. + """ + + +class Time(float): + """ + Ressource qui représente le temps depuis le lancement du jeu. + """ + + +class Delta(float): + """ + Ressource qui détermine le temps depuis la première frame. + """ + + +def __initialize(world: World): + """ + Initialise les ressources pour la gestion du temps. + """ + world.set(GlobalTime(time()), Time(0.0)) + + +def __update(world: World): + """ + Met à jour les ressources de temps. + """ + now = time() + world[Delta] = delta = now - world[GlobalTime] + world[GlobalTime] = now + world[Time] += delta + + +PLUGIN = GlobalPlugin( + [__initialize], + [__update], + [], + [], +)