Le premier jeu du Foxvoid Engine : Tappy Plane
Publié le 21 août 2026
Tappy Plane : Validation de la cross-compilation (WASM & Android)
Pour éprouver l'architecture du Foxvoid Engine et m'assurer que le pont entre le C++ et Python était viable sur différentes plateformes, il me fallait un projet simple et concret. C'est ainsi qu'est né Tappy Plane, un clone de Flappy Bird utilisant les excellents assets libres de droits de Kenney.nl.
L'objectif principal n'était pas d'innover sur le gameplay, mais de valider le pipeline de cross-compilation. Je voulais m'assurer que le moteur pouvait s'exporter vers WebAssembly (WASM) pour les navigateurs et vers Android pour le mobile, le tout en conservant l'exécution native de mes scripts Python de gameplay.
Logique de jeu avec l'API Python
Le fonctionnement de Tappy Plane est trivial : une gravité constante tire l'avion vers le bas, et une impulsion vers le haut est appliquée lors d'un appui (ou d'un "tap" sur l'écran tactile). L'API Python du Foxvoid Engine rend cette implémentation extrêmement concise.
Voici le script complet contrôlant le joueur :
from foxvoid import *
from typing import Optional
class PlaneController(Component):
__categories__ = {
"Movement Settings": ["jump_force", "rotation_multiplier", "max_up_angle", "max_down_angle"],
"Death Management": ["is_dead", "game_over_prefab"]
}
def __init__(self):
super().__init__()
self.jump_force = -350
self.rotation_multiplier = 0.15
self.max_up_angle = -30.0
self.max_down_angle = 45.0
self.is_dead = False
self.game_over_prefab = "assets/prefabs/GameOverPanel.prefab"
self._transform: Optional[Transform2d] = None
self._rigidbody: Optional[RigidBody2d] = None
self._animator: Optional[Animator2d] = None
def start(self):
self._transform = self.game_object.get_component(Transform2d)
self._rigidbody = self.game_object.get_component(RigidBody2d)
self._animator = self.game_object.get_component(Animator2d)
def update(self, delta_time: float):
if not self._rigidbody or not self._animator:
return
if self.is_dead:
self._rigidbody.velocity.y = 0
return
if Input.is_action_pressed("takeoff"):
self._rigidbody.velocity.y = self.jump_force
audio_manager: Optional[GameObject] = Scene.find_object_by_name("AudioManager")
if audio_manager:
audio_source = audio_manager.get_component(AudioSource)
if audio_source:
audio_source.play_sfx("jump")
if self._rigidbody.velocity.y < 0:
self._animator.play("takeoff")
else:
self._animator.play("fall")
target_rotation = self._rigidbody.velocity.y * self.rotation_multiplier
if target_rotation < self.max_up_angle:
target_rotation = self.max_up_angle
elif target_rotation > self.max_down_angle:
target_rotation = self.max_down_angle
self._transform.rotation = target_rotation
def on_collision(self, collision: Collision2D):
if collision.other and collision.other.name in ["SpikeTop", "SpikeDown", "Ground"] and not self.is_dead:
audio_manager: Optional[GameObject] = Scene.find_object_by_name("AudioManager")
if audio_manager:
audio_source = audio_manager.get_component(AudioSource)
if audio_source:
audio_source.play_sfx("game_over_collision")
self.is_dead = True
Globals.set_bool("game_over", True)
self._animator.play("fall")
self._rigidbody.gravity_scale = 0.0
if self._transform:
self._transform.rotation = self.max_down_angle
camera_obj = Scene.find_object_by_name("Camera")
if camera_obj:
camera = camera_obj.get_component(Camera2d)
if camera:
camera.shake(15.0, 0.3)
GameObject.instantiate(self.game_over_prefab)
Le défi WebAssembly (WASM)
Porter le Foxvoid Engine sur le web impliquait de compiler à la fois le code C++ (via Emscripten) et l'interpréteur Python. Puisque Raylib supporte nativement le web, le rendu graphique, la boucle principale et l'audio se sont exportés de manière relativement transparente.
Le véritable défi technique résidait dans le binding pybind11. Il a fallu s'assurer que l'interpréteur Python intégré (compilé en WASM) puisse lire le système de fichiers virtuel d'Emscripten pour charger le script plane_controller.py et les assets de Kenney. Après avoir correctement configuré le système de Virtual File System (VFS) d'Emscripten au moment du build CMake, le jeu tourne à 60 FPS constants dans un navigateur, prouvant la viabilité du moteur pour des démos web. Exportation vers Android
La seconde étape était le portage sur Android en utilisant le NDK (Native Development Kit).
Ici, le travail a consisté à :
- Unification des Inputs : Adapter le InputManager du moteur C++ pour que les événements tactiles de l'écran Android soient traduits en actions génériques (l'action "flap" appelée dans le script Python s'active désormais aussi bien avec la barre espace qu'avec un toucher sur l'écran).
- Gestion des Assets : Packager correctement les dossiers assets/ et scripts/ dans l'APK. Le moteur C++ utilise l'API native d'Android (AAssetManager) pour extraire ces fichiers à la volée et les fournir à l'interpréteur Python et à Raylib au lancement.
Conclusion
Le développement de Tappy Plane a parfaitement rempli son rôle. Le Foxvoid Engine est désormais capable d'exporter un jeu 2D piloté par des scripts Python sur Windows, Linux, Android et sur le Web.
C'est une étape cruciale de franchie. La prochaine consistera à continuer d'éprouver ces systèmes pour mes futurs projets, notamment en préparant une architecture client-serveur et un rendu adapté pour mon RPG 3D low-poly, Project Elyria.