better_ecs #2

Merged
CoCo_Sol merged 3 commits from better_ecs into main 2024-01-04 00:17:44 +00:00
24 changed files with 623 additions and 937 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
assets/textures/dada.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
assets/textures/dodo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

@ -6,7 +6,7 @@ Pour moddifier le monde, on n'agis que sur les composants.
""" """
from typing import Iterator, Optional, Sequence, TypeVar from typing import Iterator, Optional, Sequence
class Entity: class Entity:
@ -17,15 +17,6 @@ class Entity:
utilitaire pour acceder au monde plus facilement. utilitaire pour acceder au monde plus facilement.
""" """
__T = TypeVar("__T")
"""
Ce type est utilisé pour permettre la gestion des types
pour certaines fonctions. Par exemple, la fonction `__getitem__`
utilise cette variable pour retourner un object du type demandé.
Cela permet d'avoir des vérifications de types et de l'autocomplétion
en utilisant notre IDE.
"""
def __init__(self, world: "World", identifier: int) -> None: def __init__(self, world: "World", identifier: int) -> None:
self.__world = world self.__world = world
self.__identifier = identifier self.__identifier = identifier
@ -55,13 +46,13 @@ class Entity:
def __repr__(self) -> str: def __repr__(self) -> str:
return f"Entity({self.__identifier})" return f"Entity({self.__identifier})"
def __getitem__(self, component_type: type[__T]) -> __T: def __getitem__[T](self, component_type: type[T]) -> T:
return self.__world.get_component(self, component_type) return self.__world.get_component(self, component_type)
def __delitem__(self, component_type: type[object]): def __delitem__(self, component_type: type[object]):
self.__world.remove_component(self, component_type) self.__world.remove_component(self, component_type)
def __setitem__(self, component_type: type[__T], component: __T): def __setitem__[T](self, component_type: type[T], component: T):
if component_type != type(component): if component_type != type(component):
component = component_type(component) component = component_type(component)
self.__world.set_component(self, component) self.__world.set_component(self, component)
@ -75,7 +66,7 @@ class Entity:
def __iter__(self) -> Iterator[object]: def __iter__(self) -> Iterator[object]:
return iter(self.__world.all_components(self)) return iter(self.__world.all_components(self))
def get(self, component_type: type[__T], default: Optional[__T] = None) -> __T: def get[T](self, component_type: type[T], default: Optional[T] = None) -> T:
""" """
Renvoie le composant de type `component_type` de l'entité. Renvoie le composant de type `component_type` de l'entité.
Si aucun composant de type `component_type` n'est dans l'entité: Si aucun composant de type `component_type` n'est dans l'entité:
@ -126,15 +117,6 @@ class World(Entity):
globaux, relatif au monde. globaux, relatif au monde.
""" """
__T = TypeVar("__T")
"""
Ce type est utilisé pour permettre la gestion des types
pour certaines fonctions. Par exemple, la fonction `get_component`
utilise cette variable pour retourner un object du type demandé.
Cela permet d'avoir des vérifications de types et de l'autocomplétion
en utilisant notre IDE.
"""
def __init__(self): def __init__(self):
super().__init__(self, 0) super().__init__(self, 0)
self.__components: dict[int, dict[type[object], object]] = {} self.__components: dict[int, dict[type[object], object]] = {}
@ -164,12 +146,19 @@ class World(Entity):
- `entity`: l'entité dans lequelle ajouter le composant. - `entity`: l'entité dans lequelle ajouter le composant.
- `component`: le composant à ajouter. - `component`: le composant à ajouter.
""" """
if isinstance(component, tuple):
for c in component: # type: ignore
if c is not None:
self.set_component(entity, c) # type: ignore
return
self.__components.setdefault(entity.identifier, {})[type(component)] = component self.__components.setdefault(entity.identifier, {})[type(component)] = component
self.__entities.setdefault(type(component), set()).add(entity.identifier) self.__entities.setdefault(type(component), set()).add(entity.identifier)
def get_component( def get_component[
self, entity: "Entity", component_type: type[__T], default: Optional[__T] = None T
) -> __T: ](
self, entity: "Entity", component_type: type[T], default: Optional[T] = None
) -> T:
""" """
Renvoie le composant de type `component_type` de l'entité `entity`. Renvoie le composant de type `component_type` de l'entité `entity`.
Si aucun composant de type `component_type` n'est dans l'entité: Si aucun composant de type `component_type` n'est dans l'entité:

View file

@ -2,9 +2,239 @@
Module d'exemple de l'utilisation du moteur de jeu. Module d'exemple de l'utilisation du moteur de jeu.
""" """
from engine import start_game from engine import Scene, start_game
from plugins import defaults from engine.ecs import Entity, World
from scenes import menu from engine.math import Vec2
from plugins import defaults, physics
from plugins import render
from plugins.inputs import Held
from plugins.render import (
Origin,
Position,
Scale,
SpriteBundle,
)
from plugins.timing import Delta
start_game(defaults.PLUGIN, menu.SCENE) def lol(a: Entity, b: Entity, simul: bool = False):
if Bounce in b:
speed = a[physics.Velocity].length
a[physics.Velocity] = a[physics.Velocity].normalized
a[physics.Velocity].y = (a[Position].y - b[Position].y) * 0.005
a[physics.Velocity] = a[physics.Velocity].normalized * min(
(speed * 1.1), 1000.0
)
return True
class Bounce:
pass
def lol_simul(a: Entity, b: Entity):
lol(a, b, True)
return RightWall not in b
class Simulated:
pass
class Bar:
pass
class RightWall:
pass
class BallFollow(int):
pass
class Mine:
pass
def __initialize(world: World):
"""
Initialise les ressources pour le moteur de jeu.
"""
world.new_entity().set(
SpriteBundle(
"background.png",
-1,
scale=Vec2(render.WIDTH, render.HEIGHT),
),
)
# world.new_entity().set(
# SpriteBundle(
# "dodo.png",
# 0,
# position=Vec2(800, 500),
# origin=Vec2(0.5, 0.5),
# scale=Vec2(400, 300),
# ),
# physics.Solid(),
# )
world.new_entity().set(
SpriteBundle(
"dodo.png",
0,
position=Vec2(100, 100),
origin=Vec2(0, 0),
scale=Vec2(render.WIDTH - 200, 10),
),
physics.Solid(),
)
world.new_entity().set(
SpriteBundle(
"dodo.png",
0,
position=Vec2(100, render.HEIGHT - 100),
origin=Vec2(0, 1),
scale=Vec2(render.WIDTH - 200, 10),
),
physics.Solid(),
)
world.new_entity().set(
SpriteBundle(
"dodo.png",
0,
position=Vec2(render.WIDTH - 100, 100),
origin=Vec2(1, 0),
scale=Vec2(10, render.HEIGHT - 200),
),
physics.Solid(),
RightWall(),
)
world.new_entity().set(
SpriteBundle(
"dodo.png",
0,
position=Vec2(100, 100),
origin=Vec2(0, 0),
scale=Vec2(10, render.HEIGHT - 200),
),
physics.Solid(),
)
world.new_entity().set(
SpriteBundle(
"dodo.png",
0,
position=Vec2(render.WIDTH - 130, render.HEIGHT / 2),
origin=Vec2(0.5, 0.5),
scale=Vec2(10, 200),
),
physics.Solid(),
Bar(),
Bounce(),
)
world.new_entity().set(
SpriteBundle(
"dodo.png",
0,
position=Vec2(130, render.HEIGHT / 2),
origin=Vec2(0.5, 0.5),
scale=Vec2(10, 200),
),
physics.Solid(),
Mine(),
Bounce(),
)
world.new_entity().set(
SpriteBundle(
"dada.png",
1,
scale=Vec2(10),
position=Vec2(500, 500),
origin=Vec2(0.5, 0.5),
),
physics.Velocity(Vec2(200, 100)),
physics.CollisionHandler(lol),
)
for i in range(20):
world.new_entity().set(
SpriteBundle(
"dodo.png",
10,
scale=Vec2(10),
),
physics.CollisionHandler(lol),
BallFollow(i + 1),
# physics.CollisionHandler(lol_simul),
)
def __update(world: World):
"""
Test.
"""
for entity in world.query(Mine, Position):
if "z" in world[Held]:
entity[Position].y -= 300 * world[Delta]
if "s" in world[Held]:
entity[Position].y += 300 * world[Delta]
ball = max(
world.query(Position, physics.Velocity, physics.CollisionHandler),
key=lambda e: e[Position].x,
)
for bar in world.query(Bar):
bar.remove(physics.Solid)
entity = world.new_entity()
entity.set(
Position(ball[Position]),
Scale(ball[Scale]),
physics.Velocity(ball[physics.Velocity]),
Origin(ball[Origin]),
physics.CollisionHandler(lol_simul),
)
physics.move_entity(entity, entity[physics.Velocity] * 500)
target = entity[Position].y
for bar in world.query(Bar):
diff = target - bar[Position].y
# bar[Position].y += (diff / abs(diff)) * 300 * world[Delta]
bar[Position].y += diff
bar.set(physics.Solid())
entity.destroy()
# for bar in world.query(Bar):
# bar.remove(physics.Solid)
for entity in world.query(BallFollow):
entity[Position] = Vec2(ball[Position])
entity[physics.Velocity] = Vec2(ball[physics.Velocity])
physics.move_entity(entity, entity[physics.Velocity] * entity[BallFollow] * 0.5)
del entity[physics.Velocity]
# for bar in world.query(Bar):
# bar.set(physics.Solid())
# ball.set(Simulated())
# for entity in world.query(Bar):
# entity.remove(physics.Solid)
# last_position = Vec2(ball[Position])
# last_velocity = Vec2(ball[physics.Velocity])
# physics.move_entity(ball, ball[physics.Velocity] * 5000)
# ball[Position] = last_position
# ball[physics.Velocity] = last_velocity
# for entity in world.query(Bar):
# entity.set(physics.Solid())
# ball.remove(Simulated)
MENU = Scene(
[__initialize],
[__update],
[],
)
start_game(defaults.PLUGIN, MENU)

View file

@ -1,110 +0,0 @@
"""
Un plugin qui permet de jouer des animations de sprites.
"""
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:
"""
Composant qui contient toutes les informations d'une animation est en cour
sur l'entité.
"""
def __init__(
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
self.ended = False
def wait(self) -> Callable[[World], bool]:
"""
Utilitaire de `Coroutine` permettant d'attendre que l'animation soit finie.
"""
return lambda world: self.ended
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]
animation.ended = True
# On trouve la dernière image de l'animation et on met à jour la texture
while texture is assets.error_texture:
frame_index -= 1
texture = assets.get_texture(f"{animation.name}/{frame_index:04d}")
if frame_index < 0:
break
if texture is not assets.error_texture:
__update_sprite(entity, texture, assets)
# 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)
animation.ended = False
PLUGIN = GlobalPlugin(
[],
[],
[__update_animations],
[],
)

View file

@ -1,271 +1,45 @@
""" """
Un plugin qui gère les assets du jeu. Ce module contient des utilitaires pour le chargement des ressources du jeu.
""" """
import glob
import random
import pygame import pygame
from engine import CurrentScene, GlobalPlugin, KeepAlive, Scene
from engine.ecs import World
from plugins import render
from plugins.sound import Sound
class Assets(KeepAlive): def load_texture(name: str, cache: dict[str, pygame.Surface] = {}) -> pygame.Surface:
""" """
Ressource qui gère les assets du jeu. Charge une texture et la renvoi.
""" """
surface = cache.get(name)
def __init__(self): if surface is None:
# Création de la texture d'erreur surface = pygame.image.load(f"assets/textures/{name}").convert_alpha()
error_texture = pygame.Surface((256, 256)) cache[name] = surface
error_texture.fill((0, 0, 0)) return surface
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")
# self.__waiting_sound = pygame.mixer.Sound("assets/waiting.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 waiting_sound(self) -> pygame.mixer.Sound:
"""
Le son de chargement.
"""
return self.__waiting_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): def load_sound(
name: str, cache: dict[str, pygame.mixer.Sound] = {}
) -> pygame.mixer.Sound:
""" """
Ajoute la ressource `Assets` au monde. Charge un son et le renvoi.
""" """
world.set(Assets()) sound = cache.get(name)
if sound is None:
sound = pygame.mixer.Sound(f"assets/sounds/{name}")
cache[name] = sound
return sound
PLUGIN = GlobalPlugin( def load_text(
[__initialize], text: str,
[], size: int,
[], color: pygame.Color,
[], cache: dict[tuple[str, int, tuple[int, int, int]], pygame.Surface] = {},
) ) -> pygame.Surface:
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é Charge un texte et le renvoi.
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>`.
""" """
surface = cache.get((text, size, (color.r, color.g, color.b)))
class AssetIterator: if surface is None:
""" surface = pygame.font.Font("assets/font.ttf", size).render(text, True, color)
Une ressource qui contient un itérateur sur les fichiers des assets cache[(text, size, (color.r, color.g, color.b))] = surface
de la scène à charger. return surface
"""
def __init__(self):
self.files = glob.glob(f"assets/{name}/**/*", recursive=True)
self.files.extend(glob.glob("assets/global/**/*", recursive=True))
random.shuffle(self.files)
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()
asset_iterator = AssetIterator()
world.set(AssetIterator())
if asset_iterator.total <= 30:
for _ in range(asset_iterator.total):
asset_iterator.load_next(world)
else:
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),
),
Sound(assets.waiting_sound, loop=True),
)
@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]
prefix = (
"assets/global/"
if file.startswith("assets/global/")
else f"assets/{name}/"
)
ressource_name = file[len(prefix) : -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]
if asset_iterator.total == 0:
progress = 0.0
else:
file_loaded = asset_iterator.total - len(asset_iterator.files)
progress = file_loaded / asset_iterator.total
# Affichage de la barre de progression
for progress_bar in world.query(ProgessBar):
progress_bar[render.Sprite].area = (0, 0, progress, 1.0)
return Scene(
[AssetIterator.prepare_world],
[AssetIterator.load_next, ProgessBar.render],
[],
)

View file

@ -7,7 +7,7 @@ from engine import GlobalPlugin
from engine.ecs import Entity, World from engine.ecs import Entity, World
from plugins.hover import Hovered from plugins.hover import Hovered
from plugins.inputs import Pressed from plugins.inputs import Pressed
from plugins.render import Sprite from plugins.render import Origin, Position, Scale
class Clicked: class Clicked:
@ -30,7 +30,7 @@ def __update_clicked(world: World):
Met à jour les composants `Clicked`. Met à jour les composants `Clicked`.
""" """
mouse_click = "button_1" in world[Pressed] mouse_click = "button_1" in world[Pressed]
sprite_entities = world.query(Sprite) sprite_entities = world.query(Position, Scale, Origin)
for entity in sprite_entities: for entity in sprite_entities:
if Hovered in entity and mouse_click: if Hovered in entity and mouse_click:
entity[Clicked] = Clicked() entity[Clicked] = Clicked()

View file

@ -1,85 +0,0 @@
"""
Plugin permettant d'executer des coroutine.
Une coroutine est une fonction qui est executée sur plusieurs frames,
cela permet d'executer du code comme si il s'agissait d'une fonction
classique tout en laissant tourner la boucle du jeu. Il est a noter que
cette fonction n'est pas lancé dans une autre thread, la fonction est
executé dans le thread principal, mais lorsqu'un `yield` est utilisé,
l'execution de la fonction est arrêté jusqu'a ce que la condition donné
par le `yield` soit remplie, ce qui permet au thread principal de pouvoir
continuer de faire le rendu et la logique du jeu. Faire un traivail bloquant
dans une coroutine auras donc un impacte sur le nombre d'images par secondes
du jeu.
"""
from time import time
from typing import Callable, Generator, Optional
from engine import GlobalPlugin
from engine.ecs import Entity, World
from plugins.timing import GlobalTime
def wait(seconds: float) -> Callable[[World], bool]:
"""
Utilitaire de `Coroutine` permettant d'attendre un certain temps.
"""
stop_time = time() + seconds
return lambda world: world[GlobalTime] >= stop_time
def condition(condition_function: Callable[[World], bool]) -> Callable[[World], bool]:
"""
Utilitaire de `Coroutine` permettant d'attendre que la condition soit réalisé avant
de continuer l'execution.
"""
return condition_function
class Coroutine:
"""
Composant permettant d'executer une coroutine.
"""
def __init__(self, generator: Generator[Callable[[World], bool], None, None]):
self.generator = generator
self.__condition: Optional[ # pylint: disable=unused-private-member
Callable[[World], bool]
] = None
@staticmethod
def update(entity: Entity):
"""
Met à jour la coroutine d'une entité.
Si l'entité n'as pas de `Coroutine` la fonction ne fait rien.
"""
if Coroutine not in entity:
return
coroutine = entity[Coroutine]
if coroutine.__condition is None:
coroutine.__condition = next( # pylint: disable=unused-private-member
coroutine.generator, None
)
if coroutine.__condition is None:
del entity[Coroutine]
return
if coroutine.__condition(entity.world):
coroutine.__condition = None # pylint: disable=unused-private-member
def __update_coroutines(world: World):
"""
Met à jour les coroutine du jeu.
"""
for entity in world.query(Coroutine):
Coroutine.update(entity)
PLUGIN = GlobalPlugin(
[],
[__update_coroutines],
[],
[],
)

View file

@ -2,33 +2,16 @@
Plugin qui rassemple tous les plugins globaux. Plugin qui rassemple tous les plugins globaux.
""" """
from plugins import ( from plugins import click, display, inputs, physics, sound, render, timing, hover
animation,
assets,
click,
coroutine,
display,
hover,
inputs,
multisound,
render,
sound,
text,
timing,
)
PLUGIN = ( PLUGIN = (
display.PLUGIN display.PLUGIN
+ timing.PLUGIN + timing.PLUGIN
+ assets.PLUGIN
+ inputs.PLUGIN + inputs.PLUGIN
+ physics.PLUGIN
+ hover.PLUGIN + hover.PLUGIN
+ click.PLUGIN + click.PLUGIN
+ coroutine.PLUGIN
+ multisound.PLUGIN
+ sound.PLUGIN + sound.PLUGIN
+ text.PLUGIN
+ animation.PLUGIN
+ render.PLUGIN + render.PLUGIN
) )

View file

@ -3,13 +3,10 @@ Un plugin permettant de savoir si la souris est par dessus une entité.
""" """
import pygame
from engine import GlobalPlugin from engine import GlobalPlugin
from engine.ecs import World from engine.ecs import World
from engine.math import Vec2
from plugins.inputs import MousePosition from plugins.inputs import MousePosition
from plugins.render import Sprite from plugins.render import Origin, Position, Scale, Texture
class HoverEnter: class HoverEnter:
@ -36,7 +33,7 @@ class HoveredTexture:
celle-ci est survolée par la souris. celle-ci est survolée par la souris.
""" """
def __init__(self, normal: pygame.Surface, hovered: pygame.Surface): def __init__(self, normal: str, hovered: str):
self.normal = normal self.normal = normal
self.hovered = hovered self.hovered = hovered
@ -47,11 +44,10 @@ def __update_hovered(world: World):
""" """
# On met à jour les composants # On met à jour les composants
mouse_position = world[MousePosition] mouse_position = world[MousePosition]
for entity in world.query(Sprite): for entity in world.query(Position, Scale, Origin):
# Récupération de la position et taille de l'entité # Récupération de la position et taille de l'entité
sprite = entity[Sprite] size = entity[Scale]
size = Vec2(*sprite.texture.get_size()) * Vec2(*sprite.area[2:]) position = entity[Position] - (entity[Origin] * size)
position = sprite.position - (sprite.origin * size)
# On détermine si la souris est sur l'entité # On détermine si la souris est sur l'entité
if ( if (
@ -75,13 +71,9 @@ def __update_hovered(world: World):
# On affiche la bonne texture # On affiche la bonne texture
for entity in world.query(HoveredTexture): for entity in world.query(HoveredTexture):
if Hovered in entity: if Hovered in entity:
texture = entity[HoveredTexture].hovered entity[Texture] = entity[HoveredTexture].hovered
else: else:
texture = entity[HoveredTexture].normal entity[Texture] = entity[HoveredTexture].normal
if Sprite in entity:
entity[Sprite].texture = texture
else:
entity[Sprite] = Sprite(texture)
PLUGIN = GlobalPlugin( PLUGIN = GlobalPlugin(

View file

@ -1,65 +0,0 @@
"""
Plugin pour les sons multiples.
"""
import random
from typing import Callable
from engine import GlobalPlugin
from engine.ecs import Entity, World
from plugins.assets import Assets
from plugins.sound import Sound
class MultiSound:
"""
Composant qui quand il est ajouté a une entité, il joue un son aléatoire
parmis la liste de ses sons, il est ensuite retiré de l'entité.
Ce composant est fait pour être définis en tant que constante
puis utilisé ensuite, pour cela il prend seulement le nom des
sons au lieu des sons eux memes. Les sons seront donc récupérés
a l'aide de `Assets`.
"""
def __init__(
self,
*sound_names: str,
loop: bool = False,
volume: float = 1.0,
fade_ms: int = 0,
callback: Callable[[World, Entity], object] = lambda _w, _e: None,
):
self.sound_names = sound_names
self.loop = loop
self.volume = volume
self.fade_ms = fade_ms
self.callback = callback
def __update_sounds(world: World):
"""
Met à jour les sons du jeu.
"""
# Ajout des sons aléatoires
assets = world[Assets]
for entity in world.query(MultiSound):
multi_sound = entity[MultiSound]
sound = assets.get_sound(random.choice(multi_sound.sound_names))
entity[Sound] = Sound(
sound,
multi_sound.loop,
multi_sound.volume,
multi_sound.fade_ms,
multi_sound.callback,
)
del entity[MultiSound]
PLUGIN = GlobalPlugin(
[],
[],
[__update_sounds],
[],
)

194
src/plugins/physics.py Normal file
View file

@ -0,0 +1,194 @@
"""
Plugin implémentant une physique exacte pour des collisions AABB.
"""
from typing import Callable
from engine import GlobalPlugin
from engine.ecs import Entity, World
from engine.math import Vec2
from plugins.render import Origin, Position, Scale
from plugins.timing import Delta
class Solid:
"""
Composant représentant un objet (de préférence imobille pour que la simulation soit prédictible) qui ne laisse pas passer les objets dynamiques.
"""
class Velocity(Vec2):
"""
Composant donnant la vélocité d'un objet.
"""
class CollisionHandler:
"""
Composant permettant de traiter les collisions.
"""
def __init__(self, callback: Callable[[Entity, Entity], bool]):
self.callback = callback
class AABB:
"""
Définit une boite.
"""
def __init__(self, min: Vec2, max: Vec2, entity: Entity):
self.min = min
self.max = max
self.entity = entity
@staticmethod
def from_entity(entity: Entity):
min = entity[Position] - entity[Origin] * entity[Scale]
return AABB(min, min + entity[Scale], entity)
def entity_position(self, entity: Entity):
scale = self.max - self.min
entity[Position] = self.min + entity[Origin] * scale
entity[Scale] = scale
def __contains__(self, point: Vec2):
return (
self.min.x <= point.x <= self.max.x and self.min.y <= point.y <= self.max.y
)
def move(self, movement: Vec2):
self.min += movement
self.max += movement
def line_to_line(sa: Vec2, ea: Vec2, sb: Vec2, eb: Vec2):
"""
Renvoie la collision entre deux lignes.
"""
if sa.x == ea.x:
sa.x += 0.0001
if sb.x == eb.x:
sb.x += 0.0001
if sa.y == ea.y:
sa.y += 0.0001
if sb.y == eb.y:
sb.y += 0.0001
divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y)
if divisor == 0:
uA = 0
else:
uA = ((eb.x - sb.x) * (sa.y - sb.y) - (eb.y - sb.y) * (sa.x - sb.x)) / (divisor)
divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y)
if divisor == 0:
uB = 0
else:
uB = ((ea.x - sa.x) * (sa.y - sb.y) - (ea.y - sa.y) * (sa.x - sb.x)) / (divisor)
if uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1:
return (
Vec2((uA * (ea.x - sa.x)), (uA * (ea.y - sa.y))).length / (ea - sa).length
)
return 1.0
def line_to_aabb(start: Vec2, end: Vec2, aabb: AABB):
"""
Renvoie la collision entre une ligne et une AABB.
"""
left = line_to_line(start, end, aabb.min, Vec2(aabb.min.x, aabb.max.y))
right = line_to_line(start, end, Vec2(aabb.max.x, aabb.min.y), aabb.max)
bottom = line_to_line(start, end, aabb.min, Vec2(aabb.max.x, aabb.min.y))
top = line_to_line(start, end, Vec2(aabb.min.x, aabb.max.y), aabb.max)
t = min([left, right, bottom, top])
if t == left:
normal = Vec2(-1, 0)
elif t == right:
normal = Vec2(1, 0)
elif t == bottom:
normal = Vec2(0, -1)
elif t == top:
normal = Vec2(0, 1)
else:
normal = Vec2(0, 0)
return t, normal
def aabb_to_aabb(moving: AABB, static: AABB, movement: Vec2):
"""
Renvoie la collision entre deux AABB.
"""
size = (moving.max - moving.min) / 2
static = AABB(static.min - size, static.max + size, static.entity)
start_pos = moving.min + size
return line_to_aabb(start_pos, start_pos + movement, static)
def aabb_to_aabbs(moving: AABB, statics: list[AABB], movement: Vec2):
"""
Renvoie la collision entre deux AABB.
"""
t = 1.0
normal = Vec2(0, 0)
entity = None
for static in statics:
if static.entity == moving.entity:
continue
result = aabb_to_aabb(moving, static, movement)
if result[0] < t:
t = result[0]
normal = result[1]
entity = static.entity
return t, normal, entity
def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False):
world = entity.world
aabb = AABB.from_entity(entity)
others = [
AABB.from_entity(other) for other in world.query(Solid, Position, Scale, Origin)
]
counter = 0
while movement.length > 0.0001 and counter < 50:
t, normal, obstacle = aabb_to_aabbs(aabb, others, movement)
if t == 1.0:
step = movement
else:
step = movement * max(t - 0.000001, 0)
aabb.move(step)
aabb.entity_position(entity)
movement -= step
if normal.x != 0:
movement.x *= -1
entity[Velocity].x *= -1
if normal.y != 0:
movement.y *= -1
entity[Velocity].y *= -1
movement /= entity[Velocity]
if obstacle is not None and not disable_callback:
if not entity.get(
CollisionHandler, CollisionHandler(lambda e, o: True)
).callback(entity, obstacle):
break
if not obstacle.get(
CollisionHandler, CollisionHandler(lambda e, o: True)
).callback(obstacle, entity):
break
movement *= entity[Velocity]
counter += 1
def __apply_velocity(world: World):
"""
Applique la vélocité a toutes les entitées.
"""
delta = world[Delta]
for entity in world.query(Velocity, Position, Scale, Origin):
move_entity(entity, entity[Velocity] * delta)
PLUGIN = GlobalPlugin(
[],
[__apply_velocity],
[],
[],
)

View file

@ -4,9 +4,10 @@ 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 GlobalPlugin, KeepAlive from engine import GlobalPlugin
from engine.ecs import World from engine.ecs import World
from engine.math import Vec2 from engine.math import Vec2
from plugins import assets
WIDTH = 1440 WIDTH = 1440
@ -29,63 +30,124 @@ def calculate_surface_rect() -> tuple[float, float, float, float]:
return offset, 0.0, target_width, float(height) return offset, 0.0, target_width, float(height)
class Surface(KeepAlive, pygame.Surface): class SpriteBundle:
""" """
Ressource qui stocke la surface de rendu du jeu. Un assemblage de composants permettant de faire une sprite.
""" """
def __new__(
class Sprite: cls,
""" texture: str,
Composant donnant la texture d'une entité, sa position et son ordre de rendu. order: float,
"""
def __init__(
self,
texture: pygame.Surface,
position: Vec2 = Vec2(0), position: Vec2 = Vec2(0),
order: float = -1.0, scale: Optional[Vec2] = None,
area: Optional[tuple[float, float, float, float]] = None,
origin: Vec2 = Vec2(0), origin: Vec2 = Vec2(0),
): ):
self.texture = texture if scale is None:
self.position = position surface = assets.load_texture(texture)
self.order = order scale = Vec2(surface.get_width(), surface.get_height())
if area is None: return (
self.area = (0.0, 0.0, 1.0, 1.0) Texture(texture),
else: Order(order),
self.area = area Position(position),
self.origin = origin Scale(scale),
Origin(origin),
)
def __initialize(world: World): class TextBundle:
""" """
Prépare le monde pour la gestion du rendu. Un assemblage de composants permettant de faire un texte.
""" """
world.set(Surface((WIDTH, HEIGHT)))
def __new__(
cls,
text: str,
order: float,
size: int = 50,
color: pygame.Color = pygame.Color(255, 255, 255),
position: Vec2 = Vec2(0),
origin: Vec2 = Vec2(0),
):
return (
Text(text),
TextSize(size),
TextColor(color),
Position(position),
Order(order),
Origin(origin),
)
def __render(world: World): class Texture(str):
"""
Composant donnant le nom de la texture d'une entité.
"""
class Order(float):
"""
Composant donnant l'ordre d'affichage d'une entité.
"""
class Position(Vec2):
"""
Composant donnant la position de l'origine d'un objet.
"""
class Scale(Vec2):
"""
Composant donnant la taille de la texture d'une entité.
"""
class Origin(Vec2):
"""
Composant définissant l'emplacement de l'origine d'un objet sur sa texture.
"""
class Text(str):
"""
Composant donnant le texte d'une entité.
"""
class TextSize(int):
"""
Composant donnant la taille du texte d'une entité.
"""
class TextColor(pygame.Color):
"""
Composant donnant la couleur du texte d'une entité.
"""
def __render(world: World, surface: pygame.Surface = pygame.Surface((WIDTH, HEIGHT))):
""" """
Rend le monde du jeu sur la surface puis l'affiche sur la fenetre. Rend le monde du jeu sur la surface puis l'affiche sur la fenetre.
""" """
# On rend le monde sur la surface # On rend le monde sur la surface
surface = world[Surface] entities = world.query(Texture, Position, Order, Scale, Origin)
sprites = [entity[Sprite] for entity in world.query(Sprite)] entities.update(world.query(Text, Position, Order, Origin, TextSize, TextColor))
for sprite in sorted(sprites, key=lambda sprite: sprite.order): entities = sorted(entities, key=lambda entity: entity[Order])
original_size = Vec2(*sprite.texture.get_size()) for entity in entities:
size = original_size * Vec2(*sprite.area[2:]) if Text in entity:
position = sprite.position - (sprite.origin * size) texture = assets.load_text(
surface.blit( entity[Text], entity[TextSize], entity[TextColor]
sprite.texture, )
(position.x, position.y), scale = Scale(texture.get_width(), texture.get_height())
( else:
sprite.area[0] * original_size.x, texture = entity[Texture]
sprite.area[1] * original_size.y, texture = assets.load_texture(texture)
sprite.area[2] * original_size.x, scale = entity[Scale]
sprite.area[3] * original_size.y, texture = pygame.transform.scale(texture, (scale.x, scale.y))
), position = entity[Position] - entity[Origin] * scale
) surface.blit(texture, (position.x, position.y))
# On affiche la surface sur la fenetre # On affiche la surface sur la fenetre
rect = calculate_surface_rect() rect = calculate_surface_rect()
@ -99,7 +161,7 @@ def __render(world: World):
PLUGIN = GlobalPlugin( PLUGIN = GlobalPlugin(
[__initialize], [],
[], [],
[__render], [__render],
[], [],

View file

@ -1,40 +0,0 @@
"""
Un plugin permettant de faire des déplacements fluides des entités.
"""
from engine import Plugin, World
from engine.math import Vec2
from plugins.render import Sprite
from plugins.timing import Delta
class Target(Vec2):
"""
Composant donnant la position voulue de l'entité.
"""
class Speed(float):
"""
Composant donnant la vittesse de déplacement de l'entité.
"""
def __update_positions(world: World):
"""
Met à jour la position des entités pour se rapprocher de la position voulue.
"""
for entity in world.query(Sprite, Target):
sprite = entity[Sprite]
target = entity[Target]
speed = entity[Speed] if Speed in entity else Speed(10)
sprite.position = (
sprite.position + (target - sprite.position) * world[Delta] * speed
)
PLUGIN = Plugin(
[],
[__update_positions],
[],
)

View file

@ -5,65 +5,64 @@ Un plugin permettant de jouer des sons.
from typing import Callable from typing import Callable
import pygame import pygame
from engine import GlobalPlugin, KeepAlive from engine import GlobalPlugin
from engine.ecs import Entity, World from engine.ecs import Entity, World
from plugins import assets
class Channels(KeepAlive, dict[Entity, pygame.mixer.Channel]): class Sound(str):
"""
Ressource qui stoque les sons actuellement joués dans le jeu.
"""
class Sound:
""" """
Composant permettant de jouer un son. Composant permettant de jouer un son.
""" """
def __init__(
self, class Volume(float):
sound: pygame.mixer.Sound, """
loop: bool = False, Composant donnant le volume d'un son.
volume: float = 1.0, """
fade_ms: int = 0,
callback: Callable[[World, Entity], object] = lambda _w, _e: None,
): class Loop:
self.sound = sound """
self.loop = loop Composant indiquant si le son joué par l'entité doit se relancer en boucle.
self.volume = volume """
self.fade_ms = fade_ms
class SoundCallback:
"""
Composant donnant une fonction qui sera appelée à la fin du son.
"""
def __init__(self, callback: Callable[[World, Entity], object]):
self.callback = callback self.callback = callback
def __initialize(world: World): def __update_sounds(
""" world: World,
Ajoute les ressources utiles pour le plugin. channels: dict[Entity, pygame.mixer.Channel] = {},
""" cache: dict[str, pygame.mixer.Sound] = {},
world.set(Channels()) ):
def __update_sounds(world: World):
""" """
Met à jour les sons du jeu. Met à jour les sons du jeu.
""" """
# Ajout des sons non gérés # Ajout des sons non gérés
channels = world[Channels]
sound_entities = world.query(Sound) sound_entities = world.query(Sound)
for entity in sound_entities: for entity in sound_entities:
if entity not in channels: if entity not in channels:
sound = entity[Sound] sound_name = entity[Sound]
channel = sound.sound.play(sound.loop, fade_ms=sound.fade_ms) sound = assets.load_sound(sound_name)
channel = sound.play(Loop in entity)
if channel is not None: # type: ignore if channel is not None: # type: ignore
channel.set_volume(sound.volume) channel.set_volume(entity.get(Volume, 1.0))
channels[entity] = channel channels[entity] = channel
# On supprime les sons qui sont arrêtés ou qui n'ont plus d'entité # On supprime les sons qui sont arrêtés ou qui n'ont plus d'entité
channels_to_remove: list[Entity] = [] channels_to_remove: list[Entity] = []
for entity, channel in channels.items(): for entity, channel in channels.items():
if not channel.get_busy() and Sound in entity: if not channel.get_busy() and Sound in entity:
callback = entity[Sound].callback callback = entity.get(SoundCallback, SoundCallback(lambda w, e: None))
del entity[Sound] del entity[Sound]
callback(world, entity) callback.callback(world, entity)
channels_to_remove.append(entity) channels_to_remove.append(entity)
elif entity not in sound_entities: elif entity not in sound_entities:
channel.stop() channel.stop()
@ -73,7 +72,7 @@ def __update_sounds(world: World):
PLUGIN = GlobalPlugin( PLUGIN = GlobalPlugin(
[__initialize], [],
[], [],
[__update_sounds], [__update_sounds],
[], [],

View file

@ -1,65 +0,0 @@
"""
Un plugin permettant d'afficher du texte à l'écran.
"""
from typing import Optional
import pygame
from engine import GlobalPlugin
from engine.ecs import World
from engine.math import Vec2
from plugins.assets import Assets
from plugins.render import Sprite
class Text:
"""
Composant donnant le texte d'une entité, sa taille et sa couleur.
"""
def __init__(
self,
text: str,
size: int = 50,
color: pygame.Color = pygame.Color(255, 255, 255),
position: Optional[Vec2] = None,
order: Optional[float] = None,
origin: Optional[Vec2] = None,
):
self.text = text
self.size = size
self.color = color
self.position = position
self.order = order
self.origin = origin
def __render_texts(world: World):
"""
Rend les textes à l'écran.
Pour rendre les textes, on moddifie le composant `Sprite` des entités.
"""
assets = world[Assets]
for entity in world.query(Text):
text = entity[Text]
texture = assets.get_text(text.size, text.text, text.color)
if Sprite in entity:
entity[Sprite].texture = texture
else:
entity[Sprite] = Sprite(texture)
if text.position is not None:
entity[Sprite].position = text.position
if text.order is not None:
entity[Sprite].order = text.order
if text.origin is not None:
entity[Sprite].origin = text.origin
PLUGIN = GlobalPlugin(
[],
[],
[__render_texts],
[],
)

View file

@ -5,9 +5,8 @@ lancement du jeu et le temps depuis la dernière frame.
from time import time from time import time
from typing import Callable
from engine import GlobalPlugin, KeepAlive from engine import GlobalPlugin, KeepAlive
from engine.ecs import Entity, World from engine.ecs import World
class GlobalTime(KeepAlive, float): class GlobalTime(KeepAlive, float):
@ -28,16 +27,6 @@ class Delta(KeepAlive, float):
""" """
class TimedEvent:
"""
Composant permettant d'executer un callback après un certain temps.
"""
def __init__(self, timer: float, callback: Callable[[World, Entity], object]):
self.timer = timer
self.callback = callback
def __initialize(world: World): def __initialize(world: World):
""" """
Initialise les ressources pour la gestion du temps. Initialise les ressources pour la gestion du temps.
@ -47,22 +36,13 @@ def __initialize(world: World):
def __update(world: World): def __update(world: World):
""" """
Met à jour les ressources de temps et execute les `TimedEvent`. Met à jour les ressources de temps.
""" """
# On met à jour le temps
now = time() now = time()
world[Delta] = delta = now - world[GlobalTime] world[Delta] = delta = now - world[GlobalTime]
world[GlobalTime] = now world[GlobalTime] = now
world[Time] += delta world[Time] += delta
# On met à jour les `TimedEvent`
for entity in world.query(TimedEvent):
event = entity[TimedEvent]
event.timer -= delta
if event.timer <= 0:
del entity[TimedEvent]
event.callback(world, entity)
PLUGIN = GlobalPlugin( PLUGIN = GlobalPlugin(
[__initialize], [__initialize],

View file

@ -1,56 +0,0 @@
"""
Definit un plugin qui crée un texte avec les touches frappées
"""
from engine import Scene, World
from plugins.inputs import Pressed
from plugins.text import Text
from scenes import CLICK_SOUND
class Writing:
"""
Marque une entité comme un texte qui s'ecrit en fonction du clavier
"""
def __init__(
self, accepted_chars: str, max_chars: int = 10, base_text: str = ""
) -> None:
self.accepted_chars = accepted_chars
self.max_chars = max_chars
self.base_text = base_text
def __update(world: World):
"""
Met a jour les entitées contenant le composant Typing
"""
pressed = world[Pressed]
for entity in world.query(Writing, Text):
writing = entity[Writing]
text = entity[Text]
for key in pressed:
if key == "backspace" and text.text != writing.base_text:
text.text = text.text[:-1]
world.new_entity().set(CLICK_SOUND)
if text.text == "":
text.text = writing.base_text
if key.startswith("["): # pavé numerique
key = key[1]
if key in writing.accepted_chars and (
text.text == writing.base_text or len(text.text) < writing.max_chars
):
if text.text == writing.base_text:
text.text = key
else:
text.text += key
world.new_entity().set(CLICK_SOUND)
if text.text == "":
text.text = writing.base_text
PLUGIN = Scene(
[],
[__update],
[],
)

View file

@ -1,8 +1,3 @@
""" """
Module contenant toutes les scènes du jeu. Module contenant toutes les scènes du jeu.
""" """
from plugins.multisound import MultiSound
CLICK_SOUND = MultiSound("click/0", "click/1", "click/2")

View file

@ -1,69 +0,0 @@
"""
La scène du menu principal du jeu.
Dans cette scène nous pouvons choisir le mode de jeu.
"""
from scenes import CLICK_SOUND, one_player
from engine import CurrentScene, KeepAlive, Scene
from engine.ecs import Entity, World
from engine.math import Vec2
from plugins import render
from plugins import assets as plugin_assets
from plugins.assets import Assets
from plugins.click import Clickable
from plugins.hover import HoveredTexture
from plugins.render import Sprite
def __create_button(world: World, assets: Assets, i: int, name: str):
"""
Ajoute un bouton au monde.
"""
world.new_entity().set(
Sprite(
assets.error_texture,
Vec2(500 + 450 * i, render.HEIGHT / 2),
1,
origin=Vec2(0.5),
),
HoveredTexture(
assets.get_texture(f"button_{name}"),
assets.get_texture(f"button_{name}_hover"),
),
Clickable(lambda world, entity: __on_click_butons(world, entity, name)),
)
def __on_click_butons(world: World, _entity: Entity, name: str):
"""
Fonction qui s'execute quand on clique sur un bouton.
"""
match name:
case "one_player":
world[CurrentScene] = one_player.SCENE
case "two_player":
pass
case _:
pass
world.new_entity().set(KeepAlive(), CLICK_SOUND)
def __spawn_elements(world: World):
"""
Ajoute les éléments du menu dans le monde.
"""
assets = world[Assets]
world.new_entity().set(Sprite(assets.get_texture("background")))
scenes_name = ["one_player", "two_player"]
for i, name in enumerate(scenes_name):
__create_button(world, assets, i, name)
SCENE = plugin_assets.loading_scene(
Scene(
[__spawn_elements],
[],
[],
),
"menu",
)

View file

@ -1,22 +0,0 @@
from engine import Scene
from engine.ecs import World
from plugins import assets as plugin_assets
from plugins.render import Sprite
def __spawn_elements(world: World):
"""
Ajoute les éléments du menu dans le monde.
"""
assets = world[plugin_assets.Assets]
world.new_entity().set(Sprite(assets.get_texture("background")))
SCENE = plugin_assets.loading_scene(
Scene(
[],
[],
[],
),
"one_player",
)