Projet de jeu · USC Games · That Game Jam · Équipe de 5

Light as Wind

Un jeu de plateforme à défilement horizontal en pixel art, onirique, qui traverse l’océan vers un ciel lointain. J’étais designer et ingénieur dans l’équipe : j’ai assuré le level design de trois des dix niveaux et développé plusieurs des systèmes de physique, de shaders et de destruction sur lesquels repose le jeu.

Gameplay du niveau 03 : se cacher de la baudroie. Toutes les créatures du jeu sont animées de façon procédurale, sans aucun squelette d’animation.

Mon rôle

Designer · Ingénieur

J’ai assuré le level design des niveaux 04, 06 et 10 — scène, parcours et mécaniques. Sur l’ensemble du jeu, j’ai conçu des mécaniques d’interaction, développé la logique de gameplay et les effets, et fait de la QA et de la correction de bugs.

Côté ingénierie, je me suis concentré sur le ressenti des choses en mouvement : le papier emporté par le vent, la nage de l’espadon, le balancement du crabe aux lianes et les objets qui se brisent. J’ai aussi écrit le shader des « murs d’air » du jeu.

Le projet suit une règle stricte : la logique de chaque système est écrite en C# pur, sans aucun type du moteur, afin de pouvoir quitter Unity plus tard, et chaque modification est vérifiée par des données — sondes, logs et bots de parcours — avant les captures d’écran.

Moteur
Unity 6 / C# / HLSL
Équipe
5 étudiants, USC Games
Plateforme
Windows (itch.io)
Statut
Partie I disponible

Les fondations du projet

Rendu & animation procédurale

Light as Wind repose sur deux fondations techniques : un pixel art qui tient face à une caméra 3D en perspective qui se déplace, et des créatures animées entièrement par le code, sans un seul squelette d’animation. Les systèmes que j’ai développés reposent sur ces fondations.

01

Pipeline de rendu sur mesure

Le jeu a quitté URP par étapes pour son propre pipeline, construit sur un frame graph indépendant du moteur, écrit en C# pur, dont Unity n’est qu’un backend parmi d’autres. Chaque étape a été vérifiée texel par texel par rapport à des images de référence. Les variantes de shaders sont passées de 603 à 36, et une frame complète coûte environ 0,4–0,5 ms de GPU.

02

Du pixel art en perspective

Chaque objet s’aligne de façon rigide sur la grille de texels basse résolution, et son décalage sous-pixel résiduel est stocké dans le G-buffer puis résolu à la résolution de l’écran, si bien que les silhouettes ne fourmillent jamais pendant les mouvements de caméra. La lumière indirecte douce provient de radiance cascades en 2,5D avec des sondes ancrées dans le monde, pour environ 0,3 ms.

03

Animation procédurale

Aucun squelette d’animation nulle part : des colonnes vertébrales de poissons animées par des ondes progressives, des chaînes XPBD et Verlet, des méduses simulées en compute shaders, et des bancs de Boids de plus de 5 000 poissons dessinés en un seul appel.

Level design

Les niveaux que j’ai conçus

Systèmes

Physique, shaders & destruction

05 points forts

01 · Simulation

Du papier emporté par le vent

Chaque feuille se soulève en pivotant autour de son bord sous le vent, qui sert de charnière, se décolle jusqu’à 34°, puis se détache. En l’air, sa vitesse tend vers un vent cible par une poursuite exponentielle, et un balancement de feuille morte (±0,3 m, période de 2,1 s) couple l’inclinaison à l’oscillation pour que la feuille s’incline dans chaque virage.

Avant l’arrivée du vent, les coins frémissent sur un ressort sous-amorti. Une contrainte de mur maintient les affiches devant le mur et les fait glisser le long de sa surface, de sorte qu’elles ne le traversent jamais.

Par rafale
15–17 feuilles, toutes hors écran en 0,22–2,33 s
Traversée du mur
0,7–2,6 m → 0
Le modèle est écrit en C# pur (System.Numerics, sans type du moteur) ; Unity ne fait que l’afficher.

02 · Shader · Bruit de Worley

Le mur d’air : la peau d’une mer de rêve

Les murs invisibles devaient sembler faire partie du rêve, et non passer pour un bug. Le mur affiche une toile de Voronoi / Worley qui dérive : les lignes sont tracées selon la distance réelle aux bords des cellules, si bien qu’elles restent larges d’un pixel et continues sous n’importe quel angle, chaque bord prenant l’une des couleurs du plancton, avec quelques paillettes dorées scintillantes.

Un impact envoie une paire d’anneaux qui se propagent en écartant la toile, et des bulles s’échappent du point d’impact. Le rideau n’apparaît qu’à l’approche (1,8 m, pleinement visible à 0,95 m). La luminosité utilise des paliers nets sans tramage, en HLSL portable s’appuyant sur les headers propres au projet.

03 · Game feel

La nage de l’espadon

L’espadon nage avec une onde vertébrale de type thon, un recul pondéré par la masse qui fait osciller la tête à l’opposé de la queue comme chez un vrai poisson, et une fréquence de battement de queue fixée par le nombre de Strouhal.

J’ai travaillé sur sa maniabilité : aucun freinage avant de toucher réellement un mur (distance mesurée depuis la pointe du rostre le long de sa trajectoire), une glisse le long des plafonds et des pentes herbeuses sans perte de vitesse, un plongeon entre les sauts et des virages haut/bas plus vifs.

04 · Simulation

Le crabe qui se balance aux lianes

Le crabe est un pendule ; la liane ne fait que suivre sa pince. La prise est évaluée dans le plan de l’image, car la distance 3D pure ratait souvent dans un jeu en 2,5D. Le joueur ne peut donner de l’élan qu’en phase avec le balancement, et un plafond d’énergie (72° max) empêche les longues lianes de projeter le crabe hors du cadre.

Chaque liane est une corde de Verlet à 18 nœuds, avec 8 passes de contraintes, une traînée dans l’eau et un lent balancement ; les nœuds situés sous la pince sont repoussés hors de la carapace du crabe pour qu’elle ne la traverse jamais.

Frame moyenne, niveaux 7/8/9
6,4/7,7/8,5 → 1,7/2,6/2,7 ms
Frames au-delà de 16,7 ms
89/165/166 → 4/0/0

05 · Destruction

Découper le niveau en morceaux

Aucun élément du jeu n’est un mesh importé : détruire, c’est donc découper de la géométrie générée. Pour le tunnel qui s’effondre au niveau 06, la voûte de la grotte se brise d’elle-même : les sommets sont soudés et regroupés en rochers fermés grâce à un union-find, les rochers allongés sont découpés par des plans aléatoires (Sutherland–Hodgman, avec interpolation de tous les attributs), et chaque coupe est refermée par un éventail de triangles pour que chaque fragment reste un solide fermé.

La découpe s’exécute dans une coroutine avec un budget de 4 ms par frame, et démarre alors que le joueur est encore à 60 m : 610 rochers deviennent 169 fragments en 140–220 frames. Les objets plus petits se brisent en morceaux prédécoupés dont les jointures sont partagées entre voisins, et les éclats se passent entièrement du moteur physique — environ 0,013 ms par frame.

Effondrement du tunnel
610 rochers → 169 fragments
Pire frame lors d’un bris d’objet
14,6 → 3,3 ms