This commit is contained in:
Timéo Cézard 2024-01-04 01:12:25 +01:00
parent d8e64348cd
commit 5952cf8931
18 changed files with 32 additions and 1196 deletions

View file

@ -17,13 +17,13 @@ from plugins.render import (
from plugins.timing import Delta
def lol(a: Entity, b: Entity):
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.5), 1000.0
(speed * 1.1), 1000.0
)
return True
@ -33,7 +33,7 @@ class Bounce:
def lol_simul(a: Entity, b: Entity):
lol(a, b)
lol(a, b, True)
return RightWall not in b
@ -161,6 +161,18 @@ def __initialize(world: World):
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):
"""
@ -190,10 +202,21 @@ def __update(world: World):
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 / 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)

View file

@ -1,10 +0,0 @@
"""
Module contenant tous les plugins du jeu.
Un plugin est une scène pouvant être ajouté a d'autres scènes
afin d'ajouter des fonctionnalités au jeu.
Le but est de faire en sorte que les plugins soient génériques
afin de pouvoir les utilisers dans plusieurs scènes et donc
éviter de répéter plusieurs fois le même code.
"""

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 +0,0 @@
"""
Un plugin qui gère les assets du jeu.
"""
import glob
import random
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):
"""
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")
# 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):
"""
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.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

@ -1,48 +0,0 @@
"""
Un plugin permettant de savoir si l'on a cliqué sur une entité.
"""
from typing import Callable
from engine import GlobalPlugin
from engine.ecs import Entity, World
from plugins.hover import Hovered
from plugins.inputs import Pressed
from plugins.render import Sprite
class Clicked:
"""
Component ajouté a toutes les entitées qui viennent d'être cliqué.
"""
class Clickable:
"""
Composant qui permet d'executer une fonction lorsqu'une entité est cliquee.
"""
def __init__(self, callback: Callable[[World, Entity], object]):
self.callback = callback
def __update_clicked(world: World):
"""
Met à jour les composants `Clicked`.
"""
mouse_click = "button_1" in world[Pressed]
sprite_entities = world.query(Sprite)
for entity in sprite_entities:
if Hovered in entity and mouse_click:
entity[Clicked] = Clicked()
if Clickable in entity:
entity[Clickable].callback(world, entity)
else:
del entity[Clicked]
PLUGIN = GlobalPlugin(
[],
[__update_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

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

View file

@ -1,32 +0,0 @@
"""
Un plugin pour la gestion de la fenetre du jeu.
"""
import pygame
from engine.ecs import World
from engine import GlobalPlugin
def __initialize(_world: World):
"""
Initialise pygame et les ressources pour la gestion de la fenetre.
"""
pygame.init()
pygame.display.set_caption("Guess The Number")
pygame.display.set_mode((800, 600), pygame.RESIZABLE)
def __terminate(_world: World):
"""
Arrête pygame.
"""
pygame.quit()
PLUGIN = GlobalPlugin(
[__initialize],
[],
[],
[__terminate],
)

View file

@ -1,92 +0,0 @@
"""
Un plugin permettant de savoir si la souris est par dessus une entité.
"""
import pygame
from engine import GlobalPlugin
from engine.ecs import World
from engine.math import Vec2
from plugins.inputs import MousePosition
from plugins.render import Sprite
class HoverEnter:
"""
Composant indicant que l'entité vient de commencer a être survolée par la souris.
"""
class Hovered:
"""
Composant indicant que l'entité est survolée par la souris.
"""
class HoverExit:
"""
Composant indicant que la souris viens d'arreter de survoler l'entité.
"""
class HoveredTexture:
"""
Composant permettant de changer la texture d'une entité lorsque
celle-ci est survolée par la souris.
"""
def __init__(self, normal: pygame.Surface, hovered: pygame.Surface):
self.normal = normal
self.hovered = hovered
def __update_hovered(world: World):
"""
Vérifie le survol de la souris sur les entitées.
"""
# On met à jour les composants
mouse_position = world[MousePosition]
for entity in world.query(Sprite):
# Récupération de la position et taille de l'entité
sprite = entity[Sprite]
size = Vec2(*sprite.texture.get_size()) * Vec2(*sprite.area[2:])
position = sprite.position - (sprite.origin * size)
# On détermine si la souris est sur l'entité
if (
mouse_position.x >= position.x
and mouse_position.x <= position.x + size.x
and mouse_position.y >= position.y
and mouse_position.y <= position.y + size.y
):
if Hovered not in entity:
entity[HoverEnter] = HoverEnter()
else:
del entity[HoverEnter]
entity[Hovered] = Hovered()
else:
if Hovered in entity:
entity[HoverExit] = HoverExit()
else:
del entity[HoverExit]
del entity[Hovered]
# On affiche la bonne texture
for entity in world.query(HoveredTexture):
if Hovered in entity:
texture = entity[HoveredTexture].hovered
else:
texture = entity[HoveredTexture].normal
if Sprite in entity:
entity[Sprite].texture = texture
else:
entity[Sprite] = Sprite(texture)
PLUGIN = GlobalPlugin(
[],
[__update_hovered],
[],
[],
)

View file

@ -1,90 +0,0 @@
"""
Un plugin permettant de gérer les entrées utilisateur du jeu.
"""
import pygame
from engine import CurrentScene, GlobalPlugin, KeepAlive
from engine.ecs import World
from engine.math import Vec2
from plugins import render
class Pressed(KeepAlive, set[str]):
"""
Ressource qui correspond aux touches qui viennent d'être préssées par l'utilisateur.
Pour les boutons de la souris la touche sera nommée `button_x` avec `x` le numéro du bouton.
"""
class Held(KeepAlive, set[str]):
"""
Ressource qui correspond aux touches qui sont actuellement préssées par l'utilisateur.
"""
class Released(KeepAlive, set[str]):
"""
Ressource qui correspond aux touches qui viennent d'être relachées par l'utilisateur.
"""
class MousePosition(KeepAlive, Vec2):
"""
Ressource qui correspond à la position de la souris.
"""
def __initialize(world: World):
"""
Initialise des ressources pour la gestion des entrées utilisateur.
"""
world.set(Pressed(), Held(), Released(), MousePosition())
def __update_input(world: World):
"""
Met à jour les ressources qui permettent de savoir quels sont les
entrées utilisateur.
"""
# On récupère les ressources
pressed = world[Pressed]
held = world[Held]
released = world[Released]
# On clear les touches pressées et relachées
pressed.clear()
released.clear()
# On récupère les évenements de pygame
for event in pygame.event.get():
if event.type == pygame.QUIT:
del world[CurrentScene]
elif event.type == pygame.KEYDOWN:
key_name = pygame.key.name(event.key)
held.add(key_name)
pressed.add(key_name)
elif event.type == pygame.KEYUP:
key_name = pygame.key.name(event.key)
held.remove(key_name)
released.add(key_name)
elif event.type == pygame.MOUSEBUTTONDOWN:
held.add(f"button_{event.button}")
pressed.add(f"button_{event.button}")
elif event.type == pygame.MOUSEBUTTONUP:
held.remove(f"button_{event.button}")
released.add(f"button_{event.button}")
elif event.type == pygame.MOUSEMOTION:
x, y, w, h = render.calculate_surface_rect()
world[MousePosition] = Vec2(
((event.pos[0] - x) / w) * render.WIDTH,
((event.pos[1] - y) / h) * render.HEIGHT,
)
PLUGIN = GlobalPlugin(
[__initialize],
[__update_input],
[],
[],
)

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],
[],
)

View file

@ -1,106 +0,0 @@
"""
Un plugin qui s'occupe de rendre des choses dans la fenetre.
"""
from typing import Optional
import pygame
from engine import GlobalPlugin, KeepAlive
from engine.ecs import World
from engine.math import Vec2
WIDTH = 1440
HEIGHT = 1080
RATIO = WIDTH / HEIGHT
INVERT_RATIO = HEIGHT / WIDTH
def calculate_surface_rect() -> tuple[float, float, float, float]:
"""
Calcule et renvoie un rectangle qui corresponed a la zone dans laquelle le jeu est rendu.
"""
width, height = pygame.display.get_surface().get_size()
if width / height < RATIO:
target_height = width * INVERT_RATIO
offset = (height - target_height) / 2
return 0.0, offset, float(width), target_height
target_width = height * RATIO
offset = (width - target_width) / 2
return offset, 0.0, target_width, float(height)
class Surface(KeepAlive, pygame.Surface):
"""
Ressource qui stocke la surface de rendu du jeu.
"""
class Sprite:
"""
Composant donnant la texture d'une entité, sa position et son ordre de rendu.
"""
def __init__(
self,
texture: pygame.Surface,
position: Vec2 = Vec2(0),
order: float = -1.0,
area: Optional[tuple[float, float, float, float]] = None,
origin: Vec2 = Vec2(0),
):
self.texture = texture
self.position = position
self.order = order
if area is None:
self.area = (0.0, 0.0, 1.0, 1.0)
else:
self.area = area
self.origin = origin
def __initialize(world: World):
"""
Prépare le monde pour la gestion du rendu.
"""
world.set(Surface((WIDTH, HEIGHT)))
def __render(world: World):
"""
Rend le monde du jeu sur la surface puis l'affiche sur la fenetre.
"""
# On rend le monde sur la surface
surface = world[Surface]
sprites = [entity[Sprite] for entity in world.query(Sprite)]
for sprite in sorted(sprites, key=lambda sprite: sprite.order):
original_size = Vec2(*sprite.texture.get_size())
size = original_size * Vec2(*sprite.area[2:])
position = sprite.position - (sprite.origin * size)
surface.blit(
sprite.texture,
(position.x, position.y),
(
sprite.area[0] * original_size.x,
sprite.area[1] * original_size.y,
sprite.area[2] * original_size.x,
sprite.area[3] * original_size.y,
),
)
# On affiche la surface sur la fenetre
rect = calculate_surface_rect()
pygame.transform.set_smoothscale_backend("MMX")
pygame.transform.smoothscale(
surface,
(rect[2], rect[3]),
pygame.display.get_surface().subsurface(rect),
)
pygame.display.flip()
PLUGIN = GlobalPlugin(
[__initialize],
[],
[__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

@ -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

@ -1,52 +0,0 @@
"""
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, KeepAlive
from engine.ecs import World
class GlobalTime(KeepAlive, float):
"""
Ressource qui représente le temps global de l'ordinateur sur lequel tourne le jeu.
"""
class Time(KeepAlive, float):
"""
Ressource qui représente le temps depuis le lancement du jeu.
"""
class Delta(KeepAlive, 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],
[],
[],
)

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 GlobalPlugin
from engine.ecs import World
@ -39,9 +40,12 @@ class SpriteBundle:
texture: str,
order: float,
position: Vec2 = Vec2(0),
scale: Vec2 = Vec2(128),
scale: Optional[Vec2] = None,
origin: Vec2 = Vec2(0),
):
if scale is None:
surface = assets.load_texture(texture)
scale = Vec2(surface.get_width(), surface.get_height())
return (
Texture(texture),
Order(order),

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",
)