diff --git a/assets/global/background.png b/assets/global/background.png deleted file mode 100644 index 7341a33..0000000 Binary files a/assets/global/background.png and /dev/null differ diff --git a/assets/error.mp3 b/assets/sounds/error.mp3 similarity index 100% rename from assets/error.mp3 rename to assets/sounds/error.mp3 diff --git a/assets/textures/background.png b/assets/textures/background.png new file mode 100644 index 0000000..01be4e0 Binary files /dev/null and b/assets/textures/background.png differ diff --git a/assets/textures/dada.png b/assets/textures/dada.png new file mode 100644 index 0000000..773c20d Binary files /dev/null and b/assets/textures/dada.png differ diff --git a/assets/textures/dodo.png b/assets/textures/dodo.png new file mode 100644 index 0000000..88f011c Binary files /dev/null and b/assets/textures/dodo.png differ diff --git a/src/engine/ecs.py b/src/engine/ecs.py index 8e58b65..717eba1 100644 --- a/src/engine/ecs.py +++ b/src/engine/ecs.py @@ -6,7 +6,7 @@ Pour moddifier le monde, on n'agis que sur les composants. """ -from typing import Iterator, Optional, Sequence, TypeVar +from typing import Iterator, Optional, Sequence class Entity: @@ -17,15 +17,6 @@ class Entity: utilitaire pour acceder au monde plus facilement. """ - __T = TypeVar("__T") - """ - Ce type est utilisé pour permettre la gestion des types - pour certaines fonctions. Par exemple, la fonction `__getitem__` - utilise cette variable pour retourner un object du type demandé. - Cela permet d'avoir des vérifications de types et de l'autocomplétion - en utilisant notre IDE. - """ - def __init__(self, world: "World", identifier: int) -> None: self.__world = world self.__identifier = identifier @@ -55,13 +46,13 @@ class Entity: def __repr__(self) -> str: return f"Entity({self.__identifier})" - def __getitem__(self, component_type: type[__T]) -> __T: + def __getitem__[T](self, component_type: type[T]) -> T: return self.__world.get_component(self, component_type) def __delitem__(self, component_type: type[object]): self.__world.remove_component(self, component_type) - def __setitem__(self, component_type: type[__T], component: __T): + def __setitem__[T](self, component_type: type[T], component: T): if component_type != type(component): component = component_type(component) self.__world.set_component(self, component) @@ -75,7 +66,7 @@ class Entity: def __iter__(self) -> Iterator[object]: return iter(self.__world.all_components(self)) - def get(self, component_type: type[__T], default: Optional[__T] = None) -> __T: + def get[T](self, component_type: type[T], default: Optional[T] = None) -> T: """ Renvoie le composant de type `component_type` de l'entité. Si aucun composant de type `component_type` n'est dans l'entité: @@ -126,15 +117,6 @@ class World(Entity): globaux, relatif au monde. """ - __T = TypeVar("__T") - """ - Ce type est utilisé pour permettre la gestion des types - pour certaines fonctions. Par exemple, la fonction `get_component` - utilise cette variable pour retourner un object du type demandé. - Cela permet d'avoir des vérifications de types et de l'autocomplétion - en utilisant notre IDE. - """ - def __init__(self): super().__init__(self, 0) self.__components: dict[int, dict[type[object], object]] = {} @@ -164,12 +146,19 @@ class World(Entity): - `entity`: l'entité dans lequelle ajouter le composant. - `component`: le composant à ajouter. """ + if isinstance(component, tuple): + for c in component: # type: ignore + if c is not None: + self.set_component(entity, c) # type: ignore + return self.__components.setdefault(entity.identifier, {})[type(component)] = component self.__entities.setdefault(type(component), set()).add(entity.identifier) - def get_component( - self, entity: "Entity", component_type: type[__T], default: Optional[__T] = None - ) -> __T: + def get_component[ + T + ]( + self, entity: "Entity", component_type: type[T], default: Optional[T] = None + ) -> T: """ Renvoie le composant de type `component_type` de l'entité `entity`. Si aucun composant de type `component_type` n'est dans l'entité: diff --git a/src/main.py b/src/main.py index 58ac896..3e3c67c 100644 --- a/src/main.py +++ b/src/main.py @@ -2,9 +2,239 @@ Module d'exemple de l'utilisation du moteur de jeu. """ -from engine import start_game -from plugins import defaults -from scenes import menu +from engine import Scene, start_game +from engine.ecs import Entity, World +from engine.math import Vec2 +from plugins import defaults, physics +from plugins import render +from plugins.inputs import Held +from plugins.render import ( + Origin, + Position, + Scale, + SpriteBundle, +) +from plugins.timing import Delta -start_game(defaults.PLUGIN, menu.SCENE) +def lol(a: Entity, b: Entity, simul: bool = False): + if Bounce in b: + speed = a[physics.Velocity].length + a[physics.Velocity] = a[physics.Velocity].normalized + a[physics.Velocity].y = (a[Position].y - b[Position].y) * 0.005 + a[physics.Velocity] = a[physics.Velocity].normalized * min( + (speed * 1.1), 1000.0 + ) + return True + + +class Bounce: + pass + + +def lol_simul(a: Entity, b: Entity): + lol(a, b, True) + return RightWall not in b + + +class Simulated: + pass + + +class Bar: + pass + + +class RightWall: + pass + + +class BallFollow(int): + pass + + +class Mine: + pass + + +def __initialize(world: World): + """ + Initialise les ressources pour le moteur de jeu. + """ + world.new_entity().set( + SpriteBundle( + "background.png", + -1, + scale=Vec2(render.WIDTH, render.HEIGHT), + ), + ) + # world.new_entity().set( + # SpriteBundle( + # "dodo.png", + # 0, + # position=Vec2(800, 500), + # origin=Vec2(0.5, 0.5), + # scale=Vec2(400, 300), + # ), + # physics.Solid(), + # ) + + world.new_entity().set( + SpriteBundle( + "dodo.png", + 0, + position=Vec2(100, 100), + origin=Vec2(0, 0), + scale=Vec2(render.WIDTH - 200, 10), + ), + physics.Solid(), + ) + + world.new_entity().set( + SpriteBundle( + "dodo.png", + 0, + position=Vec2(100, render.HEIGHT - 100), + origin=Vec2(0, 1), + scale=Vec2(render.WIDTH - 200, 10), + ), + physics.Solid(), + ) + + world.new_entity().set( + SpriteBundle( + "dodo.png", + 0, + position=Vec2(render.WIDTH - 100, 100), + origin=Vec2(1, 0), + scale=Vec2(10, render.HEIGHT - 200), + ), + physics.Solid(), + RightWall(), + ) + world.new_entity().set( + SpriteBundle( + "dodo.png", + 0, + position=Vec2(100, 100), + origin=Vec2(0, 0), + scale=Vec2(10, render.HEIGHT - 200), + ), + physics.Solid(), + ) + + world.new_entity().set( + SpriteBundle( + "dodo.png", + 0, + position=Vec2(render.WIDTH - 130, render.HEIGHT / 2), + origin=Vec2(0.5, 0.5), + scale=Vec2(10, 200), + ), + physics.Solid(), + Bar(), + Bounce(), + ) + + world.new_entity().set( + SpriteBundle( + "dodo.png", + 0, + position=Vec2(130, render.HEIGHT / 2), + origin=Vec2(0.5, 0.5), + scale=Vec2(10, 200), + ), + physics.Solid(), + Mine(), + Bounce(), + ) + + world.new_entity().set( + SpriteBundle( + "dada.png", + 1, + scale=Vec2(10), + position=Vec2(500, 500), + origin=Vec2(0.5, 0.5), + ), + physics.Velocity(Vec2(200, 100)), + physics.CollisionHandler(lol), + ) + + for i in range(20): + world.new_entity().set( + SpriteBundle( + "dodo.png", + 10, + scale=Vec2(10), + ), + physics.CollisionHandler(lol), + BallFollow(i + 1), + # physics.CollisionHandler(lol_simul), + ) + + +def __update(world: World): + """ + Test. + """ + for entity in world.query(Mine, Position): + if "z" in world[Held]: + entity[Position].y -= 300 * world[Delta] + if "s" in world[Held]: + entity[Position].y += 300 * world[Delta] + + ball = max( + world.query(Position, physics.Velocity, physics.CollisionHandler), + key=lambda e: e[Position].x, + ) + for bar in world.query(Bar): + bar.remove(physics.Solid) + entity = world.new_entity() + entity.set( + Position(ball[Position]), + Scale(ball[Scale]), + physics.Velocity(ball[physics.Velocity]), + Origin(ball[Origin]), + physics.CollisionHandler(lol_simul), + ) + physics.move_entity(entity, entity[physics.Velocity] * 500) + target = entity[Position].y + for bar in world.query(Bar): + diff = target - bar[Position].y + # bar[Position].y += (diff / abs(diff)) * 300 * world[Delta] + bar[Position].y += diff + bar.set(physics.Solid()) + entity.destroy() + + # for bar in world.query(Bar): + # bar.remove(physics.Solid) + for entity in world.query(BallFollow): + entity[Position] = Vec2(ball[Position]) + entity[physics.Velocity] = Vec2(ball[physics.Velocity]) + physics.move_entity(entity, entity[physics.Velocity] * entity[BallFollow] * 0.5) + del entity[physics.Velocity] + # for bar in world.query(Bar): + # bar.set(physics.Solid()) + + # ball.set(Simulated()) + # for entity in world.query(Bar): + # entity.remove(physics.Solid) + # last_position = Vec2(ball[Position]) + # last_velocity = Vec2(ball[physics.Velocity]) + # physics.move_entity(ball, ball[physics.Velocity] * 5000) + # ball[Position] = last_position + # ball[physics.Velocity] = last_velocity + # for entity in world.query(Bar): + # entity.set(physics.Solid()) + # ball.remove(Simulated) + + +MENU = Scene( + [__initialize], + [__update], + [], +) + + +start_game(defaults.PLUGIN, MENU) diff --git a/src/plugins/animation.py b/src/plugins/animation.py deleted file mode 100644 index 9aa251a..0000000 --- a/src/plugins/animation.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Un plugin qui permet de jouer des animations de sprites. -""" - -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: - """ - Composant qui contient toutes les informations d'une animation est en cour - sur l'entité. - """ - - 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 - self.ended = False - - def wait(self) -> Callable[[World], bool]: - """ - Utilitaire de `Coroutine` permettant d'attendre que l'animation soit finie. - """ - return lambda world: self.ended - - -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] - animation.ended = True - - # On trouve la dernière image de l'animation et on met à jour la texture - while texture is assets.error_texture: - frame_index -= 1 - texture = assets.get_texture(f"{animation.name}/{frame_index:04d}") - if frame_index < 0: - break - if texture is not assets.error_texture: - __update_sprite(entity, texture, assets) - - # 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) - animation.ended = False - - -PLUGIN = GlobalPlugin( - [], - [], - [__update_animations], - [], -) diff --git a/src/plugins/assets.py b/src/plugins/assets.py index 8adf9f9..852e6f4 100644 --- a/src/plugins/assets.py +++ b/src/plugins/assets.py @@ -1,271 +1,45 @@ """ -Un plugin qui gère les assets du jeu. +Ce module contient des utilitaires pour le chargement des ressources du jeu. """ -import glob -import random import pygame -from engine import CurrentScene, GlobalPlugin, KeepAlive, Scene -from engine.ecs import World -from plugins import render -from plugins.sound import Sound -class Assets(KeepAlive): +def load_texture(name: str, cache: dict[str, pygame.Surface] = {}) -> pygame.Surface: """ - Ressource qui gère les assets du jeu. + Charge une texture et la renvoi. """ - - def __init__(self): - # Création de la texture d'erreur - 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() - - # Chragement du son d'erreur - self.__error_sound = pygame.mixer.Sound("assets/error.mp3") - # self.__waiting_sound = pygame.mixer.Sound("assets/waiting.mp3") - - # Chargement des textures de chargement - # self.__unloaded_texture = pygame.image.load("assets/unloaded.png").convert() - # self.__loaded_texture = pygame.image.load("assets/loaded.png").convert() - - # Cache des ressources - self.__textures: dict[str, pygame.Surface] = {} - self.__fonts: dict[int, pygame.font.Font] = {} - self.__texts: dict[tuple[int, str], pygame.Surface] = {} - self.__sounds: dict[str, pygame.mixer.Sound] = {} - - @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 error_sound(self) -> pygame.mixer.Sound: - """ - Le son d'erreur. - - Cette texture est utilisé lorsque le son demandé n'existe pas. - """ - return self.__error_sound - - @property - def waiting_sound(self) -> pygame.mixer.Sound: - """ - Le son de chargement. - """ - return self.__waiting_sound - - @property - def unloaded_texture(self) -> pygame.Surface: - """ - La texture de chargement qui s'affiche au début du chargement et qui - est progressivement remplacé par la texture `loaded_texture`. - """ - return self.__unloaded_texture - - @property - def loaded_texture(self) -> pygame.Surface: - """ - La texture de chargement qui s'affiche progressivement lors d'un chargement. - """ - return self.__loaded_texture - - def load_texture(self, name: str, path: str) -> pygame.Surface: - """ - Charge une texture et la renvoi. Si une texture existe déja dans le cache, - elle sera remplacée par la nouvelle. - """ - surface = pygame.image.load(path).convert_alpha() - self.__textures[name] = surface - return surface - - 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) - - def get_font(self, size: int) -> pygame.font.Font: - """ - Renvoie la police d'ecriture du jeu avec la taille demandée. - - Cette fonction charge le fichier `assets/font.ttf` pour la taille demandée - et la met dans le cache. Si la police d'ecriture existe déjà dans le cache, - elle sera renvoyée directement. - """ - font = self.__fonts.get(size) - if font is None: - font = self.__fonts[size] = pygame.font.Font("assets/font.ttf", size) - return font - - def get_text(self, size: int, text: str, color: pygame.Color) -> pygame.Surface: - """ - Renvoie une image correspondant à la chaîne de caractères demandée avec - la taille de police demandée et la couleur demandée. - - Si l'image du texte demandé n'est pas dans le cache, elle sera créer - puis mis dans le cache et enfin renvoyée. - """ - surface = self.__texts.get((size, text)) - if surface is None: - surface = self.__texts[(size, text)] = self.get_font(size).render( - text, True, color - ) - return surface - - def load_sound(self, name: str, path: str) -> pygame.mixer.Sound: - """ - Charge un son et le renvoi. Si un son existe déja dans le cache, - il sera remplacé par le nouveau. - """ - sound = pygame.mixer.Sound(path) - self.__sounds[name] = sound - return sound - - def get_sound(self, name: str) -> pygame.mixer.Sound: - """ - Renvoie le son demandé. - - Si le son n'existe pas dans le cache, le son d'erreur sera renvoyé. - """ - return self.__sounds.get(name, self.__error_sound) - - def clear_cache(self): - """ - Vide le cache des assets. - - Les fonts ne sont pas effacés car ils n'y a normalement pas énormément - de taille de police différentes utilisées. - """ - self.__textures.clear() - self.__texts.clear() - self.__sounds.clear() + surface = cache.get(name) + if surface is None: + surface = pygame.image.load(f"assets/textures/{name}").convert_alpha() + cache[name] = surface + return surface -def __initialize(world: World): +def load_sound( + name: str, cache: dict[str, pygame.mixer.Sound] = {} +) -> pygame.mixer.Sound: """ - Ajoute la ressource `Assets` au monde. + Charge un son et le renvoi. """ - world.set(Assets()) + sound = cache.get(name) + if sound is None: + sound = pygame.mixer.Sound(f"assets/sounds/{name}") + cache[name] = sound + return sound -PLUGIN = GlobalPlugin( - [__initialize], - [], - [], - [], -) - - -def loading_scene(target: Scene, name: str, clear_cache: bool = True): +def load_text( + text: str, + size: int, + color: pygame.Color, + cache: dict[tuple[str, int, tuple[int, int, int]], pygame.Surface] = {}, +) -> pygame.Surface: """ - Retourne une scène de chargement des assets qui passe à la scène donné - en paramètres lorsque tous les assets de la scène sont chargées. - - Paramètres: - - `target`: la scène qui sera lancé après le chargement des assets. - - `name`: le nom de la scène, ce nom est utilisé pour savoir dans quel - dossier sont les assets de la scène. Les assets de la scène - seront récupéré dans le dossier `assets/`. + Charge un texte et le renvoi. """ - - class AssetIterator: - """ - Une ressource qui contient un itérateur sur les fichiers des assets - de la scène à charger. - """ - - def __init__(self): - self.files = glob.glob(f"assets/{name}/**/*", recursive=True) - self.files.extend(glob.glob("assets/global/**/*", recursive=True)) - random.shuffle(self.files) - self.total = len(self.files) - - @staticmethod - def prepare_world(world: World): - """ - Retire toutes les ressource précédentes du monde puis - ajoute `ResourceIterator` et la barre de progression dans le monde. - """ - assets = world[Assets] - if clear_cache: - assets.clear_cache() - asset_iterator = AssetIterator() - world.set(AssetIterator()) - if asset_iterator.total <= 30: - for _ in range(asset_iterator.total): - asset_iterator.load_next(world) - else: - world.new_entity().set( - render.Sprite(assets.unloaded_texture, order=1000000000) - ) - world.new_entity().set( - ProgessBar(), - render.Sprite( - assets.loaded_texture, - order=1000000001, - area=(0, 0, 0, render.HEIGHT), - ), - Sound(assets.waiting_sound, loop=True), - ) - - @staticmethod - def load_next(world: World): - """ - Charge le fichier suivant de l'itérateur. - """ - assets = world[Assets] - asset_iterator = world[AssetIterator] - if len(asset_iterator.files) == 0: - world[CurrentScene] = target - else: - file = asset_iterator.files.pop().replace("\\", "/") - ressource_extension = file.split(".")[-1] - prefix = ( - "assets/global/" - if file.startswith("assets/global/") - else f"assets/{name}/" - ) - ressource_name = file[len(prefix) : -len(ressource_extension) - 1] - if ressource_extension in ("png", "jpg"): - assets.load_texture(ressource_name, file) - if ressource_extension in ("mp3", "wav", "ogg"): - assets.load_sound(ressource_name, file) - - class ProgessBar: - """ - Composant marquant une entité comme étant une barre de progression. - """ - - @staticmethod - def render(world: World): - """ - Affiche une barre de progression du chargement des assets. - """ - # Calcul du pourcentage de chargement - asset_iterator = world[AssetIterator] - if asset_iterator.total == 0: - progress = 0.0 - else: - file_loaded = asset_iterator.total - len(asset_iterator.files) - progress = file_loaded / asset_iterator.total - - # Affichage de la barre de progression - for progress_bar in world.query(ProgessBar): - progress_bar[render.Sprite].area = (0, 0, progress, 1.0) - - return Scene( - [AssetIterator.prepare_world], - [AssetIterator.load_next, ProgessBar.render], - [], - ) + surface = cache.get((text, size, (color.r, color.g, color.b))) + if surface is None: + surface = pygame.font.Font("assets/font.ttf", size).render(text, True, color) + cache[(text, size, (color.r, color.g, color.b))] = surface + return surface diff --git a/src/plugins/click.py b/src/plugins/click.py index d4548fa..47db260 100644 --- a/src/plugins/click.py +++ b/src/plugins/click.py @@ -7,7 +7,7 @@ from engine import GlobalPlugin from engine.ecs import Entity, World from plugins.hover import Hovered from plugins.inputs import Pressed -from plugins.render import Sprite +from plugins.render import Origin, Position, Scale class Clicked: @@ -30,7 +30,7 @@ def __update_clicked(world: World): Met à jour les composants `Clicked`. """ mouse_click = "button_1" in world[Pressed] - sprite_entities = world.query(Sprite) + sprite_entities = world.query(Position, Scale, Origin) for entity in sprite_entities: if Hovered in entity and mouse_click: entity[Clicked] = Clicked() diff --git a/src/plugins/coroutine.py b/src/plugins/coroutine.py deleted file mode 100644 index 6b30659..0000000 --- a/src/plugins/coroutine.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Plugin permettant d'executer des coroutine. - -Une coroutine est une fonction qui est executée sur plusieurs frames, -cela permet d'executer du code comme si il s'agissait d'une fonction -classique tout en laissant tourner la boucle du jeu. Il est a noter que -cette fonction n'est pas lancé dans une autre thread, la fonction est -executé dans le thread principal, mais lorsqu'un `yield` est utilisé, -l'execution de la fonction est arrêté jusqu'a ce que la condition donné -par le `yield` soit remplie, ce qui permet au thread principal de pouvoir -continuer de faire le rendu et la logique du jeu. Faire un traivail bloquant -dans une coroutine auras donc un impacte sur le nombre d'images par secondes -du jeu. -""" - - -from time import time -from typing import Callable, Generator, Optional -from engine import GlobalPlugin -from engine.ecs import Entity, World -from plugins.timing import GlobalTime - - -def wait(seconds: float) -> Callable[[World], bool]: - """ - Utilitaire de `Coroutine` permettant d'attendre un certain temps. - """ - stop_time = time() + seconds - return lambda world: world[GlobalTime] >= stop_time - - -def condition(condition_function: Callable[[World], bool]) -> Callable[[World], bool]: - """ - Utilitaire de `Coroutine` permettant d'attendre que la condition soit réalisé avant - de continuer l'execution. - """ - return condition_function - - -class Coroutine: - """ - Composant permettant d'executer une coroutine. - """ - - def __init__(self, generator: Generator[Callable[[World], bool], None, None]): - self.generator = generator - self.__condition: Optional[ # pylint: disable=unused-private-member - Callable[[World], bool] - ] = None - - @staticmethod - def update(entity: Entity): - """ - Met à jour la coroutine d'une entité. - - Si l'entité n'as pas de `Coroutine` la fonction ne fait rien. - """ - if Coroutine not in entity: - return - coroutine = entity[Coroutine] - if coroutine.__condition is None: - coroutine.__condition = next( # pylint: disable=unused-private-member - coroutine.generator, None - ) - if coroutine.__condition is None: - del entity[Coroutine] - return - if coroutine.__condition(entity.world): - coroutine.__condition = None # pylint: disable=unused-private-member - - -def __update_coroutines(world: World): - """ - Met à jour les coroutine du jeu. - """ - for entity in world.query(Coroutine): - Coroutine.update(entity) - - -PLUGIN = GlobalPlugin( - [], - [__update_coroutines], - [], - [], -) diff --git a/src/plugins/defaults.py b/src/plugins/defaults.py index 8545e55..2ad6ca4 100644 --- a/src/plugins/defaults.py +++ b/src/plugins/defaults.py @@ -2,33 +2,16 @@ Plugin qui rassemple tous les plugins globaux. """ -from plugins import ( - animation, - assets, - click, - coroutine, - display, - hover, - inputs, - multisound, - render, - sound, - text, - timing, -) +from plugins import click, display, inputs, physics, sound, render, timing, hover PLUGIN = ( display.PLUGIN + timing.PLUGIN - + assets.PLUGIN + inputs.PLUGIN + + physics.PLUGIN + hover.PLUGIN + click.PLUGIN - + coroutine.PLUGIN - + multisound.PLUGIN + sound.PLUGIN - + text.PLUGIN - + animation.PLUGIN + render.PLUGIN ) diff --git a/src/plugins/hover.py b/src/plugins/hover.py index c73ea65..c458e70 100644 --- a/src/plugins/hover.py +++ b/src/plugins/hover.py @@ -3,13 +3,10 @@ Un plugin permettant de savoir si la souris est par dessus une entité. """ -import pygame - from engine import GlobalPlugin from engine.ecs import World -from engine.math import Vec2 from plugins.inputs import MousePosition -from plugins.render import Sprite +from plugins.render import Origin, Position, Scale, Texture class HoverEnter: @@ -36,7 +33,7 @@ class HoveredTexture: celle-ci est survolée par la souris. """ - def __init__(self, normal: pygame.Surface, hovered: pygame.Surface): + def __init__(self, normal: str, hovered: str): self.normal = normal self.hovered = hovered @@ -47,11 +44,10 @@ def __update_hovered(world: World): """ # On met à jour les composants mouse_position = world[MousePosition] - for entity in world.query(Sprite): + for entity in world.query(Position, Scale, Origin): # Récupération de la position et taille de l'entité - sprite = entity[Sprite] - size = Vec2(*sprite.texture.get_size()) * Vec2(*sprite.area[2:]) - position = sprite.position - (sprite.origin * size) + size = entity[Scale] + position = entity[Position] - (entity[Origin] * size) # On détermine si la souris est sur l'entité if ( @@ -75,13 +71,9 @@ def __update_hovered(world: World): # On affiche la bonne texture for entity in world.query(HoveredTexture): if Hovered in entity: - texture = entity[HoveredTexture].hovered + entity[Texture] = entity[HoveredTexture].hovered else: - texture = entity[HoveredTexture].normal - if Sprite in entity: - entity[Sprite].texture = texture - else: - entity[Sprite] = Sprite(texture) + entity[Texture] = entity[HoveredTexture].normal PLUGIN = GlobalPlugin( diff --git a/src/plugins/multisound.py b/src/plugins/multisound.py deleted file mode 100644 index f42e44b..0000000 --- a/src/plugins/multisound.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Plugin pour les sons multiples. -""" - - -import random -from typing import Callable -from engine import GlobalPlugin - -from engine.ecs import Entity, World -from plugins.assets import Assets -from plugins.sound import Sound - - -class MultiSound: - """ - Composant qui quand il est ajouté a une entité, il joue un son aléatoire - parmis la liste de ses sons, il est ensuite retiré de l'entité. - - Ce composant est fait pour être définis en tant que constante - puis utilisé ensuite, pour cela il prend seulement le nom des - sons au lieu des sons eux memes. Les sons seront donc récupérés - a l'aide de `Assets`. - """ - - def __init__( - self, - *sound_names: str, - loop: bool = False, - volume: float = 1.0, - fade_ms: int = 0, - callback: Callable[[World, Entity], object] = lambda _w, _e: None, - ): - self.sound_names = sound_names - self.loop = loop - self.volume = volume - self.fade_ms = fade_ms - self.callback = callback - - -def __update_sounds(world: World): - """ - Met à jour les sons du jeu. - """ - # Ajout des sons aléatoires - assets = world[Assets] - for entity in world.query(MultiSound): - multi_sound = entity[MultiSound] - sound = assets.get_sound(random.choice(multi_sound.sound_names)) - entity[Sound] = Sound( - sound, - multi_sound.loop, - multi_sound.volume, - multi_sound.fade_ms, - multi_sound.callback, - ) - del entity[MultiSound] - - -PLUGIN = GlobalPlugin( - [], - [], - [__update_sounds], - [], -) diff --git a/src/plugins/physics.py b/src/plugins/physics.py new file mode 100644 index 0000000..8879d4f --- /dev/null +++ b/src/plugins/physics.py @@ -0,0 +1,194 @@ +""" +Plugin implémentant une physique exacte pour des collisions AABB. +""" + +from typing import Callable +from engine import GlobalPlugin +from engine.ecs import Entity, World +from engine.math import Vec2 +from plugins.render import Origin, Position, Scale +from plugins.timing import Delta + + +class Solid: + """ + Composant représentant un objet (de préférence imobille pour que la simulation soit prédictible) qui ne laisse pas passer les objets dynamiques. + """ + + +class Velocity(Vec2): + """ + Composant donnant la vélocité d'un objet. + """ + + +class CollisionHandler: + """ + Composant permettant de traiter les collisions. + """ + + def __init__(self, callback: Callable[[Entity, Entity], bool]): + self.callback = callback + + +class AABB: + """ + Définit une boite. + """ + + def __init__(self, min: Vec2, max: Vec2, entity: Entity): + self.min = min + self.max = max + self.entity = entity + + @staticmethod + def from_entity(entity: Entity): + min = entity[Position] - entity[Origin] * entity[Scale] + return AABB(min, min + entity[Scale], entity) + + def entity_position(self, entity: Entity): + scale = self.max - self.min + entity[Position] = self.min + entity[Origin] * scale + entity[Scale] = scale + + def __contains__(self, point: Vec2): + return ( + self.min.x <= point.x <= self.max.x and self.min.y <= point.y <= self.max.y + ) + + def move(self, movement: Vec2): + self.min += movement + self.max += movement + + +def line_to_line(sa: Vec2, ea: Vec2, sb: Vec2, eb: Vec2): + """ + Renvoie la collision entre deux lignes. + """ + if sa.x == ea.x: + sa.x += 0.0001 + if sb.x == eb.x: + sb.x += 0.0001 + if sa.y == ea.y: + sa.y += 0.0001 + if sb.y == eb.y: + sb.y += 0.0001 + + divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y) + if divisor == 0: + uA = 0 + else: + uA = ((eb.x - sb.x) * (sa.y - sb.y) - (eb.y - sb.y) * (sa.x - sb.x)) / (divisor) + divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y) + if divisor == 0: + uB = 0 + else: + uB = ((ea.x - sa.x) * (sa.y - sb.y) - (ea.y - sa.y) * (sa.x - sb.x)) / (divisor) + if uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1: + return ( + Vec2((uA * (ea.x - sa.x)), (uA * (ea.y - sa.y))).length / (ea - sa).length + ) + return 1.0 + + +def line_to_aabb(start: Vec2, end: Vec2, aabb: AABB): + """ + Renvoie la collision entre une ligne et une AABB. + """ + left = line_to_line(start, end, aabb.min, Vec2(aabb.min.x, aabb.max.y)) + right = line_to_line(start, end, Vec2(aabb.max.x, aabb.min.y), aabb.max) + bottom = line_to_line(start, end, aabb.min, Vec2(aabb.max.x, aabb.min.y)) + top = line_to_line(start, end, Vec2(aabb.min.x, aabb.max.y), aabb.max) + t = min([left, right, bottom, top]) + if t == left: + normal = Vec2(-1, 0) + elif t == right: + normal = Vec2(1, 0) + elif t == bottom: + normal = Vec2(0, -1) + elif t == top: + normal = Vec2(0, 1) + else: + normal = Vec2(0, 0) + return t, normal + + +def aabb_to_aabb(moving: AABB, static: AABB, movement: Vec2): + """ + Renvoie la collision entre deux AABB. + """ + size = (moving.max - moving.min) / 2 + static = AABB(static.min - size, static.max + size, static.entity) + start_pos = moving.min + size + return line_to_aabb(start_pos, start_pos + movement, static) + + +def aabb_to_aabbs(moving: AABB, statics: list[AABB], movement: Vec2): + """ + Renvoie la collision entre deux AABB. + """ + t = 1.0 + normal = Vec2(0, 0) + entity = None + for static in statics: + if static.entity == moving.entity: + continue + result = aabb_to_aabb(moving, static, movement) + if result[0] < t: + t = result[0] + normal = result[1] + entity = static.entity + return t, normal, entity + + +def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False): + world = entity.world + aabb = AABB.from_entity(entity) + others = [ + AABB.from_entity(other) for other in world.query(Solid, Position, Scale, Origin) + ] + counter = 0 + while movement.length > 0.0001 and counter < 50: + t, normal, obstacle = aabb_to_aabbs(aabb, others, movement) + if t == 1.0: + step = movement + else: + step = movement * max(t - 0.000001, 0) + aabb.move(step) + aabb.entity_position(entity) + movement -= step + if normal.x != 0: + movement.x *= -1 + entity[Velocity].x *= -1 + if normal.y != 0: + movement.y *= -1 + entity[Velocity].y *= -1 + movement /= entity[Velocity] + if obstacle is not None and not disable_callback: + if not entity.get( + CollisionHandler, CollisionHandler(lambda e, o: True) + ).callback(entity, obstacle): + break + if not obstacle.get( + CollisionHandler, CollisionHandler(lambda e, o: True) + ).callback(obstacle, entity): + break + movement *= entity[Velocity] + counter += 1 + + +def __apply_velocity(world: World): + """ + Applique la vélocité a toutes les entitées. + """ + delta = world[Delta] + for entity in world.query(Velocity, Position, Scale, Origin): + move_entity(entity, entity[Velocity] * delta) + + +PLUGIN = GlobalPlugin( + [], + [__apply_velocity], + [], + [], +) diff --git a/src/plugins/render.py b/src/plugins/render.py index f8aec72..1827d77 100644 --- a/src/plugins/render.py +++ b/src/plugins/render.py @@ -4,9 +4,10 @@ Un plugin qui s'occupe de rendre des choses dans la fenetre. from typing import Optional import pygame -from engine import GlobalPlugin, KeepAlive +from engine import GlobalPlugin from engine.ecs import World from engine.math import Vec2 +from plugins import assets WIDTH = 1440 @@ -29,63 +30,124 @@ def calculate_surface_rect() -> tuple[float, float, float, float]: return offset, 0.0, target_width, float(height) -class Surface(KeepAlive, pygame.Surface): +class SpriteBundle: """ - Ressource qui stocke la surface de rendu du jeu. + Un assemblage de composants permettant de faire une sprite. """ - -class Sprite: - """ - Composant donnant la texture d'une entité, sa position et son ordre de rendu. - """ - - def __init__( - self, - texture: pygame.Surface, + def __new__( + cls, + texture: str, + order: float, position: Vec2 = Vec2(0), - order: float = -1.0, - area: Optional[tuple[float, float, float, float]] = None, + scale: Optional[Vec2] = None, origin: Vec2 = Vec2(0), ): - self.texture = texture - self.position = position - self.order = order - if area is None: - self.area = (0.0, 0.0, 1.0, 1.0) - else: - self.area = area - self.origin = origin + if scale is None: + surface = assets.load_texture(texture) + scale = Vec2(surface.get_width(), surface.get_height()) + return ( + Texture(texture), + Order(order), + Position(position), + Scale(scale), + Origin(origin), + ) -def __initialize(world: World): +class TextBundle: """ - Prépare le monde pour la gestion du rendu. + Un assemblage de composants permettant de faire un texte. """ - world.set(Surface((WIDTH, HEIGHT))) + + def __new__( + cls, + text: str, + order: float, + size: int = 50, + color: pygame.Color = pygame.Color(255, 255, 255), + position: Vec2 = Vec2(0), + origin: Vec2 = Vec2(0), + ): + return ( + Text(text), + TextSize(size), + TextColor(color), + Position(position), + Order(order), + Origin(origin), + ) -def __render(world: World): +class Texture(str): + """ + Composant donnant le nom de la texture d'une entité. + """ + + +class Order(float): + """ + Composant donnant l'ordre d'affichage d'une entité. + """ + + +class Position(Vec2): + """ + Composant donnant la position de l'origine d'un objet. + """ + + +class Scale(Vec2): + """ + Composant donnant la taille de la texture d'une entité. + """ + + +class Origin(Vec2): + """ + Composant définissant l'emplacement de l'origine d'un objet sur sa texture. + """ + + +class Text(str): + """ + Composant donnant le texte d'une entité. + """ + + +class TextSize(int): + """ + Composant donnant la taille du texte d'une entité. + """ + + +class TextColor(pygame.Color): + """ + Composant donnant la couleur du texte d'une entité. + """ + + +def __render(world: World, surface: pygame.Surface = pygame.Surface((WIDTH, HEIGHT))): """ Rend le monde du jeu sur la surface puis l'affiche sur la fenetre. """ # On rend le monde sur la surface - surface = world[Surface] - sprites = [entity[Sprite] for entity in world.query(Sprite)] - for sprite in sorted(sprites, key=lambda sprite: sprite.order): - original_size = Vec2(*sprite.texture.get_size()) - size = original_size * Vec2(*sprite.area[2:]) - position = sprite.position - (sprite.origin * size) - surface.blit( - sprite.texture, - (position.x, position.y), - ( - sprite.area[0] * original_size.x, - sprite.area[1] * original_size.y, - sprite.area[2] * original_size.x, - sprite.area[3] * original_size.y, - ), - ) + entities = world.query(Texture, Position, Order, Scale, Origin) + entities.update(world.query(Text, Position, Order, Origin, TextSize, TextColor)) + entities = sorted(entities, key=lambda entity: entity[Order]) + for entity in entities: + if Text in entity: + texture = assets.load_text( + entity[Text], entity[TextSize], entity[TextColor] + ) + scale = Scale(texture.get_width(), texture.get_height()) + else: + texture = entity[Texture] + texture = assets.load_texture(texture) + scale = entity[Scale] + texture = pygame.transform.scale(texture, (scale.x, scale.y)) + position = entity[Position] - entity[Origin] * scale + surface.blit(texture, (position.x, position.y)) # On affiche la surface sur la fenetre rect = calculate_surface_rect() @@ -99,7 +161,7 @@ def __render(world: World): PLUGIN = GlobalPlugin( - [__initialize], + [], [], [__render], [], diff --git a/src/plugins/smooth.py b/src/plugins/smooth.py deleted file mode 100644 index 051f9eb..0000000 --- a/src/plugins/smooth.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Un plugin permettant de faire des déplacements fluides des entités. -""" - -from engine import Plugin, World -from engine.math import Vec2 -from plugins.render import Sprite -from plugins.timing import Delta - - -class Target(Vec2): - """ - Composant donnant la position voulue de l'entité. - """ - - -class Speed(float): - """ - Composant donnant la vittesse de déplacement de l'entité. - """ - - -def __update_positions(world: World): - """ - Met à jour la position des entités pour se rapprocher de la position voulue. - """ - for entity in world.query(Sprite, Target): - sprite = entity[Sprite] - target = entity[Target] - speed = entity[Speed] if Speed in entity else Speed(10) - sprite.position = ( - sprite.position + (target - sprite.position) * world[Delta] * speed - ) - - -PLUGIN = Plugin( - [], - [__update_positions], - [], -) diff --git a/src/plugins/sound.py b/src/plugins/sound.py index 06597d7..f095244 100644 --- a/src/plugins/sound.py +++ b/src/plugins/sound.py @@ -5,65 +5,64 @@ Un plugin permettant de jouer des sons. from typing import Callable import pygame -from engine import GlobalPlugin, KeepAlive +from engine import GlobalPlugin from engine.ecs import Entity, World +from plugins import assets -class Channels(KeepAlive, dict[Entity, pygame.mixer.Channel]): - """ - Ressource qui stoque les sons actuellement joués dans le jeu. - """ - - -class Sound: +class Sound(str): """ Composant permettant de jouer un son. """ - def __init__( - self, - sound: pygame.mixer.Sound, - loop: bool = False, - volume: float = 1.0, - fade_ms: int = 0, - callback: Callable[[World, Entity], object] = lambda _w, _e: None, - ): - self.sound = sound - self.loop = loop - self.volume = volume - self.fade_ms = fade_ms + +class Volume(float): + """ + Composant donnant le volume d'un son. + """ + + +class Loop: + """ + Composant indiquant si le son joué par l'entité doit se relancer en boucle. + """ + + +class SoundCallback: + """ + Composant donnant une fonction qui sera appelée à la fin du son. + """ + + def __init__(self, callback: Callable[[World, Entity], object]): self.callback = callback -def __initialize(world: World): - """ - Ajoute les ressources utiles pour le plugin. - """ - world.set(Channels()) - - -def __update_sounds(world: World): +def __update_sounds( + world: World, + channels: dict[Entity, pygame.mixer.Channel] = {}, + cache: dict[str, pygame.mixer.Sound] = {}, +): """ Met à jour les sons du jeu. """ # Ajout des sons non gérés - channels = world[Channels] sound_entities = world.query(Sound) for entity in sound_entities: if entity not in channels: - sound = entity[Sound] - channel = sound.sound.play(sound.loop, fade_ms=sound.fade_ms) + sound_name = entity[Sound] + sound = assets.load_sound(sound_name) + channel = sound.play(Loop in entity) if channel is not None: # type: ignore - channel.set_volume(sound.volume) + channel.set_volume(entity.get(Volume, 1.0)) channels[entity] = channel # On supprime les sons qui sont arrêtés ou qui n'ont plus d'entité channels_to_remove: list[Entity] = [] for entity, channel in channels.items(): if not channel.get_busy() and Sound in entity: - callback = entity[Sound].callback + callback = entity.get(SoundCallback, SoundCallback(lambda w, e: None)) del entity[Sound] - callback(world, entity) + callback.callback(world, entity) channels_to_remove.append(entity) elif entity not in sound_entities: channel.stop() @@ -73,7 +72,7 @@ def __update_sounds(world: World): PLUGIN = GlobalPlugin( - [__initialize], + [], [], [__update_sounds], [], diff --git a/src/plugins/text.py b/src/plugins/text.py deleted file mode 100644 index 8d08c5c..0000000 --- a/src/plugins/text.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Un plugin permettant d'afficher du texte à l'écran. -""" - - -from typing import Optional -import pygame - -from engine import GlobalPlugin -from engine.ecs import World -from engine.math import Vec2 -from plugins.assets import Assets -from plugins.render import Sprite - - -class Text: - """ - Composant donnant le texte d'une entité, sa taille et sa couleur. - """ - - def __init__( - self, - text: str, - size: int = 50, - color: pygame.Color = pygame.Color(255, 255, 255), - position: Optional[Vec2] = None, - order: Optional[float] = None, - origin: Optional[Vec2] = None, - ): - self.text = text - self.size = size - self.color = color - self.position = position - self.order = order - self.origin = origin - - -def __render_texts(world: World): - """ - Rend les textes à l'écran. - - Pour rendre les textes, on moddifie le composant `Sprite` des entités. - """ - assets = world[Assets] - for entity in world.query(Text): - text = entity[Text] - texture = assets.get_text(text.size, text.text, text.color) - if Sprite in entity: - entity[Sprite].texture = texture - else: - entity[Sprite] = Sprite(texture) - if text.position is not None: - entity[Sprite].position = text.position - if text.order is not None: - entity[Sprite].order = text.order - if text.origin is not None: - entity[Sprite].origin = text.origin - - -PLUGIN = GlobalPlugin( - [], - [], - [__render_texts], - [], -) diff --git a/src/plugins/timing.py b/src/plugins/timing.py index 4d5d3d0..cfdbdcf 100644 --- a/src/plugins/timing.py +++ b/src/plugins/timing.py @@ -5,9 +5,8 @@ lancement du jeu et le temps depuis la dernière frame. from time import time -from typing import Callable from engine import GlobalPlugin, KeepAlive -from engine.ecs import Entity, World +from engine.ecs import World class GlobalTime(KeepAlive, float): @@ -28,16 +27,6 @@ class Delta(KeepAlive, float): """ -class TimedEvent: - """ - Composant permettant d'executer un callback après un certain temps. - """ - - def __init__(self, timer: float, callback: Callable[[World, Entity], object]): - self.timer = timer - self.callback = callback - - def __initialize(world: World): """ Initialise les ressources pour la gestion du temps. @@ -47,22 +36,13 @@ def __initialize(world: World): def __update(world: World): """ - Met à jour les ressources de temps et execute les `TimedEvent`. + Met à jour les ressources de temps. """ - # On met à jour le temps now = time() world[Delta] = delta = now - world[GlobalTime] world[GlobalTime] = now world[Time] += delta - # On met à jour les `TimedEvent` - for entity in world.query(TimedEvent): - event = entity[TimedEvent] - event.timer -= delta - if event.timer <= 0: - del entity[TimedEvent] - event.callback(world, entity) - PLUGIN = GlobalPlugin( [__initialize], diff --git a/src/plugins/writing.py b/src/plugins/writing.py deleted file mode 100644 index d3c1926..0000000 --- a/src/plugins/writing.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Definit un plugin qui crée un texte avec les touches frappées -""" - -from engine import Scene, World -from plugins.inputs import Pressed -from plugins.text import Text -from scenes import CLICK_SOUND - - -class Writing: - """ - Marque une entité comme un texte qui s'ecrit en fonction du clavier - """ - - def __init__( - self, accepted_chars: str, max_chars: int = 10, base_text: str = "" - ) -> None: - self.accepted_chars = accepted_chars - self.max_chars = max_chars - self.base_text = base_text - - -def __update(world: World): - """ - Met a jour les entitées contenant le composant Typing - """ - pressed = world[Pressed] - for entity in world.query(Writing, Text): - writing = entity[Writing] - text = entity[Text] - for key in pressed: - if key == "backspace" and text.text != writing.base_text: - text.text = text.text[:-1] - world.new_entity().set(CLICK_SOUND) - if text.text == "": - text.text = writing.base_text - if key.startswith("["): # pavé numerique - key = key[1] - if key in writing.accepted_chars and ( - text.text == writing.base_text or len(text.text) < writing.max_chars - ): - if text.text == writing.base_text: - text.text = key - else: - text.text += key - world.new_entity().set(CLICK_SOUND) - if text.text == "": - text.text = writing.base_text - - -PLUGIN = Scene( - [], - [__update], - [], -) diff --git a/src/scenes/__init__.py b/src/scenes/__init__.py index e3b14b0..44fb452 100644 --- a/src/scenes/__init__.py +++ b/src/scenes/__init__.py @@ -1,8 +1,3 @@ """ Module contenant toutes les scènes du jeu. """ - -from plugins.multisound import MultiSound - - -CLICK_SOUND = MultiSound("click/0", "click/1", "click/2") diff --git a/src/scenes/menu.py b/src/scenes/menu.py deleted file mode 100644 index 8256c88..0000000 --- a/src/scenes/menu.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -La scène du menu principal du jeu. - -Dans cette scène nous pouvons choisir le mode de jeu. -""" -from scenes import CLICK_SOUND, one_player -from engine import CurrentScene, KeepAlive, Scene -from engine.ecs import Entity, World -from engine.math import Vec2 -from plugins import render -from plugins import assets as plugin_assets -from plugins.assets import Assets -from plugins.click import Clickable -from plugins.hover import HoveredTexture -from plugins.render import Sprite - - -def __create_button(world: World, assets: Assets, i: int, name: str): - """ - Ajoute un bouton au monde. - """ - world.new_entity().set( - Sprite( - assets.error_texture, - Vec2(500 + 450 * i, render.HEIGHT / 2), - 1, - origin=Vec2(0.5), - ), - HoveredTexture( - assets.get_texture(f"button_{name}"), - assets.get_texture(f"button_{name}_hover"), - ), - Clickable(lambda world, entity: __on_click_butons(world, entity, name)), - ) - - -def __on_click_butons(world: World, _entity: Entity, name: str): - """ - Fonction qui s'execute quand on clique sur un bouton. - """ - match name: - case "one_player": - world[CurrentScene] = one_player.SCENE - case "two_player": - pass - case _: - pass - world.new_entity().set(KeepAlive(), CLICK_SOUND) - - -def __spawn_elements(world: World): - """ - Ajoute les éléments du menu dans le monde. - """ - assets = world[Assets] - world.new_entity().set(Sprite(assets.get_texture("background"))) - scenes_name = ["one_player", "two_player"] - for i, name in enumerate(scenes_name): - __create_button(world, assets, i, name) - - -SCENE = plugin_assets.loading_scene( - Scene( - [__spawn_elements], - [], - [], - ), - "menu", -) diff --git a/src/scenes/one_player.py b/src/scenes/one_player.py deleted file mode 100644 index 834e9ff..0000000 --- a/src/scenes/one_player.py +++ /dev/null @@ -1,22 +0,0 @@ -from engine import Scene -from engine.ecs import World -from plugins import assets as plugin_assets -from plugins.render import Sprite - - -def __spawn_elements(world: World): - """ - Ajoute les éléments du menu dans le monde. - """ - assets = world[plugin_assets.Assets] - world.new_entity().set(Sprite(assets.get_texture("background"))) - - -SCENE = plugin_assets.loading_scene( - Scene( - [], - [], - [], - ), - "one_player", -)