Finalize
This commit is contained in:
parent
d8e64348cd
commit
5952cf8931
31
src/main.py
31
src/main.py
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
"""
|
|
@ -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],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
[],
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -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],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
)
|
|
@ -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],
|
||||
[],
|
||||
[],
|
||||
)
|
|
@ -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),
|
||||
|
|
|
@ -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",
|
||||
)
|
|
@ -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",
|
||||
)
|
Loading…
Reference in a new issue