Rendu graphique¶
Le rendu est piloté par Renderer3D (gui/src/renderer/Renderer3D.cpp), assisté de
IsoCamera (caméra), AnimatedSprite (animations) et PlayerMotion (interpolation
des déplacements). Tout se déroule dans une boucle unique, mono-thread.
La boucle principale¶
Chaque frame, main exécute la même séquence (gui/src/core/main.cpp:67) :
flowchart LR
A[net.poll] --> B[parse lignes]
B --> C[gs.update dt<br/>décrémente timers]
C --> D[motionTracker.update<br/>interpolation]
D --> E[renderer.update<br/>entrées caméra]
E --> F[renderer.draw<br/>scène 3D + HUD]
F --> A
net.poll()— lit la socket non bloquante ; si le serveur ferme la connexion, on quitte la boucle (gui/src/core/main.cpp:68).- Parsing — tant qu'il reste des lignes complètes, on les passe au
MessageParser, qui mute leGameState(gui/src/core/main.cpp:70). gs.update(dt)— décrémente tous les timers d'animation (broadcast, eject, eat, drop, fork, spawn, incantation, mort) et expire les joueurs morts (gui/src/core/GameState.hpp:48).motionTracker.update(dt)— fait avancer l'interpolation de position et d'orientation de chaque joueur (gui/src/core/main.cpp:75).renderer.update(dt, gs)— lit le clavier pour la caméra (gui/src/renderer/Renderer3D.cpp:105).renderer.draw(gs)— dessine la scène 3D puis le HUD ; sinonWaiting for server data...tant que la carte n'est pas reçue (gui/src/core/main.cpp:81).
Le fond est gris foncé {30, 30, 35} (gui/src/core/main.cpp:79) ; un compteur de
FPS est affiché en haut à droite, et la bannière Winner: <team> apparaît en fin de
partie.
La scène 3D¶
Caméra (IsoCamera)¶
Malgré son nom, la caméra est une Camera3D en perspective (fovy 45°,
CAMERA_PERSPECTIVE, gui/src/renderer/IsoCamera.cpp:12) qui orbite autour du
centre de la carte. Sa position est calculée à partir d'un rayon, d'une hauteur et
d'un angle (gui/src/renderer/IsoCamera.cpp:33) :
_camera.position = {
target.x + std::cos(_angle) * _radius,
_height,
target.z + std::sin(_angle) * _radius,
};
Au premier affichage, fitCameraToMap ajuste rayon et hauteur à la taille de la
carte (gui/src/renderer/Renderer3D.cpp:80). Les contrôles clavier
(gui/src/renderer/IsoCamera.cpp:20) :
| Touche | Action |
|---|---|
| Q / E | Orbite (angle ±) |
| W / S | Hauteur de la caméra |
| A / D | Rayon (zoom avant/arrière) |
Le rayon et la hauteur sont bornés (kMinRadius, kMinHeight). Une aide est
affichée en bas à gauche : W/S height | A/D radius | Q/E orbit
(gui/src/renderer/Renderer3D.cpp:327).
Table de touches du README
Le README du GUI liste des touches partiellement incohérentes avec le code.
La référence fiable est l'aide à l'écran ci-dessus, qui correspond au code.
Tuiles¶
Chaque tuile est un cube plat ({1, 0.2, 1}) dessiné avec un dégradé en damier :
la teinte dépend de (x + y) % 4 (gui/src/renderer/Renderer3D.cpp:111). Une tuile
contenant des ressources est légèrement éclaircie. Le cube est cerclé de fil de
fer blanc et de deux lignes d'angle.
Les ressources et pierres sont des petites sphères colorées, une par type
présent sur la tuile (affichée seulement si la quantité est > 0), disposées à des
décalages fixes (gui/src/renderer/Renderer3D.cpp:155). La table de couleurs suit
l'ordre de l'énumération ResourceType
(gui/src/renderer/Renderer3D.cpp:140) :
| Index | Ressource | Couleur |
|---|---|---|
| 0 | Food (nourriture) | orange |
| 1 | Linemate | argent |
| 2 | Deraumere | violet |
| 3 | Sibur | vert |
| 4 | Mendiane | bleu |
| 5 | Phiras | rouge |
| 6 | Thystame | turquoise |
Joueurs (billboards animés)¶
Chaque joueur est un sprite billboard (DrawBillboardRec,
gui/src/renderer/Renderer3D.cpp:234), toujours orienté face à la caméra. Sa
position et son angle proviennent de l'interpolation PlayerMotion
(renderX/renderY/renderAngle).
Orientation gauche/droite par symétrie. Plutôt que quatre sprites par direction,
le code retourne horizontalement la texture (largeur source négative) quand
sin(angle) < 0 (gui/src/renderer/Renderer3D.cpp:224), ce qui suffit à donner
l'impression de regarder à gauche ou à droite.
Chaîne de priorité d'animation. L'animation jouée est choisie par une cascade de
else if (gui/src/renderer/Renderer3D.cpp:176), de la plus prioritaire à la moins
prioritaire :
spawn → dying (fall_crouch) → fin d'incantation → incantation (boucle)
→ broadcast (scream) → eject (push) → eat → drop (sit_anim)
→ fork (give birth) → walk (si en mouvement)
→ iddle (< 3 s d'inactivité) → iddle_sit (après 3 s)
Chaque animation a son propre nombre d'images par seconde
(gui/src/renderer/Renderer3D.hpp:57). Le niveau du joueur n'est pas dessiné sur
le sprite 3D ; il n'apparaîtrait que sur la carte d'information 2D — qui est figée
(voir plus bas).
Recoloration d'équipe (shader GLSL)¶
Les sprites sont peints en vert dans les assets ; un shader les recolore selon
l'équipe au moment du dessin. Si le shader s'est chargé (_recolorShader.id > 0),
le renderer active BeginShaderMode et transmet la couleur de l'équipe via
l'uniforme teamColor (gui/src/renderer/Renderer3D.cpp:229).
Le shader (gui/assets/shaders/recolor.frag, GLSL 330) procède par teinte :
- convertit le pixel RGB → HSL ;
- détecte les pixels « verts » : saturation
> 0.2et teinte à moins de0.15du vert (120° = 0.333), distance calculée en tenant compte du bouclage 0/1 (gui/assets/shaders/recolor.frag:73) ; - pour ces pixels seulement, remplace la teinte par celle de la couleur d'équipe, puis reconvertit en RGB. Les pixels non verts restent intacts.
Les couleurs d'équipe viennent d'une palette fixe de 8 entrées, attribuée dans
l'ordre d'apparition (colorForTeam, gui/src/renderer/Renderer3D.cpp:87) ; au-delà
de 8 équipes, la palette se répète.
Si le shader ne se charge pas
Sans pilote GLSL 330, LoadShader échoue et _recolorShader.id reste à 0 : les
sprites restent verts (pas de recoloration). Voir
Installation de Raylib.
Œufs¶
Les œufs sont des billboards animés (21 images, kEggFps = 4,
gui/src/renderer/Renderer3D.cpp:245), à hauteur réduite (tileSize × 0.8). Un œuf
dont le showDelay est encore positif est masqué (affichage différé jusqu'à
l'animation de ponte du parent, gui/src/renderer/Renderer3D.cpp:307).
Tri en profondeur des sprites transparents¶
Comme les billboards sont transparents, leur ordre de dessin compte. Le renderer
(gui/src/renderer/Renderer3D.cpp:279) :
- vide le lot de géométrie opaque (
rlDrawRenderBatchActive) puis désactive l'écriture de profondeur (rlDisableDepthMask) et active le blending alpha ; - trie les joueurs de l'arrière vers l'avant par distance au carré à la caméra ;
- les dessine dans cet ordre, puis vide à nouveau le lot avant de restaurer l'écriture de profondeur.
Sans cette gestion, des sprites proches masqueraient à tort des sprites éloignés.
Interpolation des déplacements (PlayerMotion)¶
Le serveur n'envoie que des positions entières ; le rendu, lui, est fluide grâce à
PlayerMotion (gui/src/player/PlayerMotion.cpp). À chaque frame, la position
courante avance vers la cible à kMoveSpeed = 4 tuiles/s, et l'angle vers sa cible à
kTurnSpeed = 12 rad/s (gui/src/player/PlayerMotion.hpp:35).
Le point clé est le chemin le plus court sur le tore. La carte de Zappy est
toroïdale : un joueur peut « sortir » d'un bord et réapparaître à l'opposé. Lors d'un
retarget, le code calcule le plus petit déplacement signé en tenant compte du
bouclage (shortestDelta, gui/src/player/PlayerMotion.cpp:33), de sorte que le
sprite traverse le bord plutôt que de revenir en arrière à travers toute la carte. La
même logique s'applique à l'angle (bouclage sur 2π).
Disposition des assets¶
loadAssets (gui/src/renderer/Renderer3D.cpp:38) charge précisément 13 dossiers
d'animation plus le shader, la police et la carte joueur. Chaque
AnimatedSprite::load(dossier, maxFrames) lit 1.png, 2.png… jusqu'à maxFrames,
ignore les fichiers manquants, et boucle l'index modulo le nombre d'images
(gui/src/renderer/AnimatedSprite.cpp:8).
| Dossier | Images | Usage |
|---|---|---|
walk |
30 | marche |
iddle |
120 | repos (< 3 s) |
iddle_sit |
120 | assis (> 3 s) |
scream? |
73 | broadcast |
push |
46 | éjection |
fall_crouch |
35 | mort |
eat |
125 | ramassage de nourriture |
sit_anim |
28 | dépôt |
give birth |
194 | fork (ponte) |
spawn |
16 | apparition |
egg |
21 | œuf |
incantation_start |
87 | incantation (boucle) |
incantation_end |
145 | fin d'incantation |
À cela s'ajoutent : shaders/recolor.frag (uniforme teamColor), font.ttf
(LoadFontEx taille 96, HUD) et le dossier playerCard/
(gui/src/renderer/Renderer3D.cpp:54). Beaucoup d'autres dossiers présents sur le
disque (ex. run, jump, Walk green cat…) ne sont pas chargés.
Points incomplets¶
Carte d'information joueur (PlayerCard) figée
Le panneau 2D PlayerCard est dessiné mais non fonctionnel. Le champ
_focusedPlayerId vaut toujours -1 et n'est jamais positionné
(gui/src/renderer/Renderer3D.hpp:53), donc la branche « joueur sélectionné »
ne s'exécute jamais. Le dessin actif passe systématiquement une PlayerData
vide avec le nom codé en dur « Salut »
(gui/src/renderer/Renderer3D.cpp:325, gui/src/renderer/PlayerCard.cpp:20).
Voir Limitations connues.
Commandes sst et mct non traitées
Le GUI n'a aucun handler pour sst (changement de fréquence côté serveur).
La commande mct est explicitement un no-op côté GUI (le serveur la
développe en une série de bct par tuile). Détails dans
Protocole GUI ↔ Serveur et
Limitations connues.
Broadcasts et incantations : pas de surcouche visuelle
Un broadcast ne déclenche que l'animation scream, sans bulle de texte ; une
incantation joue incantation_start/incantation_end sur le sprite, sans
surlignage de tuile.