Ajout d'un chargement

This commit is contained in:
Tipragot 2023-11-01 03:05:33 +01:00
parent 894e150484
commit 522126e325
8 changed files with 229 additions and 21 deletions

BIN
assets/loaded.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
assets/unloaded.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -3,7 +3,7 @@ Permet de lancer un jeu et de gérer la boucle principale de celui-ci.
"""
from typing import Callable
from typing import Callable, Optional
from engine.ecs import World
@ -46,7 +46,7 @@ class Scene:
init_systems: list[Callable[[World], object]],
update_systems: list[Callable[[World], object]],
stop_systems: list[Callable[[World], object]],
):
) -> None:
self.init_systems = init_systems
self.update_systems = update_systems
self.stop_systems = stop_systems
@ -68,13 +68,21 @@ class KeepAlive:
"""
class CurrentScene(KeepAlive, str):
class CurrentScene(KeepAlive):
"""
Resource qui permet de savoir et de changer la scène actuelle.
"""
def __init__(self, scene: Scene):
self.scene = scene
def start_game(global_scene: GlobalScene, scenes: dict[str, Scene], scene_name: str):
def __eq__(self, value: object) -> bool:
if isinstance(value, CurrentScene):
return self.scene == value.scene
return False
def start_game(global_scene: GlobalScene, scene: Optional[Scene]):
"""
Lance un jeu.
@ -84,14 +92,14 @@ def start_game(global_scene: GlobalScene, scenes: dict[str, Scene], scene_name:
"""
# On création du monde
world = World()
world[CurrentScene] = CurrentScene(scene_name)
if scene is not None:
world[CurrentScene] = CurrentScene(scene)
# On initialise la scène globale
for system in global_scene.init_systems:
system(world)
# Boucle principale
scene = scenes.get(scene_name)
while scene is not None:
# On retire les ressources de l'ancienne scène
for resource in world:
@ -107,7 +115,7 @@ def start_game(global_scene: GlobalScene, scenes: dict[str, Scene], scene_name:
system(world)
# Tant que la scène n'est pas terminé on la met à jour
while world.get(CurrentScene, "") == scene_name:
while world.get(CurrentScene, ()) == CurrentScene(scene):
# On met à jour la scène globale
for system in global_scene.pre_update_systems:
system(world)
@ -124,8 +132,12 @@ def start_game(global_scene: GlobalScene, scenes: dict[str, Scene], scene_name:
for system in scene.stop_systems:
system(world)
# Récupération de la scène suivante
scene = scenes.get(world.get(CurrentScene, ""))
# On met à jour la scène
current_scene = world.get(CurrentScene, ())
if isinstance(current_scene, tuple):
scene = None
else:
scene = current_scene.scene
# On arrête la scène globale
for system in global_scene.stop_systems:

View file

@ -3,13 +3,7 @@ Module d'exemple de l'utilisation du moteur de jeu.
"""
from engine import Scene, start_game
from plugins import defaults
from plugins import assets, defaults
start_game(
defaults.PLUGIN,
{
"menu": Scene([], [], []),
},
"menu",
)
start_game(defaults.PLUGIN, assets.loading_scene(Scene([], [], []), "textures"))

23
src/plugins/animation.py Normal file
View file

@ -0,0 +1,23 @@
"""
Un plugin qui permet de jouer des animations de sprites.
"""
from engine import GlobalScene
class Animation:
"""
Composant qui contient toutes les informations d'une animation est en cour
sur l'entité.
"""
def __init__(self) -> None:
pass
PLUGIN = GlobalScene(
[],
[],
[],
[],
)

161
src/plugins/assets.py Normal file
View file

@ -0,0 +1,161 @@
"""
Un plugin qui gère les ressources du jeu.
"""
import glob
import pygame
from engine import CurrentScene, GlobalScene, 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()
# 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] = {}
@property
def unloaded_texture(self) -> pygame.Surface:
return self.__unloaded_texture
@property
def loaded_texture(self) -> pygame.Surface:
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.
"""
return self.__textures.get(name, self.__error_texture)
def clear_cache(self):
"""
Vide le cache des assets.
"""
self.__textures.clear()
def __initialize(world: World):
"""
Ajoute la ressource `Assets` au monde.
"""
world.set(Assets())
PLUGIN = GlobalScene(
[__initialize],
[],
[],
[],
)
def loading_scene(target: Scene, name: str):
"""
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]
assets.clear_cache()
world.set(AssetIterator())
world.new_entity().set(
render.Sprite(assets.unloaded_texture, order=100000000000)
)
world.new_entity().set(
ProgessBar(),
render.Sprite(
assets.loaded_texture,
order=100000000001,
area=(0, 0, 0, render.HEIGHT),
),
)
@staticmethod
def load_next(world: World):
"""
Charge le fichier suivant de l'itérateur.
"""
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"):
world[Assets].load_texture(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],
[],
)

View file

@ -2,7 +2,7 @@
Plugin qui rassemple tous les plugins globaux.
"""
from plugins import display, inputs, render
from plugins import assets, display, inputs, render
PLUGIN = display.PLUGIN + inputs.PLUGIN + render.PLUGIN
PLUGIN = display.PLUGIN + assets.PLUGIN + inputs.PLUGIN + render.PLUGIN

View file

@ -2,6 +2,7 @@
Un plugin qui s'occupe de rendre des choses dans la fenetre.
"""
from typing import Optional
import pygame
from engine import GlobalScene, KeepAlive
from engine.ecs import World
@ -40,11 +41,24 @@ class Sprite:
"""
def __init__(
self, surface: pygame.Surface, position: Vec2 = Vec2(0), order: float = -1.0
self,
surface: pygame.Surface,
position: Vec2 = Vec2(0),
order: float = -1.0,
area: Optional[tuple[float, float, float, float]] = None,
):
self.surface = surface
self.position = position
self.order = order
if area is None:
self.area = (
0.0,
0.0,
float(surface.get_width()),
float(surface.get_height()),
)
else:
self.area = area
def __initialize(world: World):
@ -62,7 +76,11 @@ def __render(world: World):
surface = world[Surface]
sprites = [entity[Sprite] for entity in world.query(Sprite)]
for sprite in sorted(sprites, key=lambda sprite: sprite.order):
surface.blit(sprite.surface, (sprite.position.x, sprite.position.y))
surface.blit(
sprite.surface,
(sprite.position.x, sprite.position.y),
sprite.area,
)
# On affiche la surface sur la fenetre
rect = calculate_surface_rect()