6 astuces de shaders Godot simples pour donner un look pro à votre jeu 2D
Flash blanc à l'impact, contours de sprites, effets de dissolution et plus encore. Six techniques de shaders que vous pouvez ajouter à votre jeu 2D en un après-midi.
Votre gameplay est solide. Vos sprites sont réussis. Mais quelque chose semble plat. Les attaques manquent de punch. L’environnement paraît statique. Les ennemis disparaissent simplement quand ils meurent.
La solution, ce sont presque toujours les shaders. Pas les shaders complexes bourrés de maths. Des astuces simples de fragment shaders qui prennent un après-midi à implémenter et rendent instantanément votre jeu plus poli.
Voici six effets utilisés par des jeux comme Dead Cells, Hollow Knight et Celeste que vous pouvez ajouter à votre propre projet dès aujourd’hui.
1. Flash blanc à l’impact
C’est l’effet visuel le plus percutant que vous puissiez ajouter. Quand un ennemi prend des dégâts, chaque pixel du sprite devient blanc pendant 1 à 2 frames. C’est rapide, c’est punchy, et ça donne au joueur un retour immédiat confirmant que son attaque a touché.
Comment ça marche : Le fragment shader mélange la couleur originale du sprite avec du blanc pur en fonction d’un uniform float. Quand le float vaut 1.0, chaque pixel devient blanc. Quand il vaut 0.0, le sprite s’affiche normalement. Votre code de jeu le passe à 1.0 lors de l’impact et le ramène à 0.0 en quelques frames.
// Godot (canvas_item shader)
shader_type canvas_item;
uniform float flash_amount : hint_range(0.0, 1.0) = 0.0;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
COLOR = vec4(mix(tex.rgb, vec3(1.0), flash_amount), tex.a);
}
Dead Cells utilise cette technique à chaque coup. Le flash blanc, combiné au hit stop (geler le jeu pendant 2 à 3 frames), crée cette sensation de combat crunchy qui fait sa signature. Hollow Knight fait la même chose mais ajoute une brève animation de recul pour renforcer l’impact.
Astuce : La durée du flash compte. Une frame, c’est trop subtil. Cinq frames, c’est trop mou. Deux à trois frames, c’est le sweet spot pour la plupart des jeux d’action. Pour aller plus loin, consultez notre guide de l’équilibrage du combat pour une vue d’ensemble sur comment rendre les coups percutants.
2. Contour de sprite
Les contours font ressortir les personnages sur des arrière-plans chargés. On les utilise pour les états de survol, les indicateurs de dégâts, la sélection et la lisibilité en général.
Comment ça marche : Le shader échantillonne la texture avec de petits décalages dans les quatre directions cardinales (haut, bas, gauche, droite). Si un pixel voisin est transparent alors que le pixel actuel l’est aussi, et qu’au moins un voisin est opaque, on dessine la couleur du contour à cet endroit. Le résultat est une bordure colorée autour du sprite.
// Godot (canvas_item shader)
shader_type canvas_item;
uniform vec4 outline_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float outline_width : hint_range(0.0, 10.0) = 1.0;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
vec2 size = TEXTURE_PIXEL_SIZE * outline_width;
float outline = texture(TEXTURE, UV + vec2(size.x, 0)).a;
outline += texture(TEXTURE, UV + vec2(-size.x, 0)).a;
outline += texture(TEXTURE, UV + vec2(0, size.y)).a;
outline += texture(TEXTURE, UV + vec2(0, -size.y)).a;
outline = min(outline, 1.0);
vec4 color = mix(vec4(outline_color.rgb, outline), tex, tex.a);
COLOR = color;
}
Hollow Knight utilise un contour sombre subtil sur le sprite du Chevalier pour le séparer des arrière-plans détaillés, parfois sombres. Certains types d’ennemis reçoivent un contour lumineux coloré quand ils sont sur le point d’attaquer, offrant au joueur un indice visuel sans casser le style artistique.
Astuce : Pour les jeux en pixel art, utilisez outline_width = 1.0 et restez sur des tailles de pixels entiers. Pour de l’art haute résolution, vous pouvez aller plus large. L’échantillonnage diagonal (ajouter quatre vérifications supplémentaires aux coins) donne des contours plus lisses au prix de quatre lectures de texture en plus.
3. Effet de dissolution
Au lieu que les ennemis disparaissent d’un coup, dissolvez-les. Une texture de bruit pilote l’effet, grignotant le sprite pixel par pixel jusqu’à ce qu’il ne reste plus rien. C’est spectaculaire et ça ne nécessite qu’une texture de bruit et un uniform de seuil.
Comment ça marche : Échantillonnez une texture de bruit aux coordonnées UV du sprite. Comparez la valeur de bruit à un seuil uniform que vous animez de 0.0 à 1.0. Là où la valeur de bruit tombe sous le seuil, éliminez le pixel. Pour plus de polish, ajoutez un bord lumineux à la limite de dissolution.
// Godot (canvas_item shader)
shader_type canvas_item;
uniform sampler2D noise_tex;
uniform float threshold : hint_range(0.0, 1.0) = 0.0;
uniform vec4 edge_color : source_color = vec4(1.0, 0.5, 0.0, 1.0);
uniform float edge_width : hint_range(0.0, 0.1) = 0.05;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
float noise = texture(noise_tex, UV).r;
if (noise < threshold) {
discard;
}
float edge = smoothstep(threshold, threshold + edge_width, noise);
COLOR = mix(edge_color, tex, edge);
COLOR.a = tex.a;
}
On appelle parfois cet effet le « Hello World » des shaders car il est simple à implémenter mais visuellement impressionnant. Animez le threshold de 0.0 à 1.0 en une demi-seconde et vous obtenez une animation de mort satisfaisante qui ne coûte presque rien en assets graphiques.
Astuce : Utilisez différentes textures de bruit pour différents rendus. Un bruit de Perlin donne des dissolutions organiques, comme des flammes. Un bruit cellulaire/Voronoï donne un effet de cristal qui se brise. Un simple dégradé de bas en haut donne un effet « brûle en partant des pieds ».
4. Palette Swap / Remplacement de couleurs
Besoin du même ennemi en trois variantes de couleur ? D’un état de dégâts qui passe le sprite au rouge ? D’un power-up qui teinte le joueur en doré ? Le palette swap gère tout cela sans créer de nouvelles feuilles de sprites.
Comment ça marche : Il y a deux approches courantes. La plus simple est la teinte de couleur : multipliez la couleur du sprite par une valeur de couleur uniforme. Ça fonctionne pour les flashs de dégâts et les états temporaires. L’approche plus puissante utilise une texture de lookup (une rampe de couleur 1D) où la valeur de gris du sprite est associée à une couleur spécifique dans la rampe.
// Approche simple par teinte (Godot)
shader_type canvas_item;
uniform vec4 tint_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float tint_amount : hint_range(0.0, 1.0) = 0.0;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
COLOR = vec4(mix(tex.rgb, tex.rgb * tint_color.rgb, tint_amount), tex.a);
}
Dead Cells pousse ce concept plus loin. Le jeu exporte des frames d’animation depuis des modèles 3D avec leurs normal maps, puis applique un toon shader pour rendre le volume sur ce qui ressemble à de l’art 2D dessiné à la main. Différents niveaux d’ennemis reçoivent des palettes de couleurs différentes via des shaders plutôt que des sprites redessinés, ce qui a permis à la petite équipe de Motion Twin de peupler le jeu avec des dizaines de variantes d’ennemis de manière efficace.
Astuce : L’approche par texture de lookup est puissante pour le pixel art. Dessinez vos sprites avec une palette de gris limitée (4 à 8 nuances), puis créez plusieurs textures de rampe de couleur. Changer la texture de rampe re-skine instantanément le personnage entier. C’est ainsi que les jeux NES et SNES classiques géraient les palette swaps, et ça fonctionne toujours aussi bien aujourd’hui.
5. Reflets d’eau 2D
Une surface d’eau réfléchissante transforme une scène plate en quelque chose qui paraît vivant. L’effet semble complexe mais est étonnamment simple en 2D.
Comment ça marche : Retournez la texture d’écran (ou la portion concernée) verticalement, puis appliquez une distorsion UV à l’aide d’une texture de bruit ou de fonctions sinus pour simuler des ondulations. Teintez l’image réfléchie en bleu ou vert, réduisez son opacité, et affichez-la sous la ligne d’eau.
// Approche conceptuelle (Godot)
shader_type canvas_item;
uniform float wave_speed = 2.0;
uniform float wave_strength = 0.01;
uniform vec4 water_tint : source_color = vec4(0.2, 0.4, 0.8, 0.5);
void fragment() {
vec2 uv = SCREEN_UV;
uv.y = 1.0 - uv.y; // retourner verticalement
uv.x += sin(uv.y * 40.0 + TIME * wave_speed) * wave_strength;
vec4 reflection = texture(SCREEN_TEXTURE, uv);
COLOR = mix(reflection, water_tint, 0.4);
}
Note : Dans Godot 4, SCREEN_TEXTURE nécessite un uniform sampler2D avec un hint hint_screen_texture au lieu du built-in. Le concept reste le même.
Celeste utilise des effets d’eau subtils dans ses niveaux de cavernes de glace. Les reflets ne sont pas photoréalistes. Ils sont simples, légèrement déformés et teintés pour correspondre à l’ambiance de chaque zone. Cette retenue fait partie de ce qui rend l’effet si efficace dans un jeu en pixel art.
6. Aberration chromatique
Séparez les canaux rouge, vert et bleu de l’image de quelques pixels et vous obtenez cet effet de « lentille cassée » qui crie l’intensité. Utilisez-le sur les transitions d’écran, les entrées de boss, ou quand le joueur subit un coup critique.
Comment ça marche : Échantillonnez la texture d’écran trois fois avec de légers décalages UV. Prenez le canal rouge d’un échantillon, le vert du centre, et le bleu du troisième. L’amplitude du décalage contrôle l’intensité.
// Godot (canvas_item shader, appliqué à un ColorRect plein écran)
shader_type canvas_item;
uniform float aberration_amount : hint_range(0.0, 0.02) = 0.005;
void fragment() {
float r = texture(TEXTURE, UV + vec2(aberration_amount, 0)).r;
float g = texture(TEXTURE, UV).g;
float b = texture(TEXTURE, UV - vec2(aberration_amount, 0)).b;
float a = texture(TEXTURE, UV).a;
COLOR = vec4(r, g, b, a);
}
Astuce : Un peu suffit. Gardez le décalage petit (1 à 3 pixels maximum) pour un malaise subtil, ou poussez-le brièvement à l’impact pour un effet dramatique. Animez-le dans le temps avec une onde sinusoïdale pour une vibration « quelque chose ne va pas ». Combinez-le avec du screen shake pour un impact maximum.
Par où commencer
Ces six effets couvrent l’essentiel de ce qu’il faut pour un jeu 2D poli. L’idée clé, c’est que les shaders servent le ressenti, pas la prouesse technique. Un shader de flash blanc en deux lignes fait plus pour votre jeu qu’un générateur procédural de nébuleuse de cent lignes.
Si vous débutez dans le développement de jeux, notre guide pour coder votre premier jeu couvre la mise en place de l’engine et la structure de projet. Pour choisir entre les moteurs, consultez notre comparatif Godot vs Unity vs Unreal.
Ressources pour aller plus loin
- Godot Shaders (godotshaders.com) dispose d’une bibliothèque communautaire de shaders 2D et 3D prêts à l’emploi
- The Book of Shaders (thebookofshaders.com) est la meilleure ressource gratuite pour comprendre les maths des shaders en partant de zéro
- Unity Shader Graph dans la documentation couvre la création de shaders par nœuds pour ceux qui préfèrent les workflows visuels
- The Godot Shaders Bible de Fabrizio Espindola est un guide payant complet pour le développement de shaders sous Godot
Commencez par le flash blanc. Ajoutez-le à votre personnage joueur, testez-le, et ressentez la différence. Puis parcourez la liste. Chacun prend un après-midi au maximum, et ensemble ils transformeront le ressenti de votre jeu.
Écrit par
Florian HuetDev iOS le jour, dev de jeux indé la nuit. J'essaie de donner vie à GameDō Studio.
Je fais des jeux et je parle de ceux auxquels je n'arrive pas à décrocher.