From 26bfb318ffa4026e47d4f6b417e3459f95868fc8 Mon Sep 17 00:00:00 2001 From: Tipragot Date: Mon, 30 Oct 2023 17:30:56 +0100 Subject: [PATCH] =?UTF-8?q?Nouveau=20syst=C3=A8me=20d'ECS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 5 +- src/engine.py | 857 ++------------------------------- src/main.py | 18 +- src/plugins/__init__.py | 9 +- src/plugins/smooth.py | 37 -- src/plugins/typing.py | 37 -- src/scenes/__init__.py | 3 - src/scenes/classique.py | 227 --------- src/scenes/directory_search.py | 378 --------------- src/scenes/menteur.py | 236 --------- src/scenes/menu.py | 57 --- 11 files changed, 45 insertions(+), 1819 deletions(-) delete mode 100644 src/plugins/smooth.py delete mode 100644 src/plugins/typing.py delete mode 100644 src/scenes/__init__.py delete mode 100644 src/scenes/classique.py delete mode 100644 src/scenes/directory_search.py delete mode 100644 src/scenes/menteur.py delete mode 100644 src/scenes/menu.py diff --git a/.vscode/settings.json b/.vscode/settings.json index c438c22..a8c4fd9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,13 +9,10 @@ "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.inlayHints.pytestParameters": true, "python.analysis.typeCheckingMode": "strict", - "python.analysis.diagnosticSeverityOverrides": { - "reportPrivateUsage": "none" - }, "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" }, "pylint.args": [ - "--disable=broad-exception-caught,protected-access,undefined-variable,import-error,unused-import,no-member", + "--disable=protected-access,undefined-variable,import-error,unused-import,no-member", ], } \ No newline at end of file diff --git a/src/engine.py b/src/engine.py index c604f32..30460ae 100644 --- a/src/engine.py +++ b/src/engine.py @@ -1,614 +1,16 @@ """ -Un moteur de jeu inspiré de bevy. +Permet de lancer un jeu et de gérer la boucle principale de celui-ci. """ -import json -import math -import os -import random -from typing import Callable, Optional, Sequence, SupportsFloat, TypeVar, Union -from time import time -import pygame +from typing import Callable - -class World: - """ - Un monde contenant toutes les données du jeu. - """ - - __T = TypeVar("__T") - """ - Permet d'indiquer le type d'un composant ou d'une ressource. - """ - - def __init__(self): - """ - Constructeur d'un nouveau monde vide. - """ - self._entities: dict[type[object], set[Entity]] = {} - self.__resources: dict[type[object], object] = {} - - def create_entity(self, *components: object) -> "Entity": - """ - Crée une nouvelle entité avec les composants donnés et l'ajoute au monde. - - Paramètres: - *components: les composants de l'entité. - - Retourne: - L'entité. - """ - return Entity(self, *components) - - def __setitem__(self, resource_type: type[__T], resource: __T): - if resource_type != type(resource): - raise TypeError() - self.__resources[resource_type] = resource - - def __getitem__(self, resource_type: type[__T]) -> __T: - return self.__resources[resource_type] # type: ignore - - def __contains__(self, resource_type: type[object]) -> bool: - return resource_type in self.__resources - - def __delitem__(self, resource_type: type[object]): - del self.__resources[resource_type] - - def query( - self, *component_types: type[object], without: Sequence[type[object]] = () - ) -> set["Entity"]: - """ - Renvoie les entités qui ont tous les composants de type *component_types - et qui n'ont aucun des composants de type *without. - - Paramètres: - *component_types: les types de composants des entités a renvoyer. - without: les types de composants des entités a exclure. - - Retourne: - Les entités qui correspondent aux critères. - """ - if len(component_types) == 0: - return set() - entities = set(self._entities.get(component_types[0], set())) - for component_type in component_types[1:]: - entities.intersection_update(self._entities.get(component_type, set())) - for component_type in without: - entities.difference_update(self._entities.get(component_type, set())) - return entities - - -class Entity: - """ - Une entité du monde. Elle contient des composants. - """ - - __T = TypeVar("__T") - """ - Permet d'indiquer le type d'un composant. - """ - - def __init__(self, world: World, *components: object): - """ - Crée une nouvelle entité avec les composants donnés et l'ajoute au monde. - - Paramètres: - world: le monde dans lequel ajouter l'entité - *components: les composants à ajouter à l'entité. - """ - self.__world = world - self.__components: dict[type[object], object] = {} - for component in components: - self[type(component)] = component - - def __setitem__(self, component_type: type[__T], component: __T): - if component_type != type(component): - raise TypeError() - self.__components[component_type] = component - self.__world._entities.setdefault(component_type, set()).add(self) - - def __getitem__(self, component_type: type[__T]) -> __T: - return self.__components[component_type] # type: ignore - - def __contains__(self, component_type: type[object]) -> bool: - return component_type in self.__components - - def __delitem__(self, component_type: type[object]): - if self.__components.pop(component_type, None) is not None: - self.__world._entities[component_type].remove(self) - if len(self.__world._entities[component_type]) == 0: - del self.__world._entities[component_type] - - def __repr__(self) -> str: - return f"Entity({id(self)})" - - -class Vec2: - """ - Un vecteur 2D - """ - - def __init__(self, *args: Union[SupportsFloat, "Vec2"]): - 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 - else: - self.x = float(args[0]) - self.y = float(args[0]) - 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, SupportsFloat): - return Vec2(self.x + float(other), self.y + float(other)) - 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, SupportsFloat): - return Vec2(self.x - float(other), self.y - float(other)) - 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, SupportsFloat): - return Vec2(self.x * float(other), self.y * float(other)) - 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, SupportsFloat): - return Vec2(self.x / float(other), self.y / float(other)) - raise ValueError( - f"Unsupported operand type(s) for /: 'Vec2' and '{type(other)}'" - ) - - 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})" - - -class Display: - """ - Une classe utilitaire pour la gestion de la fenêtre du jeu. - """ - - WIDTH = 1440.0 - HEIGHT = 1080.0 - RATIO = WIDTH / HEIGHT - INVERT_RATIO = HEIGHT / WIDTH - - @staticmethod - def _calculate_surface_rect() -> tuple[float, float, float, float]: - """ - Calcule et renvoit le rectangle de la surface dans la fenêtre du jeu. - """ - 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 - - -class Game: - """ - Un ressource qui représente le jeu actuel. - """ - - def __init__(self): - self.stop_requested = False - self.next_scene: Optional[str] = None - - def stop(self): - """ - Demande l'arrêt du jeu. - """ - self.stop_requested = True - - def change_scene(self, scene_name: str): - """ - Demande un changement de scène. - - Paramètres: - scene_name: Le nom de la scène dans laquelle aller. - """ - self.next_scene = scene_name - - -class Assets: - """ - Resource qui permet la gestion des assets. - """ - - def __init__(self, surface: pygame.Surface): - # 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(surface) - - # Cache des ressources - self.__textures: dict[str, pygame.Surface] = {} - self.__fonts: dict[int, pygame.font.Font] = {} - self.__sounds: dict[str, pygame.mixer.Sound] = {} - - def get_texture(self, name: str) -> pygame.Surface: - """ - Renvoie la texture qui correspond au nom *name*. - - Paramètres: - name: Le nom de la texture. - - Retourne: - La texture qui correspond au nom *name*. - """ - texture = self.__textures.get(name) - if texture is None: - if os.path.exists(f"assets/textures/{name}"): - texture = pygame.image.load(f"assets/textures/{name}") - if not name.startswith("animations/"): - texture = texture.convert_alpha() - self.__textures[name] = texture - return texture - return self.__error_texture - return texture - - def get_texture_size(self, name: str) -> Vec2: - """ - Renvoie la taille de la texture qui correspond au nom *name*. - - Paramètres: - name: Le nom de la texture. - - Retourne: - La taille de la texture qui correspond au nom *name*. - """ - return Vec2(*self.get_texture(name).get_size()) - - def get_font(self, size: int) -> pygame.font.Font: - """ - Renvoie la police qui correspond à la taille *size*. - - Paramètres: - size: La taille de la police. - - Retourne: - La police qui correspond à la taille *size*. - """ - font = self.__fonts.get(size) - if font is None: - font = pygame.font.Font("assets/font.ttf", size) - self.__fonts[size] = font - return font - - def get_text_size(self, text: str, size: int) -> Vec2: - """ - Renvoie la taille d'un texte avec une certaine taille de police. - - Paramètres: - text: Le texte. - size: La taille de la police. - - Retourne: - La taille d'un texte avec une certaine taille de police. - """ - return Vec2(*self.get_font(size).size(text)) - - def get_sound(self, name: str) -> pygame.mixer.Sound: - """ - Renvoie le son qui correspond au nom *name*. - - Paramètres: - name: Le nom du son. - - Retourne: - Le son qui correspond au nom *name*. - """ - sound = self.__sounds.get(name) - if sound is None: - sound = pygame.mixer.Sound(f"assets/sounds/{name}") - self.__sounds[name] = sound - return sound - - -class Time(float): - """ - Ressource qui represente le temps depuis 1900. - """ - - -class Delta(float): - """ - Ressource qui represente le temps depuis la dernière frame. - """ - - -class Keyboard: - """ - Ressource qui représente les entrées utilisateurs sur le clavier à la frame actuelle. - """ - - def __init__(self): - self.keys: set[str] = set() - self.pressed: set[str] = set() - self.released: set[str] = set() - - 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): - self.buttons: set[int] = set() - self.pressed: set[int] = set() - self.released: set[int] = set() - self.position: Vec2 = Vec2(0.0, 0.0) - self.delta: Vec2 = Vec2(0.0, 0.0) - - 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 - - -class Position(Vec2): - """ - Composant qui représente la position d'une entité. - """ - - -class Centered: - """ - Composant permettant de dire que l'affichage de l'entité doit être centré. - """ - - -class Offset(Vec2): - """ - Composant qui represente un décalage de la position d'une entité. - """ - - -class Order(int): - """ - Composant qui represente l'ordre d'affichage d'une entité. - """ - - -class Texture(str): - """ - Composant qui rerpésente la texture d'une entité. - """ - - -class HoveredTexture(Texture): - """ - Composant qui represente la texture lorsque l'entité est survolée. - """ - - -class Animation: - """ - Composant qui représente une animation. - """ - - def __init__( - self, - name: str, - callback: Callable[[World, Entity], object] = lambda _w, _e: None, - ): - self.name = name - self.callback = callback - - with open( - f"assets/textures/animations/{name}/info.json", - "r", - encoding="utf-8", - ) as f: - info = json.load(f) - self.end_image: str = info.get("end_image", "") - self.offset = Offset(info["offset"]["x"], info["offset"]["y"]) - self.frame_count: int = info["frame_count"] - self.fps: int = info["fps"] - self.time = 0.0 - - -class Text(str): - """ - Composant qui represente un texte. - """ - - -class TextSize(int): - """ - Composant qui represente la taille d'un texte. - """ - - -class Color(pygame.Color): - """ - Composant qui represente la couleur d'une entité. - """ - - -class HoverEnter: - """ - Composant qui marque un entité comme commencée à être survolée. - """ - - -class Hovered: - """ - Composant qui marque un entité comme survolée. - """ - - -class HoverExit: - """ - Composant qui marque un entité comme arreté d'être survolée. - """ - - -class Clickable: - """ - Composant qui marque un entité comme pouvant etre cliquer. - """ - - def __init__(self, callback: Callable[[World, Entity], object]): - self.callback = callback - - -class Sound: - """ - Composant qui une entité emettrant un son. - """ - - def __init__( - self, - name: str, - volume: float = 0.5, - loop: bool = False, - callback: Callable[[World, Entity], object] = lambda _w, _e: None, - stop_on_remove: bool = False, - ) -> None: - self.name = name - - if os.path.isdir(f"assets/sounds/{name}"): - list_files = os.listdir(f"assets/sounds/{name}") - random_file = random.choice(list_files) - self.name = f"{name}/{random_file}" - - self.volume = volume - self.loop = loop - self.callback = callback - self.stop_on_remove = stop_on_remove +from ecs import World class Scene: """ - Une scène dans le jeu. + Une scène du jeu. """ def __init__( @@ -629,241 +31,52 @@ class Scene: ) -def start_game( - scenes: dict[str, Scene], - start_scene: str, - *, - title: str = "Game", -) -> None: +class CurrentScene(str): """ - Lance le moteur de jeu. + Resource qui permet de savoir et de changer la scène actuelle. """ - # Initialisation de pygame - pygame.init() - if os.path.exists("icon.png"): - pygame.display.set_icon(pygame.image.load("icon.png")) - pygame.display.set_caption(title) - pygame.display.set_mode((800, 600), pygame.RESIZABLE) - surface = pygame.Surface((Display.WIDTH, Display.HEIGHT)) - keyboard = Keyboard() - mouse = Mouse() - # Chargements des assets - assets = Assets(surface) - # creation des channels pour les sons - channels: dict[Entity, tuple[bool, pygame.mixer.Channel]] = {} +class KeepAlive: + """ + Composant qui marque une entité comme n'étant pas détruit lors + d'un changement de scène. + """ - # On récupère la première scène - scene = scenes.get(start_scene) - # Tant qu'il y a des scènes a executer on les executent +def start_game(scenes: dict[str, Scene], scene_name: str): + """ + Lance un jeu. + + Paramètres: + - `scenes`: un dictionnaire contenant les scènes du jeu. + - `scene_name`: le nom de la scène à charger en premier lors du lancement du jeu. + """ + world = World() + world[CurrentScene] = CurrentScene(scene_name) + scene = scenes.get(scene_name) while scene is not None: - # Initialisation du monde - world = World() - world[Game] = Game() - world[Assets] = assets - world[Keyboard] = keyboard - world[Mouse] = mouse - world[Time] = Time(time()) + # On retire les ressources de l'ancienne scène + for resource in world: + if not isinstance(resource, KeepAlive): + world.remove(type(resource)) - # Initialisation de la scène + # On retire les entité de l'ancienne scène + for entity in world.query(without=(KeepAlive,)): + entity.destroy() + + # On initialise la nouvelle scene for system in scene.init_systems: system(world) - # Tant que la scène n'est pas terminé - while world[Game].next_scene is None and not world[Game].stop_requested: - # On gère les évenements pygame - keyboard.pressed.clear() - keyboard.released.clear() - mouse.pressed.clear() - mouse.released.clear() - last_position = Vec2(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) - 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: - 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) - elif event.type == pygame.MOUSEMOTION: - rect = Display._calculate_surface_rect() - mouse.position = Vec2( - ((event.pos[0] - rect[0]) / rect[2]) * Display.WIDTH, - ((event.pos[1] - rect[1]) / rect[3]) * Display.HEIGHT, - ) - mouse.delta = mouse.position - last_position - - # On vérifie le survol des textures et textes - for entity in world.query(Position, Texture): - # Récupération de la position et taille de l'entité - position: Vec2 = entity[Position] - if Offset in entity: - position = position + entity[Offset] - size = assets.get_texture_size(entity[Texture]) - if Centered in entity: - position -= size / 2 - - # On détermine si la souris est sur l'entité - if ( - mouse.position.x >= position.x - and mouse.position.x <= position.x + size.x - and mouse.position.y >= position.y - and mouse.position.y <= position.y + size.y - ): - if Hovered not in entity: - entity[HoverEnter] = HoverEnter() - else: - del entity[HoverEnter] - entity[Hovered] = Hovered() - else: - if Hovered in entity: - entity[HoverExit] = HoverExit() - else: - del entity[HoverExit] - del entity[Hovered] - - # On met à jour le temps et delta - now = time() - world[Delta] = Delta(now - world[Time]) - world[Time] = Time(now) - - # On execute les objets clickables - if mouse.is_button_pressed(1): - for entity in world.query(Clickable, Hovered): - entity[Clickable].callback(world, entity) - - # On met à jour la scène + # Tant que la scène n'est pas terminé on la met à jour + while world.get(CurrentScene, "") == scene_name: for system in scene.update_systems: system(world) - # Gestion des sons en cours - sound_entities = world.query(Sound) - entities_to_delete: list[Entity] = [] - for entity, (stop_on_remove, channel) in channels.items(): - if Sound in entity: - entity_sound = entity[Sound] - channel.set_volume(entity_sound.volume) - if not channel.get_busy(): - entities_to_delete.append(entity) - del entity[Sound] - entity_sound.callback(world, entity) - continue - if stop_on_remove and entity not in sound_entities: - entities_to_delete.append(entity) - channel.stop() - for entity in entities_to_delete: - del channels[entity] - - # Ajout des sons non gérés - for entity in world.query(Sound): - if entity not in channels: - entity_sound = entity[Sound] - sound = assets.get_sound(entity_sound.name) - channel = sound.play(loops=-1 if entity_sound.loop else 0) - if channel is None: # type: ignore - continue - channel.set_volume(entity_sound.volume) - channels[entity] = entity_sound.stop_on_remove, channel - - # Mise à jour des animations - for entity in world.query(Animation): - animation = entity[Animation] - if animation.time == 0: - if animation.end_image == "" and Texture in entity: - animation.end_image = entity[Texture] - animation.time += world[Delta] - frame_index = int(animation.time * animation.fps) - if frame_index >= animation.frame_count: - entity[Texture] = Texture(animation.end_image) - del entity[Animation] - del entity[Offset] - animation.callback(world, entity) - else: - entity[Offset] = Offset(animation.offset.x, animation.offset.y) - entity[Texture] = Texture( - f"animations/{animation.name}/{frame_index:04}.png" - ) - - # Rendu du monde - for entity in sorted(world.query(Order, Position), key=lambda e: e[Order]): - # Récupération de la position de l'entité - position: Vec2 = entity[Position] - if Offset in entity: - position = position + entity[Offset] - centered = Centered in entity - - # Affichage de la texture - if Texture in entity: - if HoveredTexture in entity and Hovered in entity: - texture = assets.get_texture(entity[HoveredTexture]) - else: - texture = assets.get_texture(entity[Texture]) - surface.blit( - texture, - ( - position.x - texture.get_width() / 2 - if centered - else position.x, - position.y - texture.get_height() / 2 - if centered - else position.y, - ), - ) - - # Affichage des textes - if Text in entity: - color = entity[Color] if Color in entity else Color(255, 255, 255) - size = entity[TextSize] if TextSize in entity else 50 - font = assets.get_font(size) - font_surface = font.render(entity[Text], True, color) - surface.blit( - font_surface, - ( - position.x - font_surface.get_width() / 2 - if centered - else position.x, - position.y - font_surface.get_height() / 2 - if centered - else position.y, - ), - ) - - # Mise a jour de la fenêtre - rect = Display._calculate_surface_rect() - pygame.transform.set_smoothscale_backend("MMX") - pygame.transform.smoothscale( - surface, - (rect[2], rect[3]), - pygame.display.get_surface().subsurface(rect), - ) - pygame.display.flip() - surface.fill((0, 0, 0)) - - # Arrêt de la scène + # On arrete la scene for system in scene.stop_systems: system(world) # Récupération de la scène suivante - next_scene = world[Game].next_scene - if next_scene is not None: - scene = scenes.get(next_scene) - else: - scene = None - - # Arrêt du jeu - pygame.quit() + scene = scenes.get(world.get(CurrentScene, "")) diff --git a/src/main.py b/src/main.py index 58edf92..acd8e3d 100644 --- a/src/main.py +++ b/src/main.py @@ -1,19 +1,3 @@ """ -Example de l'utilisation du moteur de jeu. +Module d'exemple de l'utilisation du moteur de jeu. """ - - -from engine import start_game -from scenes import classique, menteur, menu, directory_search - - -start_game( - { - "menu": menu.SCENE, - "classique": classique.SCENE, - "menteur": menteur.SCENE, - "histoire": directory_search.SCENE, - }, - "menu", - title="Guess The Number", -) diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py index 25d84e6..63ddc3a 100644 --- a/src/plugins/__init__.py +++ b/src/plugins/__init__.py @@ -1,3 +1,10 @@ """ -Contient des plugins pour certaines features du jeu. +Module contenant tous les plugins du jeu. + +Un plugin est une scène pouvant être ajouté a d'autres scènes +afin d'ajouter des fonctionnalités au jeu. + +Le but est de faire en sorte que les plugins soient génériques +afin de pouvoir les utilisers dans plusieurs scènes et donc +éviter de répéter plusieurs fois le même code. """ diff --git a/src/plugins/smooth.py b/src/plugins/smooth.py deleted file mode 100644 index 0f9681e..0000000 --- a/src/plugins/smooth.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Un plugin permettant de faire des déplacements fluides des entités. -""" - -from engine import Delta, Position, Scene, Vec2, World - - -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(Position, Target): - position = entity[Position] - target = entity[Target] - speed = entity[Speed] if Speed in entity else Speed(10) - entity[Position] = Position( - position + (target - position) * world[Delta] * speed - ) - - -PLUGIN = Scene( - [], - [__update_positions], - [], -) diff --git a/src/plugins/typing.py b/src/plugins/typing.py deleted file mode 100644 index 6379dd3..0000000 --- a/src/plugins/typing.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Definit un plugin qui crée un texte avec les touches frappées -""" - -from engine import Keyboard, Scene, Sound, Text, World - - -class Typing(str): - """ - Marque une entité comme un texte qui s'ecrit en fonction du clavier - """ - - -def __update(world: World): - """ - Met a jour les entitées contenant le composant Typing - """ - keyboard = world[Keyboard] - for entity in world.query(Typing, Text): - text = entity[Text] - for key in keyboard.pressed: - if key == "backspace": - world.create_entity(Sound("click")) - text = text[:-1] - if key.startswith("["): # pavé numerique - key = key[1] - if key in entity[Typing]: - world.create_entity(Sound("click")) - text += key - entity[Text] = Text(text) - - -PLUGIN = Scene( - [], - [__update], - [], -) diff --git a/src/scenes/__init__.py b/src/scenes/__init__.py deleted file mode 100644 index 40fe25c..0000000 --- a/src/scenes/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Contient toutes les scènes du jeu. -""" diff --git a/src/scenes/classique.py b/src/scenes/classique.py deleted file mode 100644 index de6d51d..0000000 --- a/src/scenes/classique.py +++ /dev/null @@ -1,227 +0,0 @@ -""" -Définis la scène du jeu classique, sans variante. -""" - -import random -from plugins import typing -from engine import ( - Centered, - Clickable, - Color, - Display, - Entity, - Game, - HoveredTexture, - Keyboard, - Order, - Position, - Scene, - Sound, - Text, - TextSize, - Texture, - World, -) - -COLOR_TEXT = Color(66, 39, 148) - - -class RandomNumber(int): - """ - La ressource qui est le nombre a deviner. - """ - - -class TextDialogue: - """ - Le component qui declare l'entitee Text qui affiche le plus petit ou le plus grand - """ - - -class NombreEssai(int): - """ - Le component qui declare le nombre d'essai - """ - - -class NombreEssaiText: - """ - Le component qui affiche le nombre d'essai - """ - - -class IsRunning: - """ - Le component qui indique si le jeu est en cours - """ - - -def __initialize_world(world: World): - """ - Initialise le monde du menu. - """ - - # Fond d'ecran - world.create_entity( - Position(), - Order(0), - Texture("classique/background.png"), - ) - - # Bouton valider/rejouer - world.create_entity( - Position(Display.WIDTH / 2, 875), - Order(1), - Centered(), - Texture("classique/valider.png"), - HoveredTexture("classique/valider_hover.png"), - Clickable(lambda world, _: _update(world)), - ) - - # Zone de saisie - world.create_entity( - Position(Display.WIDTH / 2, 750), - Order(2), - Centered(), - typing.Typing("1234567890"), - Text(""), - COLOR_TEXT, - TextSize(150), - ) - - # Text qui dit si ton nombre et trop grand ou trop petit - world.create_entity( - Position(Display.WIDTH / 2, 500), - Order(3), - Centered(), - TextDialogue(), - TextSize(150), - COLOR_TEXT, - Text("Devine le nombre..."), - ) - - # Text qui affiche le nombre d'essai - world.create_entity( - Position(Display.WIDTH / 2 - 100, 150), - Order(4), - TextSize(100), - NombreEssaiText(), - COLOR_TEXT, - Text("il reste : 7 essais"), - ) - - # Bouton pour revenir au menu - world.create_entity( - Order(11), - Position(150, 150), - Texture("classique/arrow.png"), - Clickable(on_menu_button), - HoveredTexture("classique/arrow_hover.png"), - ) - - # Les ressources. - world[NombreEssai] = NombreEssai(7) - world[RandomNumber] = RandomNumber(random.randint(0, 99)) - world[IsRunning] = IsRunning() - - -def on_menu_button(world: World, entity: Entity): - """ - Fonction qui s'execute quand on clique sur un bouton. - """ - world[Game].change_scene("menu") - entity[Sound] = Sound("click") - - -def _update(world: World): - """ - Verifie si le nombre donné est le meme que celui que l'on a choisi. - Boucle du jeu. - """ - - world.create_entity(Sound("menu_click.wav")) - - # si le jeu s'est arrete. - if IsRunning not in world: - # on relance le jeu. - world[Game].change_scene("classique") - - for entity in world.query(typing.Typing, Text): - # One efface le nombre. - number: str = entity[Text] - entity[Text] = Text("") - - # On gere le l'input de l'utilisateur. - for entity_text in world.query(TextDialogue): - if number == "": # si il a rien evoyé. - entity_text[Text] = Text("tu doit entrer un nombre !") - return - if world[RandomNumber] == int(number): # si il a trouve le nombre. - end_game(world, "Gagné") - return - elif world[NombreEssai] <= 1: # si il n'a plus d'essai. - end_game(world, "Perdu") - return - elif world[RandomNumber] > int(number): # si le nombre est trop petit. - entity_text[Text] = Text("Plus grand...") - else: # si le nombre est trop grand. - entity_text[Text] = Text("Plus petit...") - - # on update l'affichage du nombre d'essai. - world[NombreEssai] = NombreEssai(world[NombreEssai] - 1) - for entity in world.query(NombreEssaiText): - entity[Text] = Text( - f"il reste : {world[NombreEssai]} essai{'s' if world[NombreEssai] != 1 else ''}" - ) - - -def end_game(world: World, state: str): - """ - fonction applé quand le jeu est fini. - """ - del world[IsRunning] # le jeu est fini. - - # On joue le son - if state == "Gagné": - world.create_entity(Sound("win_sound.wav")) - else: - world.create_entity(Sound("lose_sound.wav")) - - # On affiche le message de fin. - for entity_text in world.query(TextDialogue): - entity_text[Text] = Text(f"{state} !") - - # On empeche de pourvoir continuer le jeu. - for entity in world.query(typing.Typing, Text): - del entity[typing.Typing] - - if state == "Gagné": - for entity in world.query(NombreEssaiText): - entity[Text] = Text("") - else: - for entity in world.query(NombreEssaiText): - entity[Text] = Text(" plus d'essais") - - # on change la texture du button submit. - for entity in world.query(Clickable, Centered): - entity[Texture] = Texture("classique/play_again.png") - entity[HoveredTexture] = HoveredTexture("classique/play_again_hover.png") - - -def _check_return(world: World): - """ - Verifie si la touche entrée est appuyée. - """ - keyboard = world[Keyboard] - if keyboard.is_key_pressed("return") or keyboard.is_key_pressed("enter"): - _update(world) - - -SCENE = ( - Scene( - [__initialize_world], - [_check_return], - [], - ) - + typing.PLUGIN -) diff --git a/src/scenes/directory_search.py b/src/scenes/directory_search.py deleted file mode 100644 index cd50f94..0000000 --- a/src/scenes/directory_search.py +++ /dev/null @@ -1,378 +0,0 @@ -""" -Scène du jeu dans lequel on se cache de Edmond dans les dossiers. -""" - -from enum import Enum -import random -from engine import ( - Animation, - Centered, - Delta, - Display, - Entity, - Hovered, - Mouse, - Order, - Position, - Scene, - Sound, - Text, - TextSize, - Texture, - Vec2, - World, -) -from plugins import smooth - - -LINES = 3 -COLUMNS = 5 -SPACING = 200 - - -class State(Enum): - """ - Etat de la scène. - """ - - MOVING = 0 - SEARCHING = 1 - GAME_OVER = 2 - - -class SelectedDirectory: - """ - Une ressource qui stoque le dossier selectionné pour le déplacement. - """ - - def __init__(self, entity: Entity, start_position: Vec2): - self.entity = entity - self.position = start_position - - -class AttackTimer(float): - """ - Ressource qui stoque un timer pour l'attaque. - """ - - -class AttackSpeed(float): - """ - Ressource qui dit le temps de l'attaque. - """ - - -class DirectoryPosition: - """ - La position d'un dossier dans la grille. - """ - - def __init__(self, x: int, y: int): - self.x = x - self.y = y - - def __eq__(self, value: object) -> bool: - if isinstance(value, DirectoryPosition): - return self.x == value.x and self.y == value.y - return False - - def screen_position(self) -> Vec2: - """ - Calcule la position de l'entité sur l'ecran. - """ - size = Vec2(SPACING) - offset = -(size * Vec2(COLUMNS - 1, LINES - 1) / 2) - first_position = Vec2(Display.WIDTH / 2, Display.HEIGHT / 2) + offset - return first_position + Vec2(self.x, self.y) * size - - -class AttackPoint(DirectoryPosition): - """ - Composant qui marque un point d'attaque. - """ - - -class DirectoryName: - """ - Composant qui marque une entité comme étant le nom d'un dossier. - """ - - def __init__(self, entity: Entity): - self.entity = entity - - -class UserDirectory: - """ - Composant qui marque le dossier que l'utilisateur doit protéger. - """ - - -class GameStarted: - """ - Une ressource qui permet de savoir que le jeu commence. - """ - - -def __change_folders_speeds(world: World, _e: Entity): - """ - Change les vitesses des dossiers. - """ - for entity in world.query(DirectoryPosition, smooth.Speed): - entity[smooth.Speed] = smooth.Speed(random.uniform(2.0, 2.5)) - for entity in world.query(TextSize): - entity[TextSize] = TextSize(40) - - -def __remove_folders_speeds(world: World): - """ - Supprime les vitesses des dossiers. - """ - if GameStarted not in world and world[AttackTimer] >= 3.0: - for entity in world.query(DirectoryPosition, smooth.Speed): - del entity[smooth.Speed] - world[GameStarted] = GameStarted() - - -def __initialize_world(world: World): - """ - Initialise le monde de la scène. - """ - world[State] = State.MOVING - world[AttackTimer] = AttackTimer(0.0) - world[AttackSpeed] = AttackSpeed(5.0) - - world.create_entity( - Position(), - Order(0), - Animation("fade_desktop", __change_folders_speeds), - ) - - names = [ - "Classique", - "Menteur", - "Tricheur", - "Histoire", - "Je t'aime", - "Hello", - "Cheval", - "Defender", - "Dansons", - "Secrets", - "Edmond", - "Mon Amour", - "Melatonin", - "Films", - "Cinéma", - ] - - positions = [ - Position(40 + (7 * 180) + 48, 35 + (5 * 166) + 38), - Position(40 + (5 * 180) + 48, 35 + (5 * 166) + 38), - Position(40 + (4 * 180) + 48, 35 + (4 * 166) + 38), - Position(40 + (3 * 180) + 48, 35 + (5 * 166) + 38), - Position(40 + (1 * 180) + 48, 35 + (5 * 166) + 38), - Position(40 + (6 * 180) + 48, 35 + (2 * 166) + 38), - Position(40 + (5 * 180) + 48, 35 + (3 * 166) + 38), - Position(40 + (4 * 180) + 48, 35 + (2 * 166) + 38), - Position(40 + (2 * 180) + 48, 35 + (4 * 166) + 38), - Position(40 + (1 * 180) + 48, 35 + (2 * 166) + 38), - Position(40 + (7 * 180) + 48, 35 + (1 * 166) + 38), - Position(40 + (5 * 180) + 48, 35 + (1 * 166) + 38), - Position(40 + (3 * 180) + 48, 35 + (0 * 166) + 38), - Position(40 + (2 * 180) + 48, 35 + (1 * 166) + 38), - Position(40 + (0 * 180) + 48, 35 + (1 * 166) + 38), - ] - - for y in range(LINES): - for x in range(COLUMNS): - position = DirectoryPosition(x, y) - entity = world.create_entity( - positions.pop(), - smooth.Speed(0), - Order(1), - Centered(), - Texture("directory.png"), - position, - ) - - if x == 2 and y == 1: - entity[UserDirectory] = UserDirectory() - entity[Texture] = Texture("user_directory.png") - - world.create_entity( - Position(0, 0), - Order(1), - Centered(), - Text(names.pop()), - TextSize(0), - DirectoryName(entity), - ) - - -def __attacks(world: World): - """ - Déclenche les attaques de Edmond. - """ - if world[State] == State.GAME_OVER: - return - world[AttackTimer] = AttackTimer(world[AttackTimer] + world[Delta]) - timer = world[AttackTimer] - if timer >= world[AttackSpeed] and world[State] == State.MOVING: - world[State] = State.SEARCHING - for entity in world.query(AttackPoint): - position = entity[AttackPoint] - for directory_entity in world.query(DirectoryPosition): - if directory_entity[DirectoryPosition] == position: - if UserDirectory in directory_entity: - directory_entity[Animation] = Animation( - "search_directory_failed" - ) - world[State] = State.GAME_OVER - else: - directory_entity[Animation] = Animation("search_directory") - del entity[AttackPoint] - del entity[Position] - del entity[Order] - del entity[Centered] - del entity[Texture] - elif timer >= world[AttackSpeed] + 4.5 and world[State] == State.SEARCHING: - world[State] = State.MOVING - for _ in range(10): - position = AttackPoint( - random.randint(0, COLUMNS - 1), - random.randint(0, LINES - 1), - ) - world.create_entity( - position, - Position(position.screen_position()), - Order(50), - Centered(), - Texture("attack_point.png"), - ) - world[AttackTimer] = AttackTimer(0.0) - world[AttackSpeed] = AttackSpeed(world[AttackSpeed] * 0.9) - - -def __move_directories(world: World): - """ - Permet de déplacer les dossiers avec la souris. - """ - # Si on n'est pas dans le bon state on annule - if GameStarted not in world or world[State] == State.GAME_OVER: - return - - # On met à jour la séléction - mouse = world[Mouse] - for entity in world.query(Hovered, DirectoryPosition): - if Animation in entity: - continue - if mouse.is_button_pressed(1): - world[SelectedDirectory] = SelectedDirectory(entity, Vec2(mouse.position)) - break - - # Si un dossier est séléctionné - if SelectedDirectory in world: - selected_directory = world[SelectedDirectory] - selected_entity = selected_directory.entity - directory_position = selected_entity[DirectoryPosition] - - # Vérification du relachement de la souris - if not mouse.is_button(1): - del world[SelectedDirectory] - return - - # On calcule le déplacement de la souris - mouse_delta = mouse.position - selected_directory.position - - # On annule si il y a pas eu de déplacement significatif de la souris - if mouse_delta.length < 40: - return - - # Récupération du mouvement voulu - if abs(mouse_delta.x) >= abs(mouse_delta.y): - movement = (int(mouse_delta.x / abs(mouse_delta.x)), 0) - else: - movement = (0, int(mouse_delta.y / abs(mouse_delta.y))) - - # Récupération des mouvements possible du dossier - movements: list[tuple[int, int]] = [] - if directory_position.x != 0: - movements.append((-1, 0)) - if directory_position.x != COLUMNS - 1: - movements.append((1, 0)) - if directory_position.y != 0: - movements.append((0, -1)) - if directory_position.y != LINES - 1: - movements.append((0, 1)) - if len(movements) == 0: - return - - # Si le mouvement n'est pas possible, on annule - if movement not in movements: - return - - # Si l'entité est animé on annule - if Animation in selected_entity: - return - - # On trouve l'autre dossier - for entity in world.query(DirectoryPosition, without=(Animation,)): - if entity != selected_entity and entity[ - DirectoryPosition - ] == DirectoryPosition( - directory_position.x + movement[0], - directory_position.y + movement[1], - ): - other_directory = entity - break - else: - return - - # On actualise la position de l'autre dossier - other_directory[DirectoryPosition].x -= movement[0] - other_directory[DirectoryPosition].y -= movement[1] - - # On actualise la position du dossier - selected_entity[DirectoryPosition].x += movement[0] - selected_entity[DirectoryPosition].y += movement[1] - - # On joue un son - world.create_entity(Sound("slide.wav")) - - # On retire le dossier selectionné - del world[SelectedDirectory] - - -def __update_positions(world: World): - """ - Met à jour la position cible des dossiers. - """ - for entity in world.query(DirectoryPosition): - position = entity[DirectoryPosition] - entity[smooth.Target] = smooth.Target(position.screen_position()) - entity[Order] = Order(position.y + 1) - - -def __update_directory_names(world: World): - """ - Met à jour la position des noms des dossiers. - """ - for entity in world.query(DirectoryName): - directory_entity = entity[DirectoryName].entity - entity[Position] = Position(directory_entity[Position] + Vec2(0, 75)) - entity[Order] = directory_entity[Order] - - -SCENE = ( - Scene( - [__initialize_world], - [__attacks, __move_directories, __update_positions, __remove_folders_speeds], - [], - ) - + smooth.PLUGIN - + Scene( - [], - [__update_directory_names], - [], - ) -) diff --git a/src/scenes/menteur.py b/src/scenes/menteur.py deleted file mode 100644 index 682b4cf..0000000 --- a/src/scenes/menteur.py +++ /dev/null @@ -1,236 +0,0 @@ -""" -Définis la scène du jeu menteur, sans variante. -""" - -from random import randint -from plugins import typing -from engine import ( - Centered, - Clickable, - Color, - Display, - Entity, - Game, - HoveredTexture, - Keyboard, - Order, - Position, - Scene, - Sound, - Text, - TextSize, - Texture, - World, -) - - -class RandomNumber(int): - """ - La ressource qui est le nombre a deviner. - """ - - -class TextDialogue: - """ - Le component qui declare l'entitee Text qui affiche le plus petit ou le plus grand - """ - - -class NombreEssai(int): - """ - Le component qui declare le nombre d'essai - """ - - -class NombreEssaiText: - """ - Le component qui affiche le nombre d'essai - """ - - -class IsRunning: - """ - Le component qui indique si le jeu est en cours - """ - - -COLOR_TEXT = Color(59, 162, 0) - - -def __initialize_world(world: World): - """ - Initialise le monde du menu. - """ - - # Fond d'ecran - world.create_entity( - Position(), - Order(0), - Texture("menteur/background.png"), - ) - - # Bouton valider/rejouer - world.create_entity( - Position(Display.WIDTH / 2, 880), - Order(1), - Centered(), - Texture("menteur/valider.png"), - HoveredTexture("menteur/valider_hover.png"), - Clickable(lambda world, _: _update(world)), - ) - - # Zone de saisie - world.create_entity( - Position(Display.WIDTH / 2, 750), - Order(2), - Centered(), - typing.Typing("1234567890"), - Text(""), - COLOR_TEXT, - TextSize(150), - ) - - # Text qui dit si ton nombre et trop grand ou trop petit - world.create_entity( - Position(Display.WIDTH / 2, 500), - Order(3), - Centered(), - TextDialogue(), - TextSize(150), - COLOR_TEXT, - Text("Devine le nombre..."), - ) - - # Text qui affiche le nombre d'essai - world.create_entity( - Position(Display.WIDTH - 750, 120), - Order(4), - TextSize(100), - NombreEssaiText(), - COLOR_TEXT, - Text("il reste : 15 essais"), - ) - - # Bouton pour revenir au menu - world.create_entity( - Order(11), - Position(100, 100), - Texture("menteur/arrow.png"), - Clickable(on_menu_button), - HoveredTexture("menteur/arrow_hover.png"), - ) - - # Les ressources. - world[NombreEssai] = NombreEssai(15) - world[RandomNumber] = RandomNumber(randint(0, 99)) - world[IsRunning] = IsRunning() - - -def on_menu_button(world: World, entity: Entity): - """ - Fonction qui s'execute quand on clique sur un bouton. - """ - world[Game].change_scene("menu") - entity[Sound] = Sound("click") - - -def _update(world: World): - """ - Verifie si le nombre donné est le meme que celui que l'on a choisi. - Boucle du jeu. - """ - - world.create_entity(Sound("menu_click.wav")) - - # si le jeu s'est arrete. - if IsRunning not in world: - # on relance le jeu. - world[Game].change_scene("menteur") - - for entity in world.query(typing.Typing, Text): - # One efface le nombre. - number: str = entity[Text] - entity[Text] = Text("") - - # On gere le l'input de l'utilisateur. - for entity_text in world.query(TextDialogue): - if number == "": # si il a rien evoyé. - entity_text[Text] = Text("tu doit entrer un nombre !") - return - if world[RandomNumber] == int(number): # si il a trouve le nombre. - end_game(world, "Gagné") - return - elif world[NombreEssai] <= 1: # si il n'a plus d'essai. - end_game(world, "Perdu") - return - elif world[RandomNumber] > int(number): # si le nombre est trop petit. - lie = randint(1, 4) - if lie == 4: - entity_text[Text] = Text("Plus petit...") - else: - entity_text[Text] = Text("Plus grand...") - else: # si le nombre est trop grand. - lie = randint(1, 4) - if lie == 4: - entity_text[Text] = Text("Plus grand...") - else: - entity_text[Text] = Text("Plus petit...") - - # on update l'affichage du nombre d'essai. - world[NombreEssai] = NombreEssai(world[NombreEssai] - 1) - for entity in world.query(NombreEssaiText): - entity[Text] = Text( - f"il reste : {world[NombreEssai]} essai{'s' if world[NombreEssai] != 1 else ''}" - ) - - -def end_game(world: World, state: str): - """ - fonction applé quand le jeu est fini. - """ - del world[IsRunning] # le jeu est fini. - - # On joue le son - if state == "Gagné": - world.create_entity(Sound("win_sound.wav")) - else: - world.create_entity(Sound("lose_sound.wav")) - - # On affiche le message de fin. - for entity_text in world.query(TextDialogue): - entity_text[Text] = Text(f"{state} !") - - # On empeche de pourvoir continuer le jeu. - for entity in world.query(typing.Typing, Text): - del entity[typing.Typing] - - if state == "Gagné": - for entity in world.query(NombreEssaiText): - entity[Text] = Text("") - else: - for entity in world.query(NombreEssaiText): - entity[Text] = Text(" plus d'essais") - - # on change la texture du button submit. - for entity in world.query(Clickable, Centered): - entity[Texture] = Texture("menteur/play_again.png") - entity[HoveredTexture] = HoveredTexture("menteur/play_again_hover.png") - - -def _check_return(world: World): - """ - Verifie si la touche entrée est appuyée. - """ - keyboard = world[Keyboard] - if keyboard.is_key_pressed("return") or keyboard.is_key_pressed("enter"): - _update(world) - - -SCENE = ( - Scene( - [__initialize_world], - [_check_return], - [], - ) - + typing.PLUGIN -) diff --git a/src/scenes/menu.py b/src/scenes/menu.py deleted file mode 100644 index 1fb3d80..0000000 --- a/src/scenes/menu.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Définis la scène du menu du jeu. -""" - -from engine import ( - Centered, - Clickable, - Display, - Entity, - Game, - HoveredTexture, - Order, - Position, - Scene, - Sound, - Texture, - World, -) - - -def __create_button(world: World, i: int, name: str): - """ - Ajoute un bouton au monde. - """ - world.create_entity( - Position(Display.WIDTH / 2, 450 + 150 * i), - Order(1), - Centered(), - Texture(f"menu/button_{name}.png"), - HoveredTexture(f"menu/button_{name}_hover.png"), - 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. - """ - entity[Sound] = Sound("click") - world[Game].change_scene(name) - - -def __initialize_world(world: World): - """ - Initialise le monde du menu. - """ - world.create_entity(Position(), Order(0), Texture("menu/background.png")) - scenes_name = ["classique", "menteur", "tricheur", "histoire"] - for i, name in enumerate(scenes_name): - __create_button(world, i, name) - - -SCENE = Scene( - [__initialize_world], - [], - [], -)