better_ecs #2
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
BIN
assets/textures/background.png
Normal file
BIN
assets/textures/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
BIN
assets/textures/dada.png
Normal file
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
BIN
assets/textures/dodo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
|
@ -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é:
|
||||||
|
|
238
src/main.py
238
src/main.py
|
@ -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)
|
||||||
|
|
|
@ -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 +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):
|
|
||||||
# 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:
|
if surface is None:
|
||||||
surface = self.__texts[(size, text)] = self.get_font(size).render(
|
surface = pygame.image.load(f"assets/textures/{name}").convert_alpha()
|
||||||
text, True, color
|
cache[name] = surface
|
||||||
)
|
|
||||||
return surface
|
return surface
|
||||||
|
|
||||||
def load_sound(self, name: str, path: str) -> pygame.mixer.Sound:
|
|
||||||
|
def load_sound(
|
||||||
|
name: str, cache: dict[str, pygame.mixer.Sound] = {}
|
||||||
|
) -> pygame.mixer.Sound:
|
||||||
"""
|
"""
|
||||||
Charge un son et le renvoi. Si un son existe déja dans le cache,
|
Charge un son et le renvoi.
|
||||||
il sera remplacé par le nouveau.
|
|
||||||
"""
|
"""
|
||||||
sound = pygame.mixer.Sound(path)
|
sound = cache.get(name)
|
||||||
self.__sounds[name] = sound
|
if sound is None:
|
||||||
|
sound = pygame.mixer.Sound(f"assets/sounds/{name}")
|
||||||
|
cache[name] = sound
|
||||||
return 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é.
|
def load_text(
|
||||||
|
text: str,
|
||||||
|
size: int,
|
||||||
|
color: pygame.Color,
|
||||||
|
cache: dict[tuple[str, int, tuple[int, int, int]], pygame.Surface] = {},
|
||||||
|
) -> pygame.Surface:
|
||||||
"""
|
"""
|
||||||
return self.__sounds.get(name, self.__error_sound)
|
Charge un texte et le renvoi.
|
||||||
|
|
||||||
def clear_cache(self):
|
|
||||||
"""
|
"""
|
||||||
Vide le cache des assets.
|
surface = cache.get((text, size, (color.r, color.g, color.b)))
|
||||||
|
if surface is None:
|
||||||
Les fonts ne sont pas effacés car ils n'y a normalement pas énormément
|
surface = pygame.font.Font("assets/font.ttf", size).render(text, True, color)
|
||||||
de taille de police différentes utilisées.
|
cache[(text, size, (color.r, color.g, color.b))] = surface
|
||||||
"""
|
return surface
|
||||||
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],
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
)
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
194
src/plugins/physics.py
Normal 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],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
)
|
|
@ -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),
|
|
||||||
(
|
|
||||||
sprite.area[0] * original_size.x,
|
|
||||||
sprite.area[1] * original_size.y,
|
|
||||||
sprite.area[2] * original_size.x,
|
|
||||||
sprite.area[3] * original_size.y,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
scale = Scale(texture.get_width(), texture.get_height())
|
||||||
|
else:
|
||||||
|
texture = entity[Texture]
|
||||||
|
texture = assets.load_texture(texture)
|
||||||
|
scale = entity[Scale]
|
||||||
|
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],
|
||||||
[],
|
[],
|
||||||
|
|
|
@ -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],
|
|
||||||
[],
|
|
||||||
)
|
|
|
@ -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],
|
||||||
[],
|
[],
|
||||||
|
|
|
@ -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],
|
|
||||||
[],
|
|
||||||
)
|
|
|
@ -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],
|
||||||
|
|
|
@ -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],
|
|
||||||
[],
|
|
||||||
)
|
|
|
@ -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")
|
|
||||||
|
|
|
@ -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