808 lines
25 KiB
Python
808 lines
25 KiB
Python
"""
|
|
Un moteur de jeu inspiré de bevy.
|
|
"""
|
|
|
|
|
|
import glob
|
|
import json
|
|
import math
|
|
import os
|
|
from typing import Callable, Optional, Sequence, SupportsFloat, TypeVar, Union
|
|
from time import time
|
|
import pygame
|
|
|
|
|
|
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)
|
|
|
|
# Chargement des textures
|
|
self.__textures: dict[str, pygame.Surface] = {}
|
|
for file in glob.iglob("assets/textures/**/*.png", recursive=True):
|
|
self.__textures[file[16:].replace("\\", "/")] = pygame.image.load(
|
|
file
|
|
).convert_alpha(surface)
|
|
|
|
# Création du cache pour les polices
|
|
self.__fonts: dict[int, pygame.font.Font] = {}
|
|
|
|
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*.
|
|
"""
|
|
return self.__textures.get(name, self.__error_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))
|
|
|
|
|
|
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)
|
|
|
|
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) -> None:
|
|
self.name = name
|
|
self._is_paying = False
|
|
|
|
|
|
class Scene:
|
|
"""
|
|
Une scène dans le jeu.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
init_systems: list[Callable[[World], object]],
|
|
update_systems: list[Callable[[World], object]],
|
|
stop_systems: list[Callable[[World], object]],
|
|
):
|
|
self.init_systems = init_systems
|
|
self.update_systems = update_systems
|
|
self.stop_systems = stop_systems
|
|
|
|
def __add__(self, other: "Scene") -> "Scene":
|
|
return Scene(
|
|
self.init_systems + other.init_systems,
|
|
self.update_systems + other.update_systems,
|
|
self.stop_systems + other.stop_systems,
|
|
)
|
|
|
|
|
|
def start_game(
|
|
scenes: dict[str, Scene],
|
|
start_scene: str,
|
|
*,
|
|
title: str = "Game",
|
|
) -> None:
|
|
"""
|
|
Lance le moteur de jeu.
|
|
"""
|
|
# 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)
|
|
|
|
# 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
|
|
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())
|
|
|
|
# Initialisation de la scène
|
|
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()
|
|
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,
|
|
)
|
|
|
|
# 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
|
|
for system in scene.update_systems:
|
|
system(world)
|
|
|
|
# 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,
|
|
),
|
|
)
|
|
|
|
# On met le son
|
|
for entity in world.query(Sound):
|
|
# On verifie si le son est deja actif.
|
|
if not entity[Sound]._is_paying:
|
|
# On charge le son et on le joue.
|
|
if os.path.exists("assets/sounds/" + entity[Sound].name):
|
|
entity[Sound]._is_paying = True
|
|
pygame.mixer.Sound("assets/sounds/" + entity[Sound].name).play()
|
|
|
|
# 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
|
|
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()
|