Merge pull request 'Ajout du système de rendu, d'input et de fenêtre' (#7) from pygame into main
Reviewed-on: #7
This commit is contained in:
commit
de14b06c55
|
@ -147,6 +147,15 @@ class World:
|
||||||
resource: _T = self._resources[resource_type] # type: ignore[assignment]
|
resource: _T = self._resources[resource_type] # type: ignore[assignment]
|
||||||
return resource
|
return resource
|
||||||
|
|
||||||
|
def __delitem__(self, resource_type: Type[_T]) -> None:
|
||||||
|
"""
|
||||||
|
Supprime la ressource de type *resource_type.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
resource_type: Le type de ressource.
|
||||||
|
"""
|
||||||
|
self.remove(resource_type)
|
||||||
|
|
||||||
def __contains__(self, *resource_types: Type[_T]) -> bool:
|
def __contains__(self, *resource_types: Type[_T]) -> bool:
|
||||||
"""
|
"""
|
||||||
Renvoie True si le monde contient toutes les ressources de *resource_types.
|
Renvoie True si le monde contient toutes les ressources de *resource_types.
|
||||||
|
@ -219,6 +228,15 @@ class Entity:
|
||||||
"""
|
"""
|
||||||
return self._components[component_type]
|
return self._components[component_type]
|
||||||
|
|
||||||
|
def __delitem__(self, component_type: Type[_T]) -> None:
|
||||||
|
"""
|
||||||
|
Supprime le composant de type *component_type.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
component_type: Le type du composant.
|
||||||
|
"""
|
||||||
|
self.remove(component_type)
|
||||||
|
|
||||||
def __contains__(self, *component_types: Type[_T]) -> bool:
|
def __contains__(self, *component_types: Type[_T]) -> bool:
|
||||||
"""
|
"""
|
||||||
Renvoie True si l'entité contient tous les composants de *component_types.
|
Renvoie True si l'entité contient tous les composants de *component_types.
|
||||||
|
@ -264,15 +282,29 @@ class Game:
|
||||||
*plugins: Les plugins a ajouter au jeu.
|
*plugins: Les plugins a ajouter au jeu.
|
||||||
"""
|
"""
|
||||||
self._running = False
|
self._running = False
|
||||||
|
self._pre_startup_tasks: list[Callable[[World], None]] = []
|
||||||
self._startup_tasks: list[Callable[[World], None]] = []
|
self._startup_tasks: list[Callable[[World], None]] = []
|
||||||
|
self._post_startup_tasks: list[Callable[[World], None]] = []
|
||||||
self._pre_update_tasks: list[Callable[[World], None]] = []
|
self._pre_update_tasks: list[Callable[[World], None]] = []
|
||||||
self._update_tasks: list[Callable[[World], None]] = []
|
self._update_tasks: list[Callable[[World], None]] = []
|
||||||
self._post_update_tasks: list[Callable[[World], None]] = []
|
self._post_update_tasks: list[Callable[[World], None]] = []
|
||||||
|
self._pre_render_tasks: list[Callable[[World], None]] = []
|
||||||
self._render_tasks: list[Callable[[World], None]] = []
|
self._render_tasks: list[Callable[[World], None]] = []
|
||||||
|
self._post_render_tasks: list[Callable[[World], None]] = []
|
||||||
|
self._pre_shutdown_tasks: list[Callable[[World], None]] = []
|
||||||
self._shutdown_tasks: list[Callable[[World], None]] = []
|
self._shutdown_tasks: list[Callable[[World], None]] = []
|
||||||
|
self._post_shutdown_tasks: list[Callable[[World], None]] = []
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
plugin.apply(self)
|
plugin.apply(self)
|
||||||
|
|
||||||
|
def add_pre_startup_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
|
"""
|
||||||
|
Ajoute des taches qui s'executeront en premier avant le démarrage du jeu.
|
||||||
|
"""
|
||||||
|
if self._running:
|
||||||
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
|
self._pre_startup_tasks.extend(tasks)
|
||||||
|
|
||||||
def add_startup_tasks(self, *tasks: Callable[[World], None]) -> None:
|
def add_startup_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
"""
|
"""
|
||||||
Ajoute des taches qui s'executeront au démarrage du jeu.
|
Ajoute des taches qui s'executeront au démarrage du jeu.
|
||||||
|
@ -284,6 +316,14 @@ class Game:
|
||||||
raise RuntimeError("Cannot add a task while the loop is running")
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
self._startup_tasks.extend(tasks)
|
self._startup_tasks.extend(tasks)
|
||||||
|
|
||||||
|
def add_post_startup_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
|
"""
|
||||||
|
Ajoute des taches qui s'executeront en dernier après le démarrage du jeu.
|
||||||
|
"""
|
||||||
|
if self._running:
|
||||||
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
|
self._post_startup_tasks.extend(tasks)
|
||||||
|
|
||||||
def add_pre_update_tasks(self, *tasks: Callable[[World], None]) -> None:
|
def add_pre_update_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
"""
|
"""
|
||||||
Ajoute des taches qui s'executeront au debut de chaque mise à jour du jeu.
|
Ajoute des taches qui s'executeront au debut de chaque mise à jour du jeu.
|
||||||
|
@ -317,6 +357,17 @@ class Game:
|
||||||
raise RuntimeError("Cannot add a task while the loop is running")
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
self._post_update_tasks.extend(tasks)
|
self._post_update_tasks.extend(tasks)
|
||||||
|
|
||||||
|
def add_pre_render_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
|
"""
|
||||||
|
Ajoute des taches qui s'executeront au début de chaque mise à jour du jeu pour le rendu.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
*tasks: Les taches à ajouter.
|
||||||
|
"""
|
||||||
|
if self._running:
|
||||||
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
|
self._pre_render_tasks.extend(tasks)
|
||||||
|
|
||||||
def add_render_tasks(self, *tasks: Callable[[World], None]) -> None:
|
def add_render_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
"""
|
"""
|
||||||
Ajoute des taches qui s'executeront après chaque mise à jour du jeu pour le rendu.
|
Ajoute des taches qui s'executeront après chaque mise à jour du jeu pour le rendu.
|
||||||
|
@ -328,6 +379,25 @@ class Game:
|
||||||
raise RuntimeError("Cannot add a task while the loop is running")
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
self._render_tasks.extend(tasks)
|
self._render_tasks.extend(tasks)
|
||||||
|
|
||||||
|
def add_post_render_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
|
"""
|
||||||
|
Ajoute des taches qui s'executeront après chaque mise à jour du jeu pour le rendu.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
*tasks: Les taches à ajouter.
|
||||||
|
"""
|
||||||
|
if self._running:
|
||||||
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
|
self._post_render_tasks.extend(tasks)
|
||||||
|
|
||||||
|
def add_pre_shutdown_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
|
"""
|
||||||
|
Ajoute des taches qui s'executeront au début de la fin de la boucle de jeu.
|
||||||
|
"""
|
||||||
|
if self._running:
|
||||||
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
|
self._pre_shutdown_tasks.extend(tasks)
|
||||||
|
|
||||||
def add_shutdown_tasks(self, *tasks: Callable[[World], None]) -> None:
|
def add_shutdown_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
"""
|
"""
|
||||||
Ajoute des taches qui s'executeront à la fin de la boucle de jeu.
|
Ajoute des taches qui s'executeront à la fin de la boucle de jeu.
|
||||||
|
@ -339,6 +409,14 @@ class Game:
|
||||||
raise RuntimeError("Cannot add a task while the loop is running")
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
self._shutdown_tasks.extend(tasks)
|
self._shutdown_tasks.extend(tasks)
|
||||||
|
|
||||||
|
def add_post_shutdown_tasks(self, *tasks: Callable[[World], None]) -> None:
|
||||||
|
"""
|
||||||
|
Ajoute des taches qui s'executeront à la fin de la fin de la boucle de jeu.
|
||||||
|
"""
|
||||||
|
if self._running:
|
||||||
|
raise RuntimeError("Cannot add a task while the loop is running")
|
||||||
|
self._post_shutdown_tasks.extend(tasks)
|
||||||
|
|
||||||
def run(self, world: World = World()) -> World:
|
def run(self, world: World = World()) -> World:
|
||||||
"""
|
"""
|
||||||
Lance la boucle de jeu.
|
Lance la boucle de jeu.
|
||||||
|
@ -351,6 +429,14 @@ class Game:
|
||||||
world.set(self)
|
world.set(self)
|
||||||
world.apply()
|
world.apply()
|
||||||
|
|
||||||
|
# On execute les taches de pré initialisation du monde
|
||||||
|
for task in self._pre_startup_tasks:
|
||||||
|
try:
|
||||||
|
task(world)
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Error during pre-startup task: {e}")
|
||||||
|
world.apply()
|
||||||
|
|
||||||
# On execute les taches d'initialisation du monde
|
# On execute les taches d'initialisation du monde
|
||||||
for task in self._startup_tasks:
|
for task in self._startup_tasks:
|
||||||
try:
|
try:
|
||||||
|
@ -359,6 +445,14 @@ class Game:
|
||||||
error(f"Error during startup task: {e}")
|
error(f"Error during startup task: {e}")
|
||||||
world.apply()
|
world.apply()
|
||||||
|
|
||||||
|
# On execute les taches de post initialisation du monde
|
||||||
|
for task in self._post_startup_tasks:
|
||||||
|
try:
|
||||||
|
task(world)
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Error during post-startup task: {e}")
|
||||||
|
world.apply()
|
||||||
|
|
||||||
while self._running:
|
while self._running:
|
||||||
# On execute les taches de pré mise à jour du monde
|
# On execute les taches de pré mise à jour du monde
|
||||||
for task in self._pre_update_tasks:
|
for task in self._pre_update_tasks:
|
||||||
|
@ -392,6 +486,22 @@ class Game:
|
||||||
error(f"Error during render task: {e}")
|
error(f"Error during render task: {e}")
|
||||||
world.apply()
|
world.apply()
|
||||||
|
|
||||||
|
# On execute les taches de fin de rendu du jeu
|
||||||
|
for task in self._post_render_tasks:
|
||||||
|
try:
|
||||||
|
task(world)
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Error during post-render task: {e}")
|
||||||
|
world.apply()
|
||||||
|
|
||||||
|
# On execute les taches de pré fin de boucle
|
||||||
|
for task in self._pre_shutdown_tasks:
|
||||||
|
try:
|
||||||
|
task(world)
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Error during pre-shutdown task: {e}")
|
||||||
|
world.apply()
|
||||||
|
|
||||||
# On exécute les taches de fin du monde
|
# On exécute les taches de fin du monde
|
||||||
for task in self._shutdown_tasks:
|
for task in self._shutdown_tasks:
|
||||||
try:
|
try:
|
||||||
|
@ -400,6 +510,14 @@ class Game:
|
||||||
error(f"Error during shutdown task: {e}")
|
error(f"Error during shutdown task: {e}")
|
||||||
world.apply()
|
world.apply()
|
||||||
|
|
||||||
|
# On execute les taches de post fin de boucle
|
||||||
|
for task in self._post_shutdown_tasks:
|
||||||
|
try:
|
||||||
|
task(world)
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Error during post-shutdown task: {e}")
|
||||||
|
world.apply()
|
||||||
|
|
||||||
# On retourne le monde
|
# On retourne le monde
|
||||||
return world
|
return world
|
||||||
|
|
||||||
|
|
101
engine/math.py
Normal file
101
engine/math.py
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
"""
|
||||||
|
Définis des classes utiles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from typing import SupportsFloat, Union
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class Vec2:
|
||||||
|
"""
|
||||||
|
Un vecteur 2D
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args: Union[SupportsFloat, "Vec2"]) -> None:
|
||||||
|
if (
|
||||||
|
len(args) == 2
|
||||||
|
and isinstance(args[0], SupportsFloat)
|
||||||
|
and isinstance(args[1], SupportsFloat)
|
||||||
|
):
|
||||||
|
self.x = float(args[0])
|
||||||
|
self.y = float(args[1])
|
||||||
|
elif len(args) == 1:
|
||||||
|
if isinstance(args[0], Vec2):
|
||||||
|
self.x = args[0].x
|
||||||
|
self.y = args[0].y
|
||||||
|
elif isinstance(args[0], SupportsFloat):
|
||||||
|
self.x = float(args[0])
|
||||||
|
self.y = float(args[0])
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid argument")
|
||||||
|
elif len(args) == 0:
|
||||||
|
self.x = 0.0
|
||||||
|
self.y = 0.0
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid number of arguments")
|
||||||
|
|
||||||
|
def __add__(self, other: object) -> "Vec2":
|
||||||
|
if isinstance(other, Vec2):
|
||||||
|
return Vec2(self.x + other.x, self.y + other.y)
|
||||||
|
elif isinstance(other, float):
|
||||||
|
return Vec2(self.x + other, self.y + other)
|
||||||
|
raise ValueError(
|
||||||
|
f"Unsupported operand type(s) for +: 'Vec2' and '{type(other)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __sub__(self, other: object) -> "Vec2":
|
||||||
|
if isinstance(other, Vec2):
|
||||||
|
return Vec2(self.x - other.x, self.y - other.y)
|
||||||
|
elif isinstance(other, float):
|
||||||
|
return Vec2(self.x - other, self.y - other)
|
||||||
|
raise ValueError(
|
||||||
|
f"Unsupported operand type(s) for -: 'Vec2' and '{type(other)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __mul__(self, other: object) -> "Vec2":
|
||||||
|
if isinstance(other, Vec2):
|
||||||
|
return Vec2(self.x * other.x, self.y * other.y)
|
||||||
|
elif isinstance(other, float):
|
||||||
|
return Vec2(self.x * other, self.y * other)
|
||||||
|
raise ValueError(
|
||||||
|
f"Unsupported operand type(s) for *: 'Vec2' and '{type(other)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __truediv__(self, other: object) -> "Vec2":
|
||||||
|
if isinstance(other, Vec2):
|
||||||
|
return Vec2(self.x / other.x, self.y / other.y)
|
||||||
|
elif isinstance(other, float):
|
||||||
|
return Vec2(self.x / other, self.y / other)
|
||||||
|
raise ValueError(
|
||||||
|
f"Unsupported operand type(s) for /: 'Vec2' and '{type(other)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if isinstance(other, Vec2):
|
||||||
|
return self.x == other.x and self.y == other.y
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.x, self.y))
|
||||||
|
|
||||||
|
def __neg__(self) -> "Vec2":
|
||||||
|
return Vec2(-self.x, -self.y)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def length(self) -> float:
|
||||||
|
"""
|
||||||
|
Retourne la longueur du vecteur.
|
||||||
|
"""
|
||||||
|
return math.sqrt(self.x**2 + self.y**2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def normalized(self) -> "Vec2":
|
||||||
|
"""
|
||||||
|
Retourne une version normalisé du vecteur.
|
||||||
|
"""
|
||||||
|
length = self.length
|
||||||
|
return Vec2(self.x / length, self.y / length)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Vec2({self.x}, {self.y})"
|
227
engine/plugins/pygame.py
Normal file
227
engine/plugins/pygame.py
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
"""
|
||||||
|
Définit un plugin qui gère les évenements pygame.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from engine import *
|
||||||
|
from engine.math import Vec2
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
|
||||||
|
class PygamePlugin(Plugin):
|
||||||
|
"""
|
||||||
|
Plugin qui gère les évenements pygame.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_surface_rect() -> tuple[float, float, float, float]:
|
||||||
|
width, height = pygame.display.get_surface().get_size()
|
||||||
|
if width / height < Display.RATIO:
|
||||||
|
target_height = width * (Display.INVERT_RATIO)
|
||||||
|
offset = (height - target_height) / 2
|
||||||
|
rect = (0.0, offset, float(width), target_height)
|
||||||
|
else:
|
||||||
|
target_width = height * (Display.RATIO)
|
||||||
|
offset = (width - target_width) / 2
|
||||||
|
rect = (offset, 0.0, target_width, float(height))
|
||||||
|
return rect
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _initialize(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Initialize pygame et les ressources.
|
||||||
|
"""
|
||||||
|
pygame.init()
|
||||||
|
pygame.display.set_mode((800, 600), pygame.RESIZABLE)
|
||||||
|
|
||||||
|
# Initialisation des ressources
|
||||||
|
world.set(
|
||||||
|
Display(),
|
||||||
|
Keyboard(),
|
||||||
|
Mouse(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_events(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Met a jour les ressources avec les evenements pygame.
|
||||||
|
"""
|
||||||
|
|
||||||
|
keyboard = world[Keyboard]
|
||||||
|
keyboard.pressed.clear()
|
||||||
|
keyboard.released.clear()
|
||||||
|
|
||||||
|
mouse = world[Mouse]
|
||||||
|
mouse.pressed.clear()
|
||||||
|
mouse.released.clear()
|
||||||
|
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
world[Game].stop()
|
||||||
|
elif event.type == pygame.KEYDOWN:
|
||||||
|
key_name = pygame.key.name(event.key)
|
||||||
|
if key_name == "f11":
|
||||||
|
pygame.display.toggle_fullscreen()
|
||||||
|
keyboard.keys.add(key_name)
|
||||||
|
keyboard.pressed.add(key_name)
|
||||||
|
elif event.type == pygame.KEYUP:
|
||||||
|
key_name = pygame.key.name(event.key)
|
||||||
|
if key_name == "f11":
|
||||||
|
continue
|
||||||
|
keyboard.keys.remove(key_name)
|
||||||
|
keyboard.released.add(key_name)
|
||||||
|
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
mouse.buttons.add(event.button)
|
||||||
|
mouse.pressed.add(event.button)
|
||||||
|
elif event.type == pygame.MOUSEBUTTONUP:
|
||||||
|
mouse.buttons.remove(event.button)
|
||||||
|
mouse.released.add(event.button)
|
||||||
|
elif event.type == pygame.MOUSEMOTION:
|
||||||
|
rect = PygamePlugin._find_surface_rect()
|
||||||
|
mouse.position = Vec2(
|
||||||
|
((event.pos[0] - rect[0]) / rect[2]) * Display.WIDTH,
|
||||||
|
((event.pos[1] - rect[1]) / rect[3]) * Display.HEIGHT,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_display(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Met a jour le rendu de l'écran.
|
||||||
|
"""
|
||||||
|
display = world[Display]
|
||||||
|
rect = PygamePlugin._find_surface_rect()
|
||||||
|
pygame.transform.set_smoothscale_backend("MMX")
|
||||||
|
pygame.transform.smoothscale(
|
||||||
|
display._surface,
|
||||||
|
(rect[2], rect[3]),
|
||||||
|
pygame.display.get_surface().subsurface(rect),
|
||||||
|
)
|
||||||
|
pygame.display.flip()
|
||||||
|
display._surface.fill((0, 0, 0))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _terminate(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Ferme pygame.
|
||||||
|
"""
|
||||||
|
pygame.quit()
|
||||||
|
|
||||||
|
def apply(self, game: Game) -> None:
|
||||||
|
"""
|
||||||
|
Applique le plugin a un jeu.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
game: Le jeu auquel appliquer le plugin.
|
||||||
|
"""
|
||||||
|
game.add_pre_startup_tasks(self._initialize)
|
||||||
|
game.add_pre_update_tasks(self._check_events)
|
||||||
|
game.add_post_render_tasks(self._update_display)
|
||||||
|
game.add_post_shutdown_tasks(self._terminate)
|
||||||
|
|
||||||
|
|
||||||
|
class Display:
|
||||||
|
"""
|
||||||
|
Ressource qui represente la fenetre du jeu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
WIDTH = 1080.0
|
||||||
|
HEIGHT = 810.0
|
||||||
|
RATIO = WIDTH / HEIGHT
|
||||||
|
INVERT_RATIO = HEIGHT / WIDTH
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._surface = pygame.Surface((Display.WIDTH, Display.HEIGHT))
|
||||||
|
|
||||||
|
|
||||||
|
class Keyboard:
|
||||||
|
"""
|
||||||
|
Ressource qui représente les entrées utilisateurs sur le clavier à la frame actuelle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.keys: set[str] = set()
|
||||||
|
self.pressed: set[str] = set()
|
||||||
|
self.released: set[str] = set()
|
||||||
|
|
||||||
|
def is_key_pressed(self, key_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Renvoie True si la touche *key_name* a commencé a être appuyée pendant la frame actuelle.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
key_name: Le nom de la touche à tester.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
True si la touche *key_name* a commencé a être appuyée pendant la frame actuelle.
|
||||||
|
"""
|
||||||
|
return key_name in self.pressed
|
||||||
|
|
||||||
|
def is_key(self, key_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Renvoie True si la touche *key_name* est actuellement appuyée.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
key_name: Le nom de la touche à tester.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
True si la touche *key_name* est actuellement appuyée.
|
||||||
|
"""
|
||||||
|
return key_name in self.keys
|
||||||
|
|
||||||
|
def is_key_released(self, key_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Renvoie True si la touche *key_name* a été relachée pendant la frame actuelle.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
key_name: Le nom de la touche à tester.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
True si la touche *key_name* a été relachée pendant la frame actuelle.
|
||||||
|
"""
|
||||||
|
return key_name in self.released
|
||||||
|
|
||||||
|
|
||||||
|
class Mouse:
|
||||||
|
"""
|
||||||
|
Ressource qui représente l'état de la souris à la frame actuelle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.buttons: set[int] = set()
|
||||||
|
self.pressed: set[int] = set()
|
||||||
|
self.released: set[int] = set()
|
||||||
|
self.position: Vec2 = Vec2(0.0, 0.0)
|
||||||
|
|
||||||
|
def is_button_pressed(self, button: int) -> bool:
|
||||||
|
"""
|
||||||
|
Renvoie True si le bouton *button* a commencé a être appuyée pendant la frame actuelle.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
button: Le numéro du bouton à tester.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
True si le bouton *button* a commencé a être appuyée pendant la frame actuelle.
|
||||||
|
"""
|
||||||
|
return button in self.pressed
|
||||||
|
|
||||||
|
def is_button(self, button: int) -> bool:
|
||||||
|
"""
|
||||||
|
Renvoie True si le bouton *button* est actuellement appuyé.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
button: Le numéro du bouton à tester.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
True si le bouton *button* est actuellement appuyé.
|
||||||
|
"""
|
||||||
|
return button in self.buttons
|
||||||
|
|
||||||
|
def is_button_released(self, button: int) -> bool:
|
||||||
|
"""
|
||||||
|
Renvoie True si le bouton *button* a été relaché pendant la frame actuelle.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
button: Le numéro du bouton à tester.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
True si le bouton *button* aLongrightarrow relaché pendant la frame actuelle.
|
||||||
|
"""
|
||||||
|
return button in self.released
|
91
engine/plugins/render.py
Normal file
91
engine/plugins/render.py
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
"""
|
||||||
|
Définis un plugin permettant d'afficher des choses a l'écran.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from engine import *
|
||||||
|
from engine.math import Vec2
|
||||||
|
import pygame, os
|
||||||
|
|
||||||
|
from engine.plugins.pygame import Display
|
||||||
|
|
||||||
|
|
||||||
|
class RenderPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
Plugin permettant d'afficher des choses a l'écran.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _initialize(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Initialize le système de rendu.
|
||||||
|
"""
|
||||||
|
world.set(TextureManager(world[Display]))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _render(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Fais le rendu des sprites.
|
||||||
|
"""
|
||||||
|
display = world[Display]
|
||||||
|
textures = world[TextureManager]
|
||||||
|
|
||||||
|
# Rendu de toutes les objects de rendu
|
||||||
|
entities = sorted(world.query(Order, Position), key=lambda e: e[Order])
|
||||||
|
for entity in entities:
|
||||||
|
# Récupération de la position des entitées
|
||||||
|
position = entity[Position]
|
||||||
|
|
||||||
|
# Affichage de la texture
|
||||||
|
if Texture in entity:
|
||||||
|
display._surface.blit(
|
||||||
|
textures[entity[Texture]], (position.x, position.y)
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply(self, game: Game) -> None:
|
||||||
|
"""
|
||||||
|
Applique le plugin a un jeu.
|
||||||
|
|
||||||
|
Paramarters:
|
||||||
|
game: Le jeu auquel appliquer le plugin.
|
||||||
|
"""
|
||||||
|
game.add_post_startup_tasks(self._initialize)
|
||||||
|
game.add_render_tasks(self._render)
|
||||||
|
|
||||||
|
|
||||||
|
class Position(Vec2):
|
||||||
|
"""
|
||||||
|
Composant qui représente la position d'une entité.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Order(int):
|
||||||
|
"""
|
||||||
|
Composant qui represente l'ordre d'affichage d'un objet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TextureManager:
|
||||||
|
"""
|
||||||
|
Ressource qui contient les textures du jeu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, display: Display) -> None:
|
||||||
|
self._textures: dict[str, pygame.Surface] = {}
|
||||||
|
for file in os.listdir("textures"):
|
||||||
|
self._textures[file] = pygame.image.load(f"textures/{file}").convert(
|
||||||
|
display._surface
|
||||||
|
)
|
||||||
|
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(display._surface)
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> pygame.Surface:
|
||||||
|
return self._textures.get(key, self._error_texture)
|
||||||
|
|
||||||
|
|
||||||
|
class Texture(str):
|
||||||
|
"""
|
||||||
|
Composant qui represente la texture d'un sprite.
|
||||||
|
"""
|
|
@ -27,7 +27,7 @@ class TimePlugin(Plugin):
|
||||||
Paramètres:
|
Paramètres:
|
||||||
game: Le jeu auquel appliquer le plugin.
|
game: Le jeu auquel appliquer le plugin.
|
||||||
"""
|
"""
|
||||||
game.add_startup_tasks(self._initialize_time)
|
game.add_pre_startup_tasks(self._initialize_time)
|
||||||
game.add_pre_update_tasks(self._update_time)
|
game.add_pre_update_tasks(self._update_time)
|
||||||
|
|
||||||
|
|
||||||
|
|
75
main.py
75
main.py
|
@ -4,32 +4,69 @@ Ceci est un exemple de comment l'on peut utiliser le moteur du jeu.
|
||||||
|
|
||||||
|
|
||||||
from engine import *
|
from engine import *
|
||||||
from engine.plugins.timing import TimePlugin, Time
|
from engine.math import Vec2
|
||||||
|
from engine.plugins.render import Order, RenderPlugin, Position, Texture
|
||||||
|
from engine.plugins.timing import Delta, TimePlugin
|
||||||
|
from engine.plugins.pygame import Display, Keyboard, PygamePlugin
|
||||||
|
from random import random
|
||||||
|
|
||||||
|
|
||||||
# Initialisation
|
# Initialisation
|
||||||
game = Game(TimePlugin())
|
game = Game(TimePlugin(), PygamePlugin(), RenderPlugin())
|
||||||
|
|
||||||
# Ajout de tache au démarage (l'ordre d'ajout est important)
|
|
||||||
game.add_startup_tasks(lambda world: print("Hello first"))
|
|
||||||
game.add_startup_tasks(lambda world: print("Hello second"))
|
|
||||||
game.add_startup_tasks(lambda world: print("Hello third"))
|
|
||||||
game.add_startup_tasks(lambda world: print("Hello last"))
|
|
||||||
|
|
||||||
# Ajoute de tache au mise à jour (malgré le world[Game].stop(), la boucle termine les taches suivantes)
|
# On créer une tache pour afficher des sprites
|
||||||
game.add_pre_update_tasks(lambda world: print("Pre Update"))
|
def spawn_sprites(world: World) -> None:
|
||||||
game.add_pre_update_tasks(lambda world: print(world[Time]))
|
"""
|
||||||
game.add_update_tasks(lambda world: world[Game].stop())
|
Ajoute des sprites au monde.
|
||||||
game.add_post_update_tasks(lambda world: print("Post Update"))
|
"""
|
||||||
|
for i in range(100):
|
||||||
|
red = random() < 0.1
|
||||||
|
world.create_entity(
|
||||||
|
Position(random() * Display.WIDTH, random() * Display.HEIGHT),
|
||||||
|
Texture("test2.png") if red else Texture("test.png"),
|
||||||
|
Order(1 if red else 0),
|
||||||
|
)
|
||||||
|
|
||||||
# Ajout de tache au rendu
|
|
||||||
game.add_render_tasks(lambda world: print("Render task 1"))
|
|
||||||
game.add_render_tasks(lambda world: print("Render task 2"))
|
|
||||||
game.add_render_tasks(lambda world: print("Render task 3"))
|
|
||||||
|
|
||||||
# Ajout de tache à la fin
|
# On ajoutant la tache
|
||||||
game.add_shutdown_tasks(lambda world: print("Bye first"))
|
game.add_startup_tasks(spawn_sprites)
|
||||||
game.add_shutdown_tasks(lambda world: print("Bye second"))
|
|
||||||
|
|
||||||
|
def move_sprites(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Change la position des sprites.
|
||||||
|
"""
|
||||||
|
move = Vec2(
|
||||||
|
(-1.0 if world[Keyboard].is_key("q") else 0.0)
|
||||||
|
+ (1.0 if world[Keyboard].is_key("d") else 0.0),
|
||||||
|
(-1.0 if world[Keyboard].is_key("z") else 0.0)
|
||||||
|
+ (1.0 if world[Keyboard].is_key("s") else 0.0),
|
||||||
|
)
|
||||||
|
for entity in world.query(Position):
|
||||||
|
if entity[Order] == 1:
|
||||||
|
continue
|
||||||
|
entity.set(Position(entity[Position] + (move * world[Delta] * 1000.0)))
|
||||||
|
|
||||||
|
|
||||||
|
# On ajoute la tache
|
||||||
|
game.add_update_tasks(move_sprites)
|
||||||
|
|
||||||
|
|
||||||
|
# On créer une tache pour tester si les plugins fonctionnent
|
||||||
|
def salutations(world: World) -> None:
|
||||||
|
"""
|
||||||
|
Affiche "Bonjour" si la touche B est pressé et "Au revoir" si la touche B est relachée.
|
||||||
|
"""
|
||||||
|
if world[Keyboard].is_key_pressed("b"):
|
||||||
|
print("Bonjour")
|
||||||
|
|
||||||
|
if world[Keyboard].is_key_released("b"):
|
||||||
|
print("Au revoir")
|
||||||
|
|
||||||
|
|
||||||
|
# On ajoute la tache de test
|
||||||
|
game.add_update_tasks(salutations)
|
||||||
|
|
||||||
# On lance la boucle
|
# On lance la boucle
|
||||||
game.run()
|
game.run()
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
mypy
|
mypy
|
||||||
pylint
|
pylint
|
||||||
|
pygame
|
BIN
textures/test.png
Normal file
BIN
textures/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
BIN
textures/test2.png
Normal file
BIN
textures/test2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in a new issue