Good
This commit is contained in:
parent
bac035f00d
commit
d8e64348cd
Binary file not shown.
Before Width: | Height: | Size: 43 KiB 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 |
|
@ -146,10 +146,17 @@ class World(Entity):
|
|||
- `entity`: l'entité dans lequelle ajouter le composant.
|
||||
- `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.__entities.setdefault(type(component), set()).add(entity.identifier)
|
||||
|
||||
def get_component[T](
|
||||
def get_component[
|
||||
T
|
||||
](
|
||||
self, entity: "Entity", component_type: type[T], default: Optional[T] = None
|
||||
) -> T:
|
||||
"""
|
||||
|
|
203
src/main.py
203
src/main.py
|
@ -3,9 +3,58 @@ Module d'exemple de l'utilisation du moteur de jeu.
|
|||
"""
|
||||
|
||||
from engine import Scene, start_game
|
||||
from engine.ecs import World
|
||||
from plugins import defaults
|
||||
from plugins.render import Origin, Position, Scale, Texture
|
||||
from engine.ecs import Entity, World
|
||||
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
|
||||
|
||||
|
||||
def lol(a: Entity, b: Entity):
|
||||
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
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class Bounce:
|
||||
pass
|
||||
|
||||
|
||||
def lol_simul(a: Entity, b: Entity):
|
||||
lol(a, b)
|
||||
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):
|
||||
|
@ -13,16 +62,154 @@ def __initialize(world: World):
|
|||
Initialise les ressources pour le moteur de jeu.
|
||||
"""
|
||||
world.new_entity().set(
|
||||
Texture("background.png", 0),
|
||||
# Scale(1000, 1000),
|
||||
# Origin(0.5, 0.5),
|
||||
# Position(600, 600),
|
||||
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),
|
||||
)
|
||||
|
||||
|
||||
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.set(physics.Solid())
|
||||
entity.destroy()
|
||||
|
||||
# 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],
|
||||
[],
|
||||
)
|
||||
|
||||
|
|
45
src/plugins/assets.py
Normal file
45
src/plugins/assets.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
"""
|
||||
Ce module contient des utilitaires pour le chargement des ressources du jeu.
|
||||
"""
|
||||
|
||||
import pygame
|
||||
|
||||
|
||||
def load_texture(name: str, cache: dict[str, pygame.Surface] = {}) -> pygame.Surface:
|
||||
"""
|
||||
Charge une texture et la renvoi.
|
||||
"""
|
||||
surface = cache.get(name)
|
||||
if surface is None:
|
||||
surface = pygame.image.load(f"assets/textures/{name}").convert_alpha()
|
||||
cache[name] = surface
|
||||
return surface
|
||||
|
||||
|
||||
def load_sound(
|
||||
name: str, cache: dict[str, pygame.mixer.Sound] = {}
|
||||
) -> pygame.mixer.Sound:
|
||||
"""
|
||||
Charge un son et le renvoi.
|
||||
"""
|
||||
sound = cache.get(name)
|
||||
if sound is None:
|
||||
sound = pygame.mixer.Sound(f"assets/sounds/{name}")
|
||||
cache[name] = sound
|
||||
return sound
|
||||
|
||||
|
||||
def load_text(
|
||||
text: str,
|
||||
size: int,
|
||||
color: pygame.Color,
|
||||
cache: dict[tuple[str, int, tuple[int, int, int]], pygame.Surface] = {},
|
||||
) -> pygame.Surface:
|
||||
"""
|
||||
Charge un texte et le renvoi.
|
||||
"""
|
||||
surface = cache.get((text, size, (color.r, color.g, color.b)))
|
||||
if surface is None:
|
||||
surface = pygame.font.Font("assets/font.ttf", size).render(text, True, color)
|
||||
cache[(text, size, (color.r, color.g, color.b))] = surface
|
||||
return surface
|
|
@ -2,13 +2,12 @@
|
|||
Un plugin permettant de savoir si l'on a cliqué sur une entité.
|
||||
"""
|
||||
|
||||
from tkinter import Scale
|
||||
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 Position
|
||||
from plugins.render import Origin, Position, Scale
|
||||
|
||||
|
||||
class Clicked:
|
||||
|
@ -31,7 +30,7 @@ def __update_clicked(world: World):
|
|||
Met à jour les composants `Clicked`.
|
||||
"""
|
||||
mouse_click = "button_1" in world[Pressed]
|
||||
sprite_entities = world.query(Position, Scale)
|
||||
sprite_entities = world.query(Position, Scale, Origin)
|
||||
for entity in sprite_entities:
|
||||
if Hovered in entity and mouse_click:
|
||||
entity[Clicked] = Clicked()
|
||||
|
|
|
@ -2,14 +2,16 @@
|
|||
Plugin qui rassemple tous les plugins globaux.
|
||||
"""
|
||||
|
||||
from plugins import display, inputs, sound, render, timing, hover
|
||||
from plugins import click, display, inputs, physics, sound, render, timing, hover
|
||||
|
||||
|
||||
PLUGIN = (
|
||||
display.PLUGIN
|
||||
+ timing.PLUGIN
|
||||
+ inputs.PLUGIN
|
||||
+ physics.PLUGIN
|
||||
+ hover.PLUGIN
|
||||
+ click.PLUGIN
|
||||
+ sound.PLUGIN
|
||||
+ render.PLUGIN
|
||||
)
|
||||
|
|
|
@ -44,10 +44,10 @@ def __update_hovered(world: World):
|
|||
"""
|
||||
# On met à jour les composants
|
||||
mouse_position = world[MousePosition]
|
||||
for entity in world.query(Position, Scale):
|
||||
for entity in world.query(Position, Scale, Origin):
|
||||
# Récupération de la position et taille de l'entité
|
||||
size = entity[Scale]
|
||||
position = entity[Position] - (entity.get(Origin, Origin(0)) * size)
|
||||
position = entity[Position] - (entity[Origin] * size)
|
||||
|
||||
# On détermine si la souris est sur l'entité
|
||||
if (
|
||||
|
|
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],
|
||||
[],
|
||||
[],
|
||||
)
|
|
@ -3,9 +3,10 @@ Un plugin qui s'occupe de rendre des choses dans la fenetre.
|
|||
"""
|
||||
|
||||
import pygame
|
||||
from engine import GlobalPlugin, KeepAlive
|
||||
from engine import GlobalPlugin
|
||||
from engine.ecs import World
|
||||
from engine.math import Vec2
|
||||
from plugins import assets
|
||||
|
||||
|
||||
WIDTH = 1440
|
||||
|
@ -28,6 +29,52 @@ def calculate_surface_rect() -> tuple[float, float, float, float]:
|
|||
return offset, 0.0, target_width, float(height)
|
||||
|
||||
|
||||
class SpriteBundle:
|
||||
"""
|
||||
Un assemblage de composants permettant de faire une sprite.
|
||||
"""
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
texture: str,
|
||||
order: float,
|
||||
position: Vec2 = Vec2(0),
|
||||
scale: Vec2 = Vec2(128),
|
||||
origin: Vec2 = Vec2(0),
|
||||
):
|
||||
return (
|
||||
Texture(texture),
|
||||
Order(order),
|
||||
Position(position),
|
||||
Scale(scale),
|
||||
Origin(origin),
|
||||
)
|
||||
|
||||
|
||||
class TextBundle:
|
||||
"""
|
||||
Un assemblage de composants permettant de faire un texte.
|
||||
"""
|
||||
|
||||
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),
|
||||
)
|
||||
|
||||
|
||||
class Texture(str):
|
||||
"""
|
||||
Composant donnant le nom de la texture d'une entité.
|
||||
|
@ -58,37 +105,44 @@ class Origin(Vec2):
|
|||
"""
|
||||
|
||||
|
||||
class Surface(KeepAlive, pygame.Surface):
|
||||
class Text(str):
|
||||
"""
|
||||
Ressource qui stocke la surface de rendu du jeu.
|
||||
Composant donnant le texte d'une entité.
|
||||
"""
|
||||
|
||||
|
||||
def __initialize(world: World):
|
||||
class TextSize(int):
|
||||
"""
|
||||
Prépare le monde pour la gestion du rendu.
|
||||
Composant donnant la taille du texte d'une entité.
|
||||
"""
|
||||
world.set(Surface((WIDTH, HEIGHT)))
|
||||
|
||||
|
||||
def __render(world: World, cache: dict[str, pygame.Surface] = {}):
|
||||
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.
|
||||
"""
|
||||
# On rend le monde sur la surface
|
||||
surface: Surface = world[Surface]
|
||||
entities = sorted(world.query(Texture), key=lambda entity: entity.get(Order, -1))
|
||||
entities = world.query(Texture, Position, Order, Scale, Origin)
|
||||
entities.update(world.query(Text, Position, Order, Origin, TextSize, TextColor))
|
||||
entities = sorted(entities, key=lambda entity: entity[Order])
|
||||
for entity in entities:
|
||||
texture_name = entity[Texture]
|
||||
texture = cache.get(texture_name)
|
||||
if texture is None:
|
||||
texture = pygame.image.load(f"assets/textures/{texture_name}")
|
||||
cache[texture_name] = texture
|
||||
scale = entity.get(Scale, Scale(128))
|
||||
texture = pygame.transform.scale(texture, (scale.x, scale.y))
|
||||
position = (
|
||||
entity.get(Position, Position(0)) - (entity.get(Origin, Origin(0))) * scale
|
||||
if Text in entity:
|
||||
texture = assets.load_text(
|
||||
entity[Text], entity[TextSize], entity[TextColor]
|
||||
)
|
||||
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
|
||||
|
@ -103,7 +157,7 @@ def __render(world: World, cache: dict[str, pygame.Surface] = {}):
|
|||
|
||||
|
||||
PLUGIN = GlobalPlugin(
|
||||
[__initialize],
|
||||
[],
|
||||
[],
|
||||
[__render],
|
||||
[],
|
||||
|
|
|
@ -5,43 +5,38 @@ Un plugin permettant de jouer des sons.
|
|||
|
||||
from typing import Callable
|
||||
import pygame
|
||||
from engine import GlobalPlugin, KeepAlive
|
||||
from engine import GlobalPlugin
|
||||
from engine.ecs import Entity, World
|
||||
from plugins import assets
|
||||
|
||||
|
||||
class Channels(KeepAlive, dict[Entity, pygame.mixer.Channel]):
|
||||
"""
|
||||
Ressource qui stoque les sons actuellement joués dans le jeu.
|
||||
"""
|
||||
|
||||
|
||||
class Sound:
|
||||
class Sound(str):
|
||||
"""
|
||||
Composant permettant de jouer un son.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sound: str,
|
||||
loop: bool = False,
|
||||
volume: float = 1.0,
|
||||
fade_ms: int = 0,
|
||||
callback: Callable[[World, Entity], object] = lambda _w, _e: None,
|
||||
):
|
||||
self.sound = sound
|
||||
self.loop = loop
|
||||
self.volume = volume
|
||||
self.fade_ms = fade_ms
|
||||
|
||||
class Volume(float):
|
||||
"""
|
||||
Composant donnant le volume d'un son.
|
||||
"""
|
||||
|
||||
|
||||
class Loop:
|
||||
"""
|
||||
Composant indiquant si le son joué par l'entité doit se relancer en boucle.
|
||||
"""
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def __initialize(world: World):
|
||||
"""
|
||||
Ajoute les ressources utiles pour le plugin.
|
||||
"""
|
||||
world.set(Channels())
|
||||
|
||||
|
||||
def __update_sounds(
|
||||
world: World,
|
||||
channels: dict[Entity, pygame.mixer.Channel] = {},
|
||||
|
@ -51,23 +46,23 @@ def __update_sounds(
|
|||
Met à jour les sons du jeu.
|
||||
"""
|
||||
# Ajout des sons non gérés
|
||||
channels = world[Channels]
|
||||
sound_entities = world.query(Sound)
|
||||
for entity in sound_entities:
|
||||
if entity not in channels:
|
||||
sound = entity[Sound]
|
||||
channel = sound.sound.play(sound.loop, fade_ms=sound.fade_ms)
|
||||
sound_name = entity[Sound]
|
||||
sound = assets.load_sound(sound_name)
|
||||
channel = sound.play(Loop in entity)
|
||||
if channel is not None: # type: ignore
|
||||
channel.set_volume(sound.volume)
|
||||
channel.set_volume(entity.get(Volume, 1.0))
|
||||
channels[entity] = channel
|
||||
|
||||
# On supprime les sons qui sont arrêtés ou qui n'ont plus d'entité
|
||||
channels_to_remove: list[Entity] = []
|
||||
for entity, channel in channels.items():
|
||||
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]
|
||||
callback(world, entity)
|
||||
callback.callback(world, entity)
|
||||
channels_to_remove.append(entity)
|
||||
elif entity not in sound_entities:
|
||||
channel.stop()
|
||||
|
@ -77,7 +72,7 @@ def __update_sounds(
|
|||
|
||||
|
||||
PLUGIN = GlobalPlugin(
|
||||
[__initialize],
|
||||
[],
|
||||
[],
|
||||
[__update_sounds],
|
||||
[],
|
||||
|
|
Loading…
Reference in a new issue