Moteur de jeu 2D sur PyOpenGL (GLFW + Pillow)
Project description
Tiavina.engine.gl
Moteur de jeu 2D pixel art, construit sur PyOpenGL + GLFW + Pillow.
Fonctionne avec un écran virtuel (résolution logique) mis à l'échelle entière vers la fenêtre d'affichage.
Installation
pip install Tiavina.engine.gl
from Engine import engine as e
e.init(200, 200, "Mon Jeu", fps=60, display_scale=4)
def update():
if e.btn(e.KEY_ESCAPE):
e.quit()
def draw():
e.cls((30, 30, 40))
e.rect(10, 10, 20, 20, (255, 0, 0))
e.run(update, draw)
Table des matières
Architecture
Engine/
├── __init__.py ← réexporte l'API
├── engine.py ← le moteur (tout-en-un)
└── fonts/
└── PressStart2P.ttf ← police pixel embarquée
Le moteur expose une API globale : on importe from Engine import engine as e et on appelle e.init(), e.cls(), e.btn(), etc.
Sous le capot :
App— classe principale qui gère la fenêtre GLFW, l'horloge FPS, levirtual_screen(PIL Image logique), et la boucle d'événements.Graphics— dessin sur l'écran virtuel avec support de caméra, clipping, et alpha. OpenGL est utilisé pour le blit final sur l'écran physique.Input— gestion clavier et souris via GLFW.Resources— chargement d'images.Audio— stub (non implémenté, retourneNone).
Initialisation
e.init(width, height, title="T.engine", fps=30, display_scale=1, pixel_art=True)
Crée la fenêtre et initialise tous les sous-systèmes.
| Paramètre | Type | Description |
|---|---|---|
width |
int |
Largeur logique de l'écran virtuel (ex: 200) |
height |
int |
Hauteur logique (ex: 200) |
title |
str |
Titre de la fenêtre |
fps |
int |
Images par seconde cibles |
display_scale |
int |
Facteur d'échelle entier (ex: 5 → fenêtre 1000×1000 pour 200×200) |
pixel_art |
bool |
True = arrondit caméra et coordonnées, ligne 1px logique → display_scale px physiques (défaut: True) |
Quand pixel_art=True, la caméra est arrondie aux entiers, les positions de dessin sont arrondies aux entiers, et glLineWidth(display_scale) est utilisé pour les lignes de 1px logique. Mettre à False pour un rendu haute résolution sans ces contraintes.
L'écran virtuel fait width × height pixels. Chaque frame, il est scaled par display_scale et affiché dans la fenêtre système.
e.init(200, 200, "Mon Jeu", 60, 5)
e.run(update, draw)
Lance la boucle de jeu. update() est appelée à chaque frame pour la logique, draw() pour le rendu.
def update():
pass
def draw():
e.cls((0, 0, 0))
e.run(update, draw)
e.quit()
Ferme Pygame et termine le processus.
e.width(), e.height()
Retourne la largeur/hauteur logique définies dans init().
e.frame_count()
Retourne le nombre de frames écoulées depuis init().
Graphismes — Graphics
Toutes les fonctions de dessin sont affectées par la caméra (sauf si camera() est réinitialisée).
Les coordonnées sont en pixels logiques.
Primitives de dessin
| Fonction | Description |
|---|---|
cls(color) |
Remplit tout l'écran virtuel avec une couleur |
pset(x, y, color) |
Dessine un pixel |
pget(x, y) → Color |
Lit la couleur d'un pixel |
line(x1, y1, x2, y2, color) |
Ligne |
rect(x, y, w, h, color) |
Rectangle plein |
rectb(x, y, w, h, color) |
Rectangle vide (1px de bord) |
circ(x, y, r, color) |
Cercle plein |
circb(x, y, r, color) |
Cercle vide (1px de bord) |
elli(x, y, w, h, color) |
Ellipse pleine |
ellib(x, y, w, h, color) |
Ellipse vide (1px de bord) |
tri(x1, y1, x2, y2, x3, y3, color) |
Triangle plein |
trib(x1, y1, x2, y2, x3, y3, color) |
Triangle vide (1px de bord) |
Couleurs avec alpha (RGBA)
Toutes les primitives sauf text() et pget() acceptent des couleurs RGBA (4 valeurs).
L'alpha est géré via un blending additif sur une surface temporaire.
# Rectangle semi-transparent
e.rect(10, 10, 50, 50, (255, 0, 0, 128))
Blit d'image
blt(x, y, img, u, v, w, h, colkey=None, rotate=0)
| Paramètre | Type | Description |
|---|---|---|
x, y |
int |
Position de destination (coin supérieur gauche) |
img |
PIL.Image ou _Img |
Image source |
u, v |
int |
Coordonnées source (coin supérieur gauche dans l'image) |
w, h |
int |
Dimensions de la région source |
colkey |
Color ou None |
Couleur de transparence (optionnelle) |
rotate |
float |
Rotation en degrés horaire (pivot au centre) |
e.blt(100, 100, img, 0, 0, 16, 16) # blit simple
e.blt(100, 100, img, 0, 0, 16, 16, rotate=45) # rotation
Texte
text(x, y, s, color, font=None)
| Paramètre | Type | Description |
|---|---|---|
x, y |
int |
Position |
s |
str |
Texte à afficher |
color |
tuple |
Couleur RGB (pas de support alpha) |
font |
FontWrap ou None |
Police personnalisée (None = police pixel par défaut) |
La police par défaut est PressStart2P.ttf taille 6px, rendu sans anti-aliasing pour un aspect pixel art.
e.text(5, 5, "Score: 42", (255, 255, 255))
# Avec une police différente
big = e.default_font(16)
e.text(5, 20, "Titre", (255, 200, 0), font=big)
Caméra
camera(dx, dy) # Applique un décalage à tout le dessin suivant
camera() # Réinitialise le décalage à (0, 0)
La caméra est cumulative : tous les appels camera() s'ajoutent.
Pour un système plus avancé, voir la classe Camera.
e.camera(-player.x + 100, -player.y + 100) # suit le joueur
Clipping
clip(x, y, w, h) # Active un rectangle de clipping
clip() # Désactive le clipping
Le clipping est appliqué après la caméra.
Caméra intelligente — Camera
cam = e.Camera(target, screen_width, screen_height,
mouse_influence=0.2, mouse_limit=10)
Classe réutilisable avec suivi de cible, offset souris, tremblement et flash.
Paramètres du constructeur
| Paramètre | Défaut | Description |
|---|---|---|
target |
— | Objet suivi (doit avoir .x, .y) |
screen_width |
— | Largeur de l'écran logique |
screen_height |
— | Hauteur de l'écran logique |
mouse_influence |
0.2 |
Sensibilité du regard vers la souris (0.0–1.0) |
mouse_limit |
10 |
Décalage maximal dû à la souris (en pixels logiques) |
Méthodes
| Méthode | Description |
|---|---|
update() |
Calcule la nouvelle position de la caméra (target + souris + shake) |
apply() |
Applique le décalage au moteur graphique (e.camera(cam.cam_x, cam.cam_y)) |
shake(duration, intensity) |
Déclenche un tremblement d'écran |
flash(color, alpha, duration) |
Déclenche un flash d'écran |
Attributs
| Attribut | Description |
|---|---|
cam_x, cam_y |
Position calculée de la caméra (en pixels logiques) |
flash_color |
Couleur du flash actif |
flash_alpha |
Opacité courante du flash |
shake_intensity |
Intensité du tremblement actuel |
Le flash doit être dessiné manuellement dans draw() :
if cam.flash_alpha > 0:
e.camera()
e.rect(0, 0, e.width(), e.height(), (*cam.flash_color, cam.flash_alpha))
Entrées — Input
Clavier
btn(key) # → True si la touche est maintenue
btnp(key) # → True le frame où la touche est pressée (pas de répétition)
btnr(key) # → True le frame où la touche est relâchée
if e.btn(e.KEY_SPACE):
player.jump()
if e.btnp(e.KEY_E):
player.interact()
Souris
mouse_x(), mouse_y() # → Position logique (divisée par display_scale)
mouse_btn(button) # → True si le bouton est maintenu
mouse_btnp(button) # → True le frame du clic
mouse_btnr(button) # → True le frame du relâchement
mouse(visible=True) # → Affiche ou cache le curseur système
Boutons : MOUSE_BUTTON_LEFT (1), MOUSE_BUTTON_MIDDLE (2), MOUSE_BUTTON_RIGHT (3)
if e.mouse_btnp(e.MOUSE_BUTTON_LEFT):
print(f"Clic à ({e.mouse_x()}, {e.mouse_y()})")
Joystick
Non implémenté dans Tiavina.engine.gl (stub).
Ressources — Resources
resources.image(bank, path, colkey=None) # → PIL.Image ou None
resources.sound(bank, path) # → DecodedSoundFile ou None
resources.music(bank, path) # → None (enregistre le chemin)
resources.load(filename) # → lève NotImplementedError
Images
image() charge une image dans resources.images[bank].
bank: index numérique (int)path: chemin relatif ou absolucolkey: couleur de transparence optionnelle (ex:(255, 0, 255))- Retourne une
PIL.Image(convertie en RGBA)
e.resources.image(0, "./sprites/player.png")
e.resources.image(1, "./sprites/tiles.png", (255, 0, 255))
# Utilisation
img = e.resources.images[1]
Sons
sound() charge et décode un fichier audio (WAV, MP3, FLAC, OGG) via miniaudio et le stocke dans resources.sounds[bank] sous forme de DecodedSoundFile (PCM en mémoire).
e.resources.sound(0, "./sfx/jump.wav")
e.resources.sound(1, "./music/ambient.ogg")
Musique
music() enregistre le chemin d'un fichier dans resources.musics[bank] pour lecture différée via audio.playm().
e.resources.music(0, "./music/boss.ogg")
Audio — Audio
Moteur audio basé sur miniaudio. 8 canaux (0–7) pour les effets sonores, 1 flux pour la musique.
Initialisation
L'Audio est créé automatiquement par e.init(). Les dictionnaires sounds et musics sont partagés avec Resources — les sons chargés via e.resources.sound() sont directement accessibles.
Méthodes
| Méthode | Description |
|---|---|
play(ch, s, loop=False) |
Joue le son s (clé) sur le canal ch (0–7). loop=True → boucle infinie |
playm(m, loop=False) |
Joue la musique m (clé) en streaming. Arrête la musique précédente |
stop(ch=None) |
Stoppe le canal ch, ou tous les canaux + musique si ch=None |
play_pos(ch) → bool |
True si le canal ch est en train de jouer |
Détails techniques
- Les sons sont décodés en mémoire au chargement (
miniaudio.decode_file()) → lecture instantanée sans latence disque. - Chaque canal son crée son propre
miniaudio.PlaybackDeviceavec un générateur qui yield les trames PCM. - La musique est lue en streaming (
miniaudio.stream_file()) pour éviter de charger des fichiers longs en mémoire. - Formats supportés : WAV, MP3, FLAC, OGG.
- Si
miniaudion'est pas installé, l'audio est désactivé silencieusement (_HAS_AUDIO = False, toutes les méthodes sont des no-op).
Exemples
# Charger des sons
e.resources.sound(0, "./sfx/jump.wav")
e.resources.sound(1, "./sfx/hit.wav")
e.resources.music(0, "./music/theme.ogg")
# Jouer
e.audio.play(0, 0) # jump sur canal 0
e.audio.play(1, 1) # hit sur canal 1
e.audio.playm(0) # musique de fond
e.audio.play(2, 0, loop=True) # jump en boucle sur canal 2
# Vérifier
if e.audio.play_pos(0):
print("Canal 0 joue encore")
# Arrêter
e.audio.stop(0) # stop canal 0 seulement
e.audio.stop() # stop tout (canaux + musique)
Police pixel — default_font
default_font(size=6, antialias=False) → FontWrap
Charge et met en cache la police PressStart2P.ttf via Pillow (ImageFont.truetype).
L'anti-aliasing est désactivé par défaut pour un rendu pixel art.
La police est stockée dans un cache global _pixel_font_cache : chaque taille n'est chargée qu'une fois.
petite = e.default_font(6)
moyenne = e.default_font(8, antialias=True) # avec lissage
grande = e.default_font(16)
Constantes
Touches clavier
KEY_A–KEY_Z, KEY_0–KEY_9,
KEY_SPACE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT,
KEY_ESCAPE, KEY_RETURN, KEY_TAB, KEY_BACKSPACE,
KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_RCTRL,
KEY_LALT, KEY_RALT
Toutes sont des constantes GLFW (glfw.KEY_*) réexportées par le module.
Boutons souris
| Constante | Valeur |
|---|---|
MOUSE_BUTTON_LEFT |
1 |
MOUSE_BUTTON_MIDDLE |
2 |
MOUSE_BUTTON_RIGHT |
3 |
Fonctions globales
Le module engine expose des fonctions globales qui délèguent aux sous-systèmes internes :
| Fonction | Délègue à |
|---|---|
cls(c) |
graphics.cls(c) |
pset(x,y,c) |
graphics.pset(x,y,c) |
pget(x,y) |
graphics.pget(x,y) |
line(x1,y1,x2,y2,c) |
graphics.line(...) |
rect(x,y,w,h,c) |
graphics.rect(...) |
rectb(x,y,w,h,c) |
graphics.rectb(...) |
circ(x,y,r,c) |
graphics.circ(...) |
circb(x,y,r,c) |
graphics.circb(...) |
elli(x,y,w,h,c) |
graphics.elli(...) |
ellib(x,y,w,h,c) |
graphics.ellib(...) |
tri(x1,y1,x2,y2,x3,y3,c) |
graphics.tri(...) |
trib(x1,y1,x2,y2,x3,y3,c) |
graphics.trib(...) |
text(x,y,s,c,font) |
graphics.text(...) |
blt(x,y,img,u,v,w,h,...) |
graphics.blt(...) |
bltm(x,y,tm,u,v,w,h,...) |
graphics.bltm(...) |
clip(x,y,w,h) |
graphics.clip(...) |
camera(x,y) |
graphics.camera(...) |
pal(c1,c2) |
graphics.pal(...) (stub) |
dither(a) |
graphics.dither(...) (stub) |
mouse_btn(b) |
input.mouse_btn(b) |
mouse_btnp(b) |
input.mouse_btnp(b) |
mouse_btnr(b) |
input.mouse_btnr(b) |
btn(k) |
input.btn(k) |
btnp(k) |
input.btnp(k) |
btnr(k) |
input.btnr(k) |
Exemple complet
from Engine import engine as e
from random import randint
# ── Initialisation ─────────────────────────────────────
e.init(200, 200, "Aventurier", fps=60, display_scale=5,
pixel_art=True)
# ── Ressources ─────────────────────────────────────────
e.resources.image(0, "./sprites/player.png")
e.resources.image(1, "./sprites/tiles.png")
# ── Joueur ─────────────────────────────────────────────
class Player:
def __init__(self):
self.x = 100
self.y = 100
self.w = 8
self.h = 8
self.vie = 100
player = Player()
# ── Caméra ─────────────────────────────────────────────
cam = e.Camera(player, e.width(), e.height(),
mouse_influence=0.3, mouse_limit=12)
# ── Boucle ─────────────────────────────────────────────
def update():
# Déplacements
if e.btn(e.KEY_LEFT): player.x -= 1
if e.btn(e.KEY_RIGHT): player.x += 1
if e.btn(e.KEY_UP): player.y -= 1
if e.btn(e.KEY_DOWN): player.y += 1
if e.btn(e.KEY_ESCAPE): e.quit()
# Clic pour shake
if e.mouse_btnp(e.MOUSE_BUTTON_LEFT):
cam.shake(10, 6)
cam.flash((255, 255, 200), 60, 4)
cam.update()
cam.apply()
def draw():
e.cls((30, 30, 40))
# Sol (en coordonnées monde)
for i in range(-2, 30):
for j in range(-2, 20):
e.rect(i * 8, j * 8, 8, 8, (40, 45, 55))
# Joueur
e.rect(player.x, player.y, player.w, player.h,
(100, 200, 255) if player.vie > 0 else (255, 50, 50))
# Flash overlay (dessiné avant la réinitialisation caméra)
if cam.flash_alpha > 0:
e.camera()
e.rect(0, 0, e.width(), e.height(),
(*cam.flash_color, cam.flash_alpha))
# ── HUD (coordonnées écran) ──
e.camera() # ← réinitialise la caméra
e.text(4, 4, f"Vie: {player.vie}", (255, 255, 255))
e.text(4, 12, f"Pos: {player.x},{player.y}", (200, 200, 200))
# Curseur
e.circb(e.mouse_x(), e.mouse_y(), 3, (255, 255, 255, 80))
e.run(update, draw)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tiavina_engine_gl-0.3.0.tar.gz.
File metadata
- Download URL: tiavina_engine_gl-0.3.0.tar.gz
- Upload date:
- Size: 50.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd5364bd2019babc0b34e4afc0be6e66ad10f8944a4f435c154a4b72d3cf7425
|
|
| MD5 |
0c6783a885e7e82c4f6190a5c6024c1f
|
|
| BLAKE2b-256 |
d6a921427394212f6b5ebe54ee86005f71deeedfaceaf1de43d65084cd6dae6d
|
File details
Details for the file tiavina_engine_gl-0.3.0-py3-none-any.whl.
File metadata
- Download URL: tiavina_engine_gl-0.3.0-py3-none-any.whl
- Upload date:
- Size: 50.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aac67b2c3fcd09a102c76506493cf4b23c0ff12f3d1329db9985ddc4b5d1a019
|
|
| MD5 |
b100b0d1f2cfc2e6c8c66f141442a41a
|
|
| BLAKE2b-256 |
775d503fb15835665678cf565e93f2bf524e6b4f2b4cf91bb649dfcd1f1eab76
|