ecs #58

Merged
raphael merged 70 commits from ecs into main 2023-11-03 15:29:36 +00:00
8 changed files with 229 additions and 21 deletions
Showing only changes of commit 522126e325 - Show all commits

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 from engine.ecs import World
@ -46,7 +46,7 @@ class Scene:
init_systems: list[Callable[[World], object]], init_systems: list[Callable[[World], object]],
update_systems: list[Callable[[World], object]], update_systems: list[Callable[[World], object]],
stop_systems: list[Callable[[World], object]], stop_systems: list[Callable[[World], object]],
): ) -> None:
self.init_systems = init_systems self.init_systems = init_systems
self.update_systems = update_systems self.update_systems = update_systems
self.stop_systems = stop_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. 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. Lance un jeu.
@ -84,14 +92,14 @@ def start_game(global_scene: GlobalScene, scenes: dict[str, Scene], scene_name:
""" """
# On création du monde # On création du monde
world = World() world = World()
world[CurrentScene] = CurrentScene(scene_name) if scene is not None:
world[CurrentScene] = CurrentScene(scene)
# On initialise la scène globale # On initialise la scène globale
for system in global_scene.init_systems: for system in global_scene.init_systems:
system(world) system(world)
# Boucle principale # Boucle principale
scene = scenes.get(scene_name)
while scene is not None: while scene is not None:
# On retire les ressources de l'ancienne scène # On retire les ressources de l'ancienne scène
for resource in world: for resource in world:
@ -107,7 +115,7 @@ def start_game(global_scene: GlobalScene, scenes: dict[str, Scene], scene_name:
system(world) system(world)
# Tant que la scène n'est pas terminé on la met à jour # 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 # On met à jour la scène globale
for system in global_scene.pre_update_systems: for system in global_scene.pre_update_systems:
system(world) system(world)
@ -124,8 +132,12 @@ def start_game(global_scene: GlobalScene, scenes: dict[str, Scene], scene_name:
for system in scene.stop_systems: for system in scene.stop_systems:
system(world) system(world)
# Récupération de la scène suivante # On met à jour la scène
scene = scenes.get(world.get(CurrentScene, "")) current_scene = world.get(CurrentScene, ())
if isinstance(current_scene, tuple):
scene = None
else:
scene = current_scene.scene
# On arrête la scène globale # On arrête la scène globale
for system in global_scene.stop_systems: 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 engine import Scene, start_game
from plugins import defaults from plugins import assets, defaults
start_game( start_game(defaults.PLUGIN, assets.loading_scene(Scene([], [], []), "textures"))
defaults.PLUGIN,
{
"menu": Scene([], [], []),
},
"menu",
)

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. 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. Un plugin qui s'occupe de rendre des choses dans la fenetre.
""" """
from typing import Optional
import pygame import pygame
from engine import GlobalScene, KeepAlive from engine import GlobalScene, KeepAlive
from engine.ecs import World from engine.ecs import World
@ -40,11 +41,24 @@ class Sprite:
""" """
def __init__( 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.surface = surface
self.position = position self.position = position
self.order = order 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): def __initialize(world: World):
@ -62,7 +76,11 @@ def __render(world: World):
surface = world[Surface] surface = world[Surface]
sprites = [entity[Sprite] for entity in world.query(Sprite)] sprites = [entity[Sprite] for entity in world.query(Sprite)]
for sprite in sorted(sprites, key=lambda sprite: sprite.order): 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 # On affiche la surface sur la fenetre
rect = calculate_surface_rect() rect = calculate_surface_rect()