
Sommaire7
Le screen shake est la modification la plus rapide pour donner de la vie à un jeu. Sans lui, les balles font l'effet de pichenettes, les explosions ressemblent à des pétards, et les morts à un écran de pause. Avec lui, chaque impact a du poids. Voici comment implémenter la méthode trauma dans Godot 4 — l'approche la plus flexible, celle que l'on retrouve sous le capot de nombreux jeux indés.

Pourquoi l'approche naïve ne marche pas
Le premier screen shake que l'on écrit ressemble à ceci : à l'impact, on choisit un offset aléatoire, on l'applique quelques frames, terminé. Le résultat est un clignotement saccadé qui ressemble à un bug graphique plutôt qu'à un impact. Deux problèmes :
- Des valeurs aléatoires frame par frame créent du jitter, pas du mouvement. La caméra se téléporte au lieu de balancer.
- Une amplitude fixe donne la même sensation à chaque coup. Un poing et une grenade reçoivent le même coup d'épaule.
La méthode trauma résout les deux.
Pourquoi ça marche
Le modèle trauma, popularisé dans la communauté indé via le talk de Jan Willem Nijman (Vlambeer) en 2013 intitulé "The Art of Screenshake" aux INDIGO Classes (YouTube, 2013), repose sur trois idées :
- Accumulation. Le trauma est une valeur de 0 à 1 qui s'additionne. Trois tirs consécutifs font monter le shake ; il ne se réinitialise pas à chaque impact.
- Mise au carré. Le déplacement réel est
trauma²(outrauma³). À 0,3 de trauma, la caméra bouge à peine (0,09 du maximum) ; à 1,0 elle atteint toute sa force. Les petits chocs restent discrets ; les gros sont violents. - Bruit lissé. Au lieu d'un
randf()à chaque frame, la caméra échantillonne une textureFastNoiseLitele long d'un axe en mouvement. Les frames consécutives sont corrélées — la caméra balance, elle ne tressaille pas.
Le résultat s'adapte naturellement : une balle ajoute 0,2 de trauma, une explosion 0,7, une bombe plein écran 1,0. Chaque événement mérite son propre drame.
La recette
Tableau des paramètres
| Paramètre | Valeur de départ | Plage utile | Ce que ça change |
|---|---|---|---|
decay | 0.8 | 0,5 – 2,5 | Trauma perdu par seconde ; plus élevé = fondu plus rapide |
max_offset | Vector2(80, 60) | 30 – 150 px | Déplacement maximal en pixels à trauma complet |
max_roll | 0.08 rad (~4,5°) | 0,0 – 0,15 | Rotation à trauma complet ; à garder bas pour éviter la nausée |
trauma_power | 2 | 2 – 3 | Exposant ; 3 rend les petits chocs quasi invisibles |
noise.frequency | 0.5 | 0,3 – 1,5 | Plus bas = oscillation plus lente et molle ; plus haut = énergie nerveuse |
Quelle valeur de trauma par événement
| Événement | Valeur pour add_trauma() |
|---|---|
| Pression d'un bouton UI | 0,05 |
| Impact léger / balle | 0,15 – 0,25 |
| Impact lourd / mêlée | 0,3 – 0,4 |
| Explosion proche | 0,5 – 0,7 |
| Grosse explosion / mort | 0,8 – 1,0 |
Le code
Attachez ce script à votre nœud Camera2D. Il a besoin d'une ressource FastNoiseLite (créée au démarrage) et de rien d'autre — aucun nœud supplémentaire nécessaire.
# TraumaCamera2D.gd
# À attacher à un nœud Camera2D.
# Appelez add_trauma(valeur) depuis n'importe quel endroit à l'impact.
extends Camera2D
## Vitesse de décroissance du trauma par seconde (0 = jamais, 2.5 = très rapide).
@export var decay: float = 0.8
## Déplacement maximal en pixels à trauma complet.
@export var max_offset: Vector2 = Vector2(80.0, 60.0)
## Rotation maximale à trauma complet (~4,5 degrés). À garder sous 0,15.
@export var max_roll: float = 0.08
## Exposant appliqué au trauma avant le déplacement. 2 = quadratique, 3 = cubique.
@export_range(2, 3, 1) var trauma_power: int = 2
# Valeur de trauma actuelle, de 0.0 (calme) à 1.0 (shake complet).
var trauma: float = 0.0
# La position de bruit avance à chaque frame pour obtenir une oscillation continue.
var _noise_t: float = 0.0
@onready var _noise: FastNoiseLite = _make_noise()
func _make_noise() -> FastNoiseLite:
var n := FastNoiseLite.new()
n.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
n.seed = randi()
n.frequency = 0.5
return n
## À appeler à chaque impact. La valeur est comprise entre 0.0 et 1.0.
## Les valeurs s'accumulent : deux appels à 0.3 donnent 0.6 de trauma (plafonné à 1.0).
func add_trauma(amount: float) -> void:
trauma = minf(trauma + amount, 1.0)
func _process(delta: float) -> void:
if trauma <= 0.0:
return
# Décroissance linéaire du trauma dans le temps.
trauma = maxf(trauma - decay * delta, 0.0)
# Mise à la puissance : les petits traumas restent discrets.
var t: float = pow(trauma, trauma_power)
# Avancement de la position de bruit (50 est une bonne cadence).
_noise_t += delta * 50.0
# Échantillonnage de trois canaux de bruit indépendants pour x, y et la rotation.
# Les décalages de seed assurent que les canaux ne sont pas corrélés.
rotation = max_roll * t * _noise.get_noise_2d(0.0, _noise_t)
offset.x = max_offset.x * t * _noise.get_noise_2d(1000.0, _noise_t)
offset.y = max_offset.y * t * _noise.get_noise_2d(2000.0, _noise_t)
# Réinitialisation quand le trauma est épuisé.
if trauma <= 0.0:
offset = Vector2.ZERO
rotation = 0.0
Où le placer
- Sélectionnez votre nœud
Camera2Ddans l'arbre de scène. - Cliquez sur Attacher un script et collez le code ci-dessus (ou sauvegardez-le sous
TraumaCamera2D.gd). - C'est tout — pas d'autoload, pas de singleton.
Déclencher le shake depuis d'autres scripts
# Option A : référence directe (la plus simple)
@onready var camera: TraumaCamera2D = $Camera2D
func _on_enemy_hit() -> void:
camera.add_trauma(0.25)
# Option B : via un groupe (utile quand la caméra est dans une scène différente)
# Dans _ready() : add_to_group("trauma_camera")
# Depuis n'importe où :
func _on_explosion() -> void:
for cam in get_tree().get_nodes_in_group("trauma_camera"):
cam.add_trauma(0.7)

Conseils de réglage et erreurs courantes
Commencez plus petit que vous ne le pensez. Testez avec add_trauma(0.2) pour un impact standard avant de monter à 0,5. Les joueurs assis près de leur écran sont plus sensibles au mouvement que vous ne l'êtes en développement.
Ajustez decay, pas max_offset. La durée du shake compte plus que la distance. Un impact à 0,3 de trauma qui dure 0,8 seconde semble plus lourd qu'un qui saute à 50 px et disparaît en 2 frames.
Plafonnez la rotation aggressivement. max_roll = 0.08 (environ 4,5°) est le bon compromis pour la plupart des jeux 2D. Au-delà de 0,15, les joueurs commencent à ressentir le mal des transports, notamment sur mobile.
Ne pas appeler add_trauma à chaque frame pour les effets continus. Un lance-flammes appelant add_trauma(0.3) à 60 FPS verrouille la caméra à 1,0 indéfiniment. Utilisez add_trauma(0.3 * delta) pour les effets soutenus.
Réinitialisez l'offset quand le trauma atteint zéro. Le code ci-dessus le fait déjà. L'oublier laisse la caméra définitivement décalée par la valeur de bruit sur laquelle elle s'est arrêtée.
Les caméras avec interpolation physique demandent une attention particulière. Si votre projet utilise l'interpolation physique, appliquez le shake après que l'interpolation s'est exécutée. Consultez le fil de discussion sur le forum Godot pour les détails.
Exemples dans des jeux connus
Nuclear Throne (Vlambeer, 2015) — Chaque arme a sa propre signature de shake : le pistolet nudge, le fusil à pompe donne un coup, le laser vibre. Dans son talk de 2013 aux INDIGO Classes (YouTube), Nijman montre le moment précis où le screen shake a été ajouté au prototype, et la réaction immédiate qu'il a provoquée. L'idée centrale — l'intensité proportionnelle au poids de l'arme — se traduit directement par le tableau de trauma ci-dessus.
Celeste (Maddy Thorson & Noel Berry, 2018) — Utilise un shake extrêmement retenu. Le dash à peine bouge la caméra ; la mort est la seule action qui déclench un shake important. Cette sobriété volontaire garde le jeu lisible à grande vitesse — quand le shake arrive, il signifie quelque chose. Le talk GDC 2017 sur le level design (GDC Vault) aborde la façon dont chaque feedback a été maintenu minimal au service de la platforme de précision.
Hollow Knight (Team Cherry, 2017) — Les affrontements et les parades utilisent un shake bref et vif couplé à un freeze frame (hit-stop). La combinaison est ce qui fait atterrir les coups : le freeze retient la frame au moment de l'impact, puis le shake annonce le résultat de la collision. Si vous construisez un jeu d'action, screen shake et hit-stop vont ensemble.

FAQ
Le screen shake nuit-il à l'accessibilité ?
Oui, pour certains joueurs — les troubles vestibulaires rendent le mouvement de caméra réellement inconfortable, voire douloureux. Ajoutez un curseur Screen Shake (0–100 %) dans vos options et multipliez la valeur d'entrée de add_trauma() par ce facteur. Dix lignes de code pour couvrir tous vos joueurs.
Dois-je utiliser Camera2D.offset ou Camera2D.position ?
Utilisez offset. Modifier position entre en conflit avec toute logique de suivi ou de lissage sur la même caméra. offset s'ajoute au-dessus de la position calculée, sans interférer.
Mon shake a l'air robotique / trop régulier. Que se passe-t-il ?
La fréquence du bruit est probablement trop élevée. Essayez n.frequency = 0.3 dans _make_noise(). Vérifiez aussi que vous incrémentez _noise_t dans _process() et non dans _physics_process() — la physique tourne à 60 Hz fixe, ce qui crée un rythme métronomique.
Est-ce que ça marche en 3D ?
Oui, mais rotation devient trois axes et offset une translation 3D. Étendez Camera3D à la place de Camera2D, remplacez offset par h_offset / v_offset (ou surchargez la base de la caméra), et le reste de la logique reste identique.
Comment combiner ça avec un hit-stop ?
Déclenchez add_trauma() immédiatement à l'impact. Pour le hit-stop, passez Engine.time_scale = 0.05 pendant 60 à 100 ms, puis restaurez-le à 1.0. Faites tourner le timer avec process_callback = Timer.TIMER_PROCESS_PHYSICS et assurez-vous que votre caméra de shake utilise _process() (pas _physics_process()), pour que le shake continue pendant le freeze.
Le screen shake est peu coûteux, rapide à mettre en place, et l'un des meilleurs investissements pour le game feel. Collez le script ci-dessus, appelez add_trauma(0.25) sur votre prochain impact de balle, et vous ne reviendrez pas en arrière. Une fois que vous ressentez la différence, vous voudrez la boîte à outils complète — explorez le Game Feel Cookbook pour le hit-stop, le squash & stretch, et tout ce qui fait dire aux joueurs "ça a l'air vraiment bon."


