ecs #58

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

View file

@ -7,11 +7,11 @@ from typing import Callable, Optional
from engine.ecs import World from engine.ecs import World
class GlobalScene: class GlobalPlugin:
""" """
Une scène globale du jeu. Un plugin global du jeu.
La scène sera executé avant et après la scène actuelle du jeu. Le plugin sera executé avant et après la scène courrante du jeu.
Elle sera aussi executé au démarage et à l'arrêt du jeu. Elle sera aussi executé au démarage et à l'arrêt du jeu.
""" """
@ -27,8 +27,8 @@ class GlobalScene:
self.post_update_systems = post_update_systems self.post_update_systems = post_update_systems
self.stop_systems = stop_systems self.stop_systems = stop_systems
def __add__(self, other: "GlobalScene") -> "GlobalScene": def __add__(self, other: "GlobalPlugin") -> "GlobalPlugin":
return GlobalScene( return GlobalPlugin(
self.init_systems + other.init_systems, self.init_systems + other.init_systems,
self.pre_update_systems + other.pre_update_systems, self.pre_update_systems + other.pre_update_systems,
self.post_update_systems + other.post_update_systems, self.post_update_systems + other.post_update_systems,
@ -59,6 +59,17 @@ class Scene:
) )
class Plugin(Scene):
"""
Un plugin peut être ajouté a une scène pour lui ajouter des fonctionnalitées.
Techniquement, un `Plugin` est une simple `Scène` et le nom `Plugin` est
juste pour indiquer qu'il s'agit d'un plugin. Et le fait de pouvoir ajouter
le `Plugin` à une scène est due au fait que l'on peut combiner des scènes entre
elles.
"""
class KeepAlive: class KeepAlive:
""" """
Composant qui marque une entité comme n'étant pas détruit lors Composant qui marque une entité comme n'étant pas détruit lors
@ -82,7 +93,7 @@ class CurrentScene(KeepAlive):
return False return False
def start_game(global_scene: GlobalScene, scene: Optional[Scene]): def start_game(global_scene: GlobalPlugin, scene: Optional[Scene]):
""" """
Lance un jeu. Lance un jeu.

View file

@ -2,7 +2,14 @@
Un plugin qui permet de jouer des animations de sprites. Un plugin qui permet de jouer des animations de sprites.
""" """
from engine import GlobalScene from typing import Callable
import pygame
from engine import GlobalPlugin
from engine.ecs import Entity, World
from plugins.assets import Assets
from plugins.render import Sprite
from plugins.timing import Delta
class Animation: class Animation:
@ -11,13 +18,75 @@ class Animation:
sur l'entité. sur l'entité.
""" """
def __init__(self) -> None: def __init__(
pass self,
name: str,
fps: float = 60.0,
loop: bool = False,
callback: Callable[[World, Entity], object] = lambda _w, _e: None,
) -> None:
self.name = name
self.fps = fps
self.loop = loop
self.callback = callback
self.timer = 0.0
PLUGIN = GlobalScene( def __update_sprite(entity: Entity, texture: pygame.Surface, assets: Assets):
[], """
Change la texture de la `Sprite` d'une entité.
Si l'entité n'as pas de `Sprite`, une nouvelle `Sprite` sera ajoutée.
Si la texture est la texture d'erreur, la texture de la `Sprite` ne
sera pas changée.
"""
if texture is assets.error_texture:
return
if Sprite not in entity:
entity[Sprite] = Sprite(texture)
else:
entity[Sprite].texture = texture
def __update_animations(world: World) -> None:
"""
Met à jour les sprites des animations.
"""
assets = world[Assets]
delta = world[Delta]
for entity in world.query(Animation):
animation = entity[Animation]
animation.timer += delta
# On récupère la texture correspondante a la frame de l'animation
frame_index = int(animation.timer * animation.fps)
texture = assets.get_texture(f"{animation.name}/{frame_index:04d}")
# Si la texture n'existe pas, l'animation est finie
if texture is assets.error_texture:
if animation.loop:
# Si l'animation est une boucle on met la première image de l'animation
animation.timer = 0.0
__update_sprite(
entity, assets.get_texture(f"{animation.name}/0000"), assets
)
# Et on appelle la fonction de callback
animation.callback(world, entity)
else:
# Sinon on supprime le composant `Animation`
del entity[Animation]
# Et on appelle la fonction de callback
animation.callback(world, entity)
else:
# Si l'animation n'est pas finie, on met à jour la texture
__update_sprite(entity, texture, assets)
PLUGIN = GlobalPlugin(
[], [],
[__update_animations],
[], [],
[], [],
) )

View file

@ -4,7 +4,7 @@ Un plugin qui gère les ressources du jeu.
import glob import glob
import pygame import pygame
from engine import CurrentScene, GlobalScene, KeepAlive, Scene from engine import CurrentScene, GlobalPlugin, KeepAlive, Scene
from engine.ecs import World from engine.ecs import World
from plugins import render from plugins import render
@ -29,6 +29,15 @@ class Assets(KeepAlive):
# Cache des ressources # Cache des ressources
self.__textures: dict[str, pygame.Surface] = {} self.__textures: dict[str, pygame.Surface] = {}
@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 @property
def unloaded_texture(self) -> pygame.Surface: def unloaded_texture(self) -> pygame.Surface:
""" """
@ -56,6 +65,8 @@ class Assets(KeepAlive):
def get_texture(self, name: str) -> pygame.Surface: def get_texture(self, name: str) -> pygame.Surface:
""" """
Renvoie la texture demandée. 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) return self.__textures.get(name, self.__error_texture)
@ -73,7 +84,7 @@ def __initialize(world: World):
world.set(Assets()) world.set(Assets())
PLUGIN = GlobalScene( PLUGIN = GlobalPlugin(
[__initialize], [__initialize],
[], [],
[], [],

View file

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

View file

@ -5,7 +5,7 @@ Un plugin pour la gestion de la fenetre du jeu.
import pygame import pygame
from engine.ecs import World from engine.ecs import World
from engine import GlobalScene from engine import GlobalPlugin
def __initialize(_world: World): def __initialize(_world: World):
@ -23,7 +23,7 @@ def __terminate(_world: World):
pygame.quit() pygame.quit()
PLUGIN = GlobalScene( PLUGIN = GlobalPlugin(
[__initialize], [__initialize],
[], [],
[], [],

View file

@ -3,7 +3,7 @@ Un plugin permettant de gérer les entrées utilisateur du jeu.
""" """
import pygame import pygame
from engine import CurrentScene, GlobalScene, KeepAlive from engine import CurrentScene, GlobalPlugin, KeepAlive
from engine.ecs import World from engine.ecs import World
from engine.math import Vec2 from engine.math import Vec2
from plugins import render from plugins import render
@ -78,7 +78,7 @@ def __update_input(world: World):
) )
PLUGIN = GlobalScene( PLUGIN = GlobalPlugin(
[__initialize], [__initialize],
[__update_input], [__update_input],
[], [],

View file

@ -4,7 +4,7 @@ Un plugin qui s'occupe de rendre des choses dans la fenetre.
from typing import Optional from typing import Optional
import pygame import pygame
from engine import GlobalScene, KeepAlive from engine import GlobalPlugin, KeepAlive
from engine.ecs import World from engine.ecs import World
from engine.math import Vec2 from engine.math import Vec2
@ -42,20 +42,20 @@ class Sprite:
def __init__( def __init__(
self, self,
surface: pygame.Surface, texture: pygame.Surface,
position: Vec2 = Vec2(0), position: Vec2 = Vec2(0),
order: float = -1.0, order: float = -1.0,
area: Optional[tuple[float, float, float, float]] = None, area: Optional[tuple[float, float, float, float]] = None,
): ):
self.surface = surface self.texture = texture
self.position = position self.position = position
self.order = order self.order = order
if area is None: if area is None:
self.area = ( self.area = (
0.0, 0.0,
0.0, 0.0,
float(surface.get_width()), float(texture.get_width()),
float(surface.get_height()), float(texture.get_height()),
) )
else: else:
self.area = area self.area = area
@ -77,7 +77,7 @@ def __render(world: World):
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( surface.blit(
sprite.surface, sprite.texture,
(sprite.position.x, sprite.position.y), (sprite.position.x, sprite.position.y),
sprite.area, sprite.area,
) )
@ -93,7 +93,7 @@ def __render(world: World):
pygame.display.flip() pygame.display.flip()
PLUGIN = GlobalScene( PLUGIN = GlobalPlugin(
[__initialize], [__initialize],
[], [],
[__render], [__render],

52
src/plugins/timing.py Normal file
View file

@ -0,0 +1,52 @@
"""
Un plugin permettant de connaitre le temps depuis le
lancement du jeu et le temps depuis la dernière frame.
"""
from time import time
from engine import GlobalPlugin
from engine.ecs import World
class GlobalTime(float):
"""
Ressource qui représente le temps global de l'ordinateur sur lequel tourne le jeu.
"""
class Time(float):
"""
Ressource qui représente le temps depuis le lancement du jeu.
"""
class Delta(float):
"""
Ressource qui détermine le temps depuis la première frame.
"""
def __initialize(world: World):
"""
Initialise les ressources pour la gestion du temps.
"""
world.set(GlobalTime(time()), Time(0.0))
def __update(world: World):
"""
Met à jour les ressources de temps.
"""
now = time()
world[Delta] = delta = now - world[GlobalTime]
world[GlobalTime] = now
world[Time] += delta
PLUGIN = GlobalPlugin(
[__initialize],
[__update],
[],
[],
)