Browse Source

Modifs finales pour la version 1.0.0

master
scayac 1 month ago
parent
commit
d0ce2095f0
  1. 172
      README.md
  2. BIN
      duplogame.png
  3. BIN
      fonts/LEGO.ttf
  4. BIN
      img/board_1train.png
  5. BIN
      img/board_2trains.png
  6. BIN
      img/engrenage.png
  7. BIN
      img/main_screen.png
  8. BIN
      img/train.png
  9. 34
      joystick_mappings.json
  10. 800
      main.py
  11. 102
      tools/debug_joystick.py

172
README.md

@ -1,29 +1,56 @@
# duploGame # duploGame
Jeu Pygame pour piloter 1 ou 2 trains LEGO DUPLO avec clavier PC ou contrôleurs arcade/joysticks. Jeu Pygame pour piloter un ou deux trains LEGO DUPLO avec clavier PC ou contrôleurs arcade/joysticks.
## Vue d'ensemble
`duploGame` est une application de type arcade/borne conçue pour simuler le contrôle d'un train DUPLO en temps réel. Elle prend en charge :
- le choix d'un mode 1 ou 2 joueurs,
- la connexion BLE à un ou plusieurs trains DUPLO,
- la configuration des mappings clavier et joystick,
- la gestion des déplacements, lumières, sons et arrêt d'urgence,
- le rendu d'une interface orientée borne arcade avec tablature visuelle.
Le programme s'appuie sur `pygame` pour l'interface et sur `duplo_controller` pour la communication avec les hubs DUPLO.
## Fonctionnalités ## Fonctionnalités
- splash screen au démarrage - splash screen au démarrage avec sélection 1 train / 2 trains,
- menu de connexion de 1 ou 2 trains - menu de connexion BLE pour 1 ou 2 joueurs,
- écran partagé en 2 zones de contrôle indépendantes - scan automatique des trains DUPLO visibles en Bluetooth,
- support clavier (2 joueurs) + joysticks (1 joystick par joueur) - support clavier et joystick arcade,
- intégration de `duploController` (asynchrone) pour piloter les trains - remapping temps réel via écrans de configuration (A REVOIR, mieux vaut passer directement par les fichiers JSON joystick_mappings.json et keyboard_mappings.json),
- sauvegarde des mappings dans des fichiers JSON,
- logique de vitesse, lumière, son, couleur et arrêt immédiat,
- mode simulation si `duploController` n'est pas installé.
## Architecture du projet
Le code est organisé autour de quelques composants clés :
- `InputManager` : normalise les actionneurs clavier/joystick en événements simples (`up`, `down`, `start_pressed`, etc.).
- `TrainProxy` : encapsule la communication asynchrone avec un train unique.
- `DuploGame` : orchestre les écrans, la logique d'état et le rendu visuel.
- `AsyncLoopThread` : exécute la boucle asyncio dans un thread dédié pour éviter de bloquer la boucle Pygame.
Cette séparation permet d'isoler le traitement du signal physique des commandes de transport BLE et rend le code plus facile à maintenir.
## Dépendances ## Dépendances
- Python 3.10+ - Python 3.10+
- `pygame` - `pygame`
- `duploController` : https://gitea.christophe-scaya.fr/scayac/duploController - `duploController` : https://gitea.christophe-scaya.fr/scayac/duploController
- `bleak` (pour le scan BLE)
## Installation ## Installation
Exemple: Exemple de configuration locale :
```bash ```bash
python -m venv .venv python -m venv .venv
source .venv/bin/activate source .venv/bin/activate
pip install pygame pip install pygame bleak
pip install git+https://gitea.christophe-scaya.fr/scayac/duploController.git pip install git+https://gitea.christophe-scaya.fr/scayac/duploController.git
``` ```
@ -33,35 +60,48 @@ pip install git+https://gitea.christophe-scaya.fr/scayac/duploController.git
python main.py python main.py
``` ```
## Fichiers de configuration générés
Au premier lancement, les écrans de configuration enregistrent automatiquement :
- `joystick_mappings.json` : mapping physique des boutons du joystick pour les joueurs 1 et 2,
- `keyboard_mappings.json` : mapping clavier pour les deux joueurs.
Ces fichiers sont rechargés au démarrage suivant.
## Export Batocera (.pygame) ## Export Batocera (.pygame)
Un script d'export est disponible pour générer un package `.pygame`: Un script d'export est disponible pour générer un package `.pygame` :
```bash ```bash
python tools/export_batocera_pygame.py --name duploGame --entrypoint main.py python tools/export_batocera_pygame.py --name duploGame --entrypoint main.py
``` ```
Le fichier est généré dans `dist/duploGame.pygame`. Le fichier est généré dans `dist/duploGame.pygame`.
Par défaut (`--mode folder`), l'export génère le dossier `dist/duploGame/` au format
style Retrotrivia avec:
- `dist/duploGame/duploGame.pygame` (script lanceur) ### Mode dossier (`--mode folder`)
- les fichiers du jeu et assets à côté
- `vendor/` pour les modules Python embarqués Par défaut, le script génère un dossier `dist/duploGame/` au format style Retrotrivia :
Ce format est généralement le plus compatible avec Batocera. - `dist/duploGame/duploGame.pygame` (script lanceur),
- les fichiers du jeu et des assets à côté,
- `vendor/` pour les modules Python embarqués.
Le mode `single-file` reste disponible, mais moins recommandé: C'est le format habituellement le plus compatible avec Batocera.
### Mode single-file
```bash ```bash
python tools/export_batocera_pygame.py --mode single-file python tools/export_batocera_pygame.py --mode single-file
``` ```
Dans ce mode, l'export génère `dist/duploGame.pygame` et un dossier payload `dist/duploGame/`. Dans ce mode, un seul fichier `dist/duploGame.pygame` est généré, avec un dossier payload `dist/duploGame/`.
### Modules vendus inclus
Par défaut, le script embarque les modules Python `duplo_controller`, `bleak` et `dbus_fast` dans `vendor/`. Par défaut, le script embarque les modules Python `duplo_controller`, `bleak` et `dbus_fast` dans `vendor/`.
Options utiles: Options utiles :
```bash ```bash
python tools/export_batocera_pygame.py \ python tools/export_batocera_pygame.py \
@ -75,83 +115,95 @@ python tools/export_batocera_pygame.py \
--vendor-module dbus_fast --vendor-module dbus_fast
``` ```
Si un module vendor n'est pas présent sur la machine de build: Si un module est absent sur la machine de build :
```bash ```bash
python tools/export_batocera_pygame.py --allow-missing-vendor python tools/export_batocera_pygame.py --allow-missing-vendor
``` ```
Ensuite, copier vers Batocera: ### Mise en place sur Batocera
- mode `folder`: copier le dossier `dist/duploGame/` dans `/userdata/roms/pygame` - mode `folder` : copier le dossier `dist/duploGame/` dans `/userdata/roms/pygame`
- mode `single-file`: copier `dist/duploGame.pygame` **et** `dist/duploGame/` dans `/userdata/roms/pygame` - mode `single-file` : copier `dist/duploGame.pygame` et `dist/duploGame/` dans `/userdata/roms/pygame`
## Contrôles ## Contrôles
### Menu ### Écran d'accueil
- `UP` / `DOWN` (ou joystick) : choisir 1 ou 2 joueurs - `1` / `2` : choisir 1 ou 2 trains,
- `ACTION 1` joueur 1 : connecter/déconnecter train 1 - `UP` / `DOWN` : sélectionner le mode,
- `ACTION 1` joueur 2 : connecter/déconnecter train 2 (mode 2 joueurs) - `START` ou `ACTION 1` : valider et passer au menu,
- `START` joueur 1 : lancer le mode contrôle (si trains requis connectés) - bouton engrenage : ouvrir l'écran de configuration,
- `SELECT` joueur 1 (ou touche `C`) : ouvrir la configuration joystick - `ESC` : quitter.
- `ACTION 2` joueur 1 (ou touche `K`) : ouvrir la configuration clavier
### Menu de connexion
- `UP` / `DOWN` : choisir 1 ou 2 joueurs,
- `LEFT` / `RIGHT` : sélectionner le train dans la liste scanée,
- `ACTION 1` sur chaque joueur : connecter / déconnecter le train sélectionné,
- `START` joueur 1 : lancer le mode contrôle si les trains requis sont connectés,
- `SELECT` joueur 1 ou touche `C` : ouvrir la configuration joystick,
- `ACTION 2` joueur 1 ou touche `K` : ouvrir la configuration clavier.
### Configuration joystick ### Configuration joystick
- `LEFT/RIGHT` : sélectionner joueur 1 ou 2 - `LEFT/RIGHT` : sélectionner le joueur 1 ou 2,
- `UP/DOWN` : sélectionner la fonction à mapper (`UP`, `DOWN`, `LEFT`, `RIGHT`, `A1..A6`, `SELECT`, `START`) - `UP/DOWN` : choisir la fonction à mapper (`UP`, `DOWN`, `LEFT`, `RIGHT`, `A1..A6`, `SELECT`, `START`),
- `ACTION 1` : entrer en mode capture, puis appuyer sur le bouton physique à associer - `ACTION 1` : entrer en mode capture, puis appuyer sur le bouton physique à associer,
- `SELECT` : reset mapping par défaut du joueur sélectionné - `SELECT` : réinitialiser le mapping du joueur courant,
- `START` : retour menu - `START` : revenir au menu précédent.
Les mappings sont sauvegardés automatiquement dans `joystick_mappings.json` à la racine du projet, Les mappings sont sauvegardés automatiquement dans `joystick_mappings.json` et rechargés au prochain lancement.
et rechargés au prochain lancement du jeu.
Important borne arcade: si votre encodeur expose le stick en boutons (et non en axes/hat), > Important pour les bornes arcade : si votre encodeur expose le stick en boutons plutôt qu'en axes/hat, il faut mapper explicitement `UP`, `DOWN`, `LEFT` et `RIGHT` dans l'écran de configuration.
mappez explicitement `UP`, `DOWN`, `LEFT`, `RIGHT` dans cet écran.
### Configuration clavier ### Configuration clavier
- `LEFT/RIGHT` : sélectionner joueur 1 ou 2 - `LEFT/RIGHT` : sélectionner le joueur 1 ou 2,
- `UP/DOWN` : sélectionner la fonction à mapper (`UP`, `DOWN`, `LEFT`, `RIGHT`, `A1..A6`, `SELECT`, `START`) - `UP/DOWN` : choisir la fonction à mapper (`UP`, `DOWN`, `LEFT`, `RIGHT`, `A1..A6`, `SELECT`, `START`),
- `ACTION 1` : entrer en mode capture, puis appuyer sur la touche clavier à associer - `ACTION 1` : entrer en mode capture, puis appuyer sur la touche clavier à associer,
- `SELECT` : reset mapping clavier par défaut du joueur sélectionné - `SELECT` : réinitialiser le mapping du joueur courant,
- `START` : retour menu - `START` : revenir au menu précédent.
Les mappings clavier sont sauvegardés automatiquement dans `keyboard_mappings.json` à la racine du projet, Les mappings clavier sont sauvegardés dans `keyboard_mappings.json`.
et rechargés au prochain lancement du jeu.
### En jeu ### En jeu
- `UP` / `DOWN` : accélérer / freiner - `UP` / `DOWN` : accélérer / freiner,
- `LEFT` / `RIGHT` : changer la couleur de la lumière - `LEFT` / `RIGHT` : changer la couleur de la lumière,
- `ACTION 1..5` : sons du train - `ACTION 1..5` : jouer les sons du train,
- `ACTION 6` ou `SELECT` : arrêt immédiat - `ACTION 6` ou `SELECT` : arrêt immédiat,
- `START` : retour menu - `START` : ouvrir le menu de connexion si le train n'est pas connecté,
- `ESC` : quitter - `ESC` : quitter.
### Mappings clavier ### Mappings clavier par défaut
- Joueur 1: - Joueur 1 :
- Directions: `W/Z`, `S`, `A/Q`, `D` - Directions: `W/Z`, `S`, `A/Q`, `D`
- Actions 1..6: `1 2 3 4 5 6` - Actions 1..6: `1 2 3 4 5 6`
- Start: `Entrée` - Start: `Entrée`
- Select: `Backspace` - Select: `Backspace`
- Joueur 2: - Joueur 2 :
- Directions: `↑ ↓ ← →` - Directions: `↑ ↓ ← →`
- Actions 1..6: `U I O J K L` - Actions 1..6: `U I O J K L`
- Start: `Shift droit` - Start: `Shift droit`
- Select: `Ctrl droit` - Select: `Ctrl droit`
### Mappings joystick arcade (par défaut) ### Mappings joystick arcade par défaut
- Directions: D-Pad (hat) ou axes gauche X/Y - Directions : D-Pad (hat) ou axes gauche X/Y,
- Actions 1..6: boutons `0..5` - Actions 1..6 : boutons `0..5`,
- Select: bouton `6` - Select : bouton `6`,
- Start: bouton `7` - Start : bouton `7`.
Ces mappings sont modifiables à chaud via l'écran de configuration joystick. Ces mappings sont modifiables à chaud via l'écran de configuration joystick.
> Si `duploController` n'est pas installé, le jeu démarre en mode simulation (sans contrôle BLE réel). ## Notes de fonctionnement
- Si `duploController` n'est pas installé, l'application démarre en mode simulation et continue à fonctionner sans contrôle BLE réel.
- Le scan BLE est fait en arrière-plan dans un thread dédié pour éviter de bloquer le rendu.
- Les controllers identiques peuvent partager des identifiants SDL similaires ; le code évite ces collisions en associant les événements du joystick par index de périphérique au lieu de se fier uniquement à l'instance ID.
- Les capturations de touches ou boutons pendant la configuration sont désactivées temporairement pour éviter les boucles de détection entre le mapping en cours et les états actifs du joueur.
> Pour le développement local, le plus simple est de lancer le jeu avec le fichier `main.py` et de tester la configuration des entrées depuis le menu d'options.

BIN
duplogame.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
fonts/LEGO.ttf

Binary file not shown.

BIN
img/board_1train.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
img/board_2trains.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
img/engrenage.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
img/main_screen.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
img/train.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

34
joystick_mappings.json

@ -0,0 +1,34 @@
{
"player1": {
"actions": [
0,
1,
2,
3,
4,
5
],
"select": 6,
"start": 7,
"up": -1,
"down": -1,
"left": -1,
"right": -1
},
"player2": {
"actions": [
0,
1,
2,
3,
4,
5
],
"select": 6,
"start": 7,
"up": -1,
"down": -1,
"left": -1,
"right": -1
}
}

800
main.py

File diff suppressed because it is too large Load Diff

102
tools/debug_joystick.py

@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
Outil de diagnostic pour analyser les entrées d'un joystick.
Affiche en temps réel les axes, boutons et chapeau détectés.
"""
import os
import platform
import sys
import pygame
import time
print("=== Infos système ===")
print(f" Python : {sys.version}")
print(f" OS : {platform.system()} {platform.release()}")
# Même comportement que main.py : désactive HIDAPI pour éviter la renumérotation des boutons
os.environ.setdefault("SDL_JOYSTICK_HIDAPI", "0")
print(f" SDL_JOYSTICK_HIDAPI : {os.environ.get('SDL_JOYSTICK_HIDAPI', '(non défini)')}")
def main():
pygame.init()
pygame.joystick.init()
sdl_ver = pygame.get_sdl_version()
print(f" pygame : {pygame.__version__}")
print(f" SDL : {sdl_ver[0]}.{sdl_ver[1]}.{sdl_ver[2]}")
print()
# Petite fenêtre pour capturer les événements
screen = pygame.display.set_mode((400, 300))
pygame.display.set_caption("Joystick Debug")
clock = pygame.time.Clock()
joysticks = []
for idx in range(pygame.joystick.get_count()):
joy = pygame.joystick.Joystick(idx)
joy.init()
joysticks.append(joy)
print(f"\n=== Joystick {idx}: {joy.get_name()} ===")
try:
print(f" GUID : {joy.get_guid()}")
except AttributeError:
pass
print(f" Boutons: {joy.get_numbuttons()}")
print(f" Axes : {joy.get_numaxes()}")
print(f" Chapeaux: {joy.get_numhats()}")
print(f" Trackballs: {joy.get_numballs()}")
if not joysticks:
print("❌ Aucun joystick détecté!")
pygame.quit()
return
print("\n📋 Monitoring en direct (appuyez sur ESC ou fermez la fenêtre pour quitter)...\n")
axis_values = {}
button_states = {}
hat_states = {}
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
elif event.type == pygame.JOYBUTTONDOWN:
print(f"🔴 [J{event.joy}] BOUTON DOWN: {event.button}")
elif event.type == pygame.JOYBUTTONUP:
print(f"⚪ [J{event.joy}] BOUTON UP: {event.button}")
elif event.type == pygame.JOYAXISMOTION:
# Afficher seulement si le mouvement est significatif
if abs(event.value) > 0.1:
print(f" [J{event.joy}] AXE {event.axis}: {event.value:.3f}")
elif event.type == pygame.JOYHATMOTION:
print(f"🧭 [J{event.joy}] CHAPEAU: {event.value}")
# État en direct des axes
for idx, joy in enumerate(joysticks):
print("\r", end="")
status = f"[J{idx}] "
for axis_idx in range(min(4, joy.get_numaxes())): # Afficher les 4 premiers axes
val = joy.get_axis(axis_idx)
status += f"AX{axis_idx}:{val:6.3f} "
# État du chapeau
try:
hat_x, hat_y = joy.get_hat(0)
status += f"| CHAPEAU:({hat_x},{hat_y})"
except:
pass
print(status, end="", flush=True)
clock.tick(30) # 30 FPS
pygame.quit()
print("\n✅ Terminé.")
if __name__ == "__main__":
main()
Loading…
Cancel
Save