249 lines
8.1 KiB
Python
249 lines
8.1 KiB
Python
"""
|
|
Un plugin qui gère les assets du jeu.
|
|
"""
|
|
|
|
import glob
|
|
import pygame
|
|
from engine import CurrentScene, GlobalPlugin, KeepAlive, Scene
|
|
from engine.ecs import World
|
|
from plugins import render
|
|
|
|
|
|
class Assets(KeepAlive):
|
|
"""
|
|
Ressource qui gère les assets du jeu.
|
|
"""
|
|
|
|
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")
|
|
|
|
# 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 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()
|
|
|
|
|
|
def __initialize(world: World):
|
|
"""
|
|
Ajoute la ressource `Assets` au monde.
|
|
"""
|
|
world.set(Assets())
|
|
|
|
|
|
PLUGIN = GlobalPlugin(
|
|
[__initialize],
|
|
[],
|
|
[],
|
|
[],
|
|
)
|
|
|
|
|
|
def loading_scene(target: Scene, name: str, clear_cache: bool = True):
|
|
"""
|
|
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/<name>`.
|
|
"""
|
|
|
|
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.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()
|
|
world.set(AssetIterator())
|
|
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),
|
|
),
|
|
)
|
|
|
|
@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]
|
|
ressource_name = "/".join(file.split("/")[2:])[
|
|
: -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]
|
|
file_loaded = asset_iterator.total - len(asset_iterator.files)
|
|
progress = file_loaded / asset_iterator.total
|
|
pixels = int(render.WIDTH * progress)
|
|
|
|
# Affichage de la barre de progression
|
|
progress_bar = world.query(ProgessBar).pop()
|
|
progress_bar[render.Sprite].area = (0, 0, pixels, render.HEIGHT)
|
|
|
|
return Scene(
|
|
[AssetIterator.prepare_world],
|
|
[AssetIterator.load_next, ProgessBar.render],
|
|
[],
|
|
)
|