singe/docs/fr/lessons/24-physics.adoc

564 lines
23 KiB
Text
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[#_lesson_24_physics]
== Leçon 24 : La physique
image::learn/24-physics.png[La leçon terminée, 480]
Jusqu'ici, tout ce qui bougeait dans vos jeux bougeait parce que vous faisiez
l'arithmétique. Un nombre augmentait de deux à chaque image ; s'il dépassait un
bord, vous le faisiez repartir dans l'autre sens. C'est la façon honnête de
faire bouger un jeu, et la plupart des jeux sont faits de ça.
Cette leçon confie l'arithmétique à quelqu'un d'autre. Un *moteur physique*
simule le monde -- la gravité, les chutes, les glissades, les empilements, les
rebonds, les roulades, et les choses qui se cognent -- et déplace vos nœuds à
votre place. À la fin, vous aurez un plateau de caisses et un canon qui les
renverse. La dernière section explique quand ne rien faire de tout ça, et c'est
la partie à lire deux fois.
[#_what_it_does_and_what_it_does_not]
=== Ce qu'elle fait, et ce qu'elle ne fait pas
Donnez un *corps* à un nœud et la simulation le prend en charge. À partir de
là, il tombe sous l'effet de la gravité, atterrit sur ce qui se trouve en
dessous, bascule quand on le frappe hors de son centre, repose dans une pile,
glisse jusqu'à s'arrêter, et le nœud suit partout où la simulation le met.
Vous arrêtez de régler sa position. C'est le marché : vous cédez la position et
la rotation de ce nœud, et en échange vous obtenez un comportement que vous
n'avez pas eu à écrire.
Ce qu'elle ne fait pas, c'est quoi que ce soit qui concerne votre jeu. Elle ne
sait pas ce qu'est un score, ni que la caisse rouge est celle qui rapporte des
points, ni que tomber du bord coûte une vie. Elle n'anime pas un personnage qui
marche, elle ne sait pas quelle sensation doit donner un saut, et elle ne fera
pas bouger un objet d'une façon que vous pouvez prévoir exactement. La physique
est une machine à produire un désordre plausible.
Les unités sont les mètres, les kilogrammes et les secondes. La gravité vaut
`0, -9.81, 0` sauf si vous en décidez autrement, ce qui est celle de la Terre,
vers le bas, en mètres par seconde au carré. Une caisse d'une unité de large
fait un mètre de large, et elle tombe comme telle. Si votre scène est
construite à une autre échelle, tout aura l'air de tomber dans du sirop ou
dans un dessin animé, et la solution est de construire en mètres.
[#_a_floor_and_one_crate]
=== Un sol et une caisse
Créez un dossier, créez `crates.singe`, et tapez ceci. C'est une scène, une
dalle, une caisse au-dessus de la dalle, et trois lignes de physique.
[source,lua]
----
sceneEnable(true)
sceneSetBackground(16, 18, 26)
sceneSetAmbient(40, 42, 50)
local stone = materialNew()
materialSetColor(stone, 110, 110, 120)
materialSetRoughness(stone, 0.9)
local wood = materialNew()
materialSetColor(wood, 190, 140, 80)
materialSetRoughness(wood, 0.8)
local floor = nodeNew()
nodeSetMesh(floor, meshBox(16, 0.4, 16), stone)
nodeSetPosition(floor, 0, -0.2, 0)
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
local crate = nodeNew()
nodeSetMesh(crate, meshBox(0.8, 0.8, 0.8), wood)
nodeSetPosition(crate, 0.3, 4, 0)
nodeSetRotation(crate, 20, 0, 15)
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
bodySetMass(crate, 4)
local sun = lightNew(LIGHT_DIRECTIONAL)
nodeSetPosition(sun, -5, 9, 6)
nodeLookAt(sun, 0, 0, 0)
lightSetIntensity(sun, 1.7)
lightSetShadow(sun, true)
local camera = nodeNew()
nodeSetPosition(camera, 0, 4, 11)
nodeLookAt(camera, 0, 1, 0)
cameraSet(camera)
function onOverlayUpdate()
return OVERLAY_UPDATED
end
----
Lancez-le avec `Singe -R crates`. La caisse tombe de quatre mètres de haut,
atterrit sur un coin parce que vous l'avez inclinée, bascule sur une face, et
s'arrête. Appuyez sur *F5* pour la relancer et regardez-la encore une fois.
Rien dans `onOverlayUpdate` ne déplace quoi que ce soit. Les deux appels à
`bodyNew` ont tout fait.
[#_what_just_happened_20]
=== Ce qui vient de se passer
[source,lua]
----
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
----
`bodyNew` donne un corps à un nœud. Elle prend le nœud, la sorte de corps dont
il s'agit, la forme que la simulation doit utiliser pour lui, et les
dimensions de cette forme.
Il y a trois sortes, et choisir la bonne, c'est l'essentiel du savoir-faire.
* `BODY_STATIC` ne bouge jamais. Il est placé là où se trouve le nœud quand
vous créez le corps, et il y reste pour toujours. Les sols, les murs, le
terrain, la borne, le niveau. Déplacer son nœud après coup ne déplace pas le
corps, et c'est un piège à retenir : statique veut dire statique.
* `BODY_DYNAMIC` est déplacé par la simulation. Il tombe, rebondit, glisse, se
fait pousser, et entraîne son nœud avec lui. C'est une caisse, un tonneau,
une balle, un débris.
* `BODY_KINEMATIC` va là où *vous* mettez son nœud, et écarte de son chemin les
corps dynamiques sans jamais être poussé lui-même. Une plateforme mobile, une
raquette, une porte sur un rail, le poing d'un personnage qui frappe. C'est
le pont entre votre arithmétique et la simulation : vous le pilotez, il les
pilote.
La forme n'est pas le maillage. C'est une doublure grossière que la simulation
utilise parce qu'elle peut répondre à des questions sur une boîte bien plus
vite que sur neuf cents triangles. `SHAPE_BOX` prend une largeur, une hauteur
et une profondeur ; `SHAPE_SPHERE` prend un rayon ; `SHAPE_CAPSULE` et
`SHAPE_CYLINDER` prennent un rayon et une hauteur. Il y a `SHAPE_HULL`, qui
enveloppe au plus près le maillage qui se trouve sous le nœud -- la réponse
habituelle pour un modèle -- et `SHAPE_MESH`, les triangles exacts, que le
décor statique peut utiliser et qu'un objet qui tombe ne peut pas utiliser.
C'est à vous de donner les bonnes dimensions. Rien ne vérifie que le corps que
vous avez décrit correspond au maillage que vous avez dessiné, et une caisse
dessinée à `0.8` avec un corps créé à `2` flottera au-dessus du sol et
renversera des choses à distance. Quand quelque chose se comporte comme s'il
avait une autre taille, c'est qu'il l'a.
[source,lua]
----
bodySetMass(crate, 4)
----
La masse d'un corps dynamique vient de sa forme, à la densité de l'eau, ce qui
rend les choses bien plus lourdes que vous ne l'attendez : une boîte de la
taille d'une porte pèse environ cent quarante kilogrammes, et rien de ce que
vous pourrez lui lancer ne la fera bouger. Réglez la masse de tout ce qui doit
paraître léger. Quatre kilogrammes, c'est une caisse qu'on peut soulever.
La masse compte parce que les forces se répartissent selon elle. La même
poussée envoie plus loin une chose légère, et une impulsion égale à la masse
multipliée par la vitesse est ce qu'il faut pour mettre quelque chose en
mouvement à cette vitesse. Réglez d'abord la masse et les forces ensuite,
sinon vous tournerez en rond.
[#_making_things_move]
=== Faire bouger les choses
Trois appels poussent un corps dynamique, et ils ne sont pas interchangeables.
`bodySetVelocity(node, x, y, z)` dit « tu te déplaces maintenant à cette
vitesse », en remplaçant ce que le corps faisait. C'est un service, un lancement, une
apparition avec déjà un peu de vitesse.
`bodyApplyImpulse(node, ix, iy, iz)` est un coup : un coup de pied, une
explosion, une batte qui rencontre une balle. Elle s'ajoute à ce que le corps
faisait déjà. Donnez-lui un point du monde sous la forme de trois nombres de
plus et elle frappe *là* au lieu du milieu, si bien qu'une caisse frappée près
du haut bascule au lieu de glisser.
`bodyApplyForce(node, fx, fy, fz)` est une poussée qui dure : une fusée, un
ventilateur, un tapis roulant. Une force appliquée pendant une seule image ne
fait presque rien, ce qui surprend ceux qui s'attendaient à une impulsion.
La gravité elle-même, c'est `physicsSetGravity(x, y, z)`, et elle s'applique
au monde entier. La Lune, c'est `physicsSetGravity(0, -1.62, 0)`, l'espace, que
des zéros, et un jeu avec la gravité de côté est à une ligne de là.
Deux réglages de plus complètent le ressenti d'un objet.
`bodySetBounce(node, amount)` va de `0`, qui s'arrête net, à `1`, qui repart
avec tout ce qu'il avait en arrivant ; la valeur par défaut est un `0.1` à
peine perceptible. `bodySetFriction(node, amount)` vaut `0` pour la glace et
`0.5` par défaut. Les deux se combinent entre les deux choses qui se touchent,
donc un sol glacé demande le petit nombre sur le sol, pas sur tout ce qui
marche dessus.
[#_knowing_when_things_touch]
=== Savoir quand les choses se touchent
[source,lua]
----
function onCollision(nodeA, nodeB, x, y, z, speed)
if speed > 4 then
knocks = knocks + 1
end
end
----
Ici, `knocks` est une variable à vous, qui attend en haut du fichier avec
`local knocks = 0`, et le jouet plus bas l'affiche.
`onCollision` est un callback, comme `onOverlayUpdate` : vous l'écrivez, le
moteur l'appelle. Il arrive une fois pour chaque nouveau contact -- deux corps
qui restent en contact ne le signalent pas de nouveau -- avec les deux nœuds,
le point du monde où ils se sont rencontrés, et la vitesse à laquelle ils se
sont rencontrés. C'est la vitesse qui permet à un petit choc et à un fracas de
ne pas sonner pareil, et c'est pour ça que le test ci-dessus ignore le doux
tassement d'une pile.
L'autre moitié de la paire, c'est `onTrigger`. Faites d'un corps un
déclencheur avec `bodySetTrigger(node, true)` et il arrête de pousser les
choses : il devient une zone qui signale ce qui y entre et en sort, par
`onTrigger(trigger, other, entered)`. Une ligne d'arrivée, une porte, une
fosse, un objet à ramasser. C'est le descendant en 3D des tests de collision
de la leçon onze, et pour tout ce qui ressemble à « le joueur est-il arrivé
ici », c'est l'appel qu'il vous faut plutôt qu'un corps solide.
[#_the_toy]
=== Le jouet
Maintenant, le tout. Partez du script ci-dessus et transformez-le en ceci : un
plateau avec quatre murs, six caisses en pyramide, et une boule d'acier que
vous pouvez viser et tirer. Les morceaux sont tous des choses que vous avez
déjà rencontrées.
Tout ce dont le nouveau code se souvient va en haut du fichier, au-dessus de
la scène :
[source,lua]
----
local crates = {}
local balls = {}
local aimX = 0
local aimLeft = false
local aimRight = false
local knocks = 0
local showShapes = false
----
Deux handles partagés de plus vont avec les matériaux : un métal poli pour la
boule et le repère de visée, et un seul maillage de caisse pour les six
caisses.
[source,lua]
----
local steel = materialNew()
materialSetColor(steel, 200, 205, 215)
materialSetMetallic(steel, 1)
materialSetRoughness(steel, 0.25)
local crateMesh = meshBox(0.8, 0.8, 0.8)
----
Les murs du plateau sont quatre boîtes statiques, créées dans une boucle à
partir d'une table d'emplacements, et c'est la leçon six qui fait un travail
honnête :
[source,lua]
----
local wallMesh = meshBox(16, 1, 0.4)
for _, side in ipairs({ { 0, -8, 0 }, { 0, 8, 0 }, { -8, 0, 90 }, { 8, 0, 90 } }) do
local wall = nodeNew()
nodeSetMesh(wall, wallMesh, stone)
nodeSetPosition(wall, side[1], 0.5, side[2])
nodeSetRotation(wall, 0, side[3], 0)
bodyNew(wall, BODY_STATIC, SHAPE_BOX, 16, 1, 0.4)
end
----
Chaque entrée est un X, un Z et une rotation autour de Y. Deux murs sont
couchés en travers, au fond et devant ; deux sont tournés de quatre-vingt-dix
degrés pour faire les côtés. Un seul maillage sert aux quatre, et le corps est
créé après que le nœud a été placé et tourné, parce qu'un corps statique prend
la position et la rotation du nœud au moment où il est créé.
Les caisses viennent d'une fonction, pour que six caisses fassent six lignes :
[source,lua]
----
local function crateAt(x, y)
local crate = nodeNew()
nodeSetMesh(crate, crateMesh, wood)
nodeSetPosition(crate, x, y, -2)
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
bodySetMass(crate, 4)
crates[#crates + 1] = crate
end
----
Elle garde chaque caisse qu'elle crée dans la table `crates`, et c'est comme
ça que le callback de dessin peut plus tard leur poser une question à toutes.
Six appels construisent la pyramide, trois sur le sol, deux sur celles-ci, et
une au sommet :
[source,lua]
----
crateAt(-0.9, 0.4)
crateAt(0, 0.4)
crateAt(0.9, 0.4)
crateAt(-0.45, 1.25)
crateAt(0.45, 1.25)
crateAt(0, 2.1)
----
Les rangées du haut se trouvent à un doigt au-dessus de celles du dessous et
tombent en place dès que le jeu démarre. Laisser un petit écart est plus
facile que d'empiler exactement, et c'est plus gentil pour la simulation que
de lui donner une pile déjà tassée.
Tirer crée une nouvelle boule à chaque fois :
[source,lua]
----
local function fire()
local ball = nodeNew()
nodeSetMesh(ball, meshSphere(0.35, 24), steel)
nodeSetPosition(ball, aimX, 1.2, 6)
bodyNew(ball, BODY_DYNAMIC, SHAPE_SPHERE, 0.35)
bodySetMass(ball, 3)
bodySetBounce(ball, 0.3)
bodySetVelocity(ball, 0, 2, -14)
balls[#balls + 1] = ball
if #balls > 5 then
nodeDelete(balls[1])
table.remove(balls, 1)
end
end
----
Regardez l'ordre. Le nœud est créé, reçoit une forme à dessiner, est *placé*,
et ce n'est qu'ensuite qu'il reçoit un corps. Un corps dynamique commence là où
se trouve son nœud, et après ça la circulation se fait dans l'autre sens : le
corps déplace le nœud. Régler vous-même ensuite la position d'un nœud
dynamique, c'est se battre contre la simulation, et la simulation gagne. Quand
quelque chose doit être déplacé, donnez-lui une vitesse, frappez-le avec une
impulsion, ou faites comme ici -- créez-en un nouveau et laissez partir
l'ancien.
Laisser partir l'ancien, c'est `nodeDelete`, qui libère le nœud, tout ce qui se
trouve sous lui, et le corps qu'il portait. Sans ces trois lignes, chaque tir
resterait dans le monde pour toujours, et quelques centaines de boules plus
tard, la simulation ferait énormément de travail sur des choses que personne
ne voit. Faire le ménage fait partie de l'usage de la physique.
Les commandes et l'affichage forment le reste :
[source,lua]
----
function onInputPressed(what)
if what == SWITCH_BUTTON1 then
fire()
elseif what == SWITCH_BUTTON3 then
showShapes = not showShapes
if showShapes then
physicsSetDebug(DEBUG_SHAPES + DEBUG_CONTACTS)
else
physicsSetDebug(DEBUG_NONE)
end
elseif what == SWITCH_LEFT then
aimLeft = true
elseif what == SWITCH_RIGHT then
aimRight = true
end
end
----
Espace tire, Maj allume et éteint la vue de la physique, et les flèches,
tenues enfoncées, font glisser le point de lancement. Une plaque de métal
plate sans corps, déplacée à chaque image en `aimX`, montre d'où partira la
prochaine boule. Le script complet, avec le callback de relâchement et le reste
de la mise en place, est `24-physics.singe` dans le dossier `learn`.
`physicsSetDebug` est l'appel le plus utile de cette leçon. Il dessine ce que
la simulation contient réellement -- les vraies formes en fil de fer, et une
croix rouge partout où deux corps se sont touchés pendant ce pas de la
simulation -- par-dessus votre scène. La première fois qu'une caisse flotte à
une main au-dessus du sol, ou passe au travers, ou renverse quelque chose à
deux mètres de distance, allumez-le et la réponse sera à l'écran. Ajoutez
`DEBUG_STATIC` pour voir aussi le sol et les murs. Il ne coûte rien tant qu'il
est éteint.
Enfin, le comptage :
[source,lua]
----
function onOverlayUpdate()
local over = 0
local settled = true
if aimLeft then
aimX = math.max(aimX - 0.08, -5)
end
if aimRight then
aimX = math.min(aimX + 0.08, 5)
end
nodeSetPosition(marker, aimX, 0.05, 6)
for _, crate in ipairs(crates) do
local pitch, _, roll = nodeGetRotation(crate)
if math.abs(pitch) > 30 or math.abs(roll) > 30 then
over = over + 1
end
if not bodyIsResting(crate) then
settled = false
end
end
overlayClear()
overlayPrint(2, 2, "Arrows aim. Space fires. Shift shows the shapes.")
overlayPrint(2, 4, "knocked over " .. over .. " of " .. #crates .. " hard hits " .. knocks)
if settled then
overlayPrint(2, 6, "everything has settled")
end
return OVERLAY_UPDATED
end
----
Une caisse compte comme renversée quand elle penche de plus de trente degrés,
ce qui est une règle de jeu écrite en une ligne d'arithmétique par-dessus ce
que la simulation a fait. `nodeGetRotation` donne trois angles et ce code veut
le premier et le troisième, donc celui du milieu est attrapé dans une variable
appelée `_`. Ce tiret bas est un nom ordinaire qui n'a rien de spécial, utilisé
par convention pour dire « celui-là, je l'ignore ».
`bodyIsResting` dit si la simulation a mis un corps en sommeil parce qu'il a
cessé de bouger. C'est comme ça que vous savez qu'un tir est terminé : pas
avec une minuterie, pas au jugé, mais quand toutes les caisses dorment. Un jeu
de bowling compte les points de son tour exactement à ce moment-là.
[#_when_not_to_use_it]
=== Quand ne pas s'en servir
La physique est l'outil le plus tentant de n'importe quel moteur, et celui qui
gâche le plus de week-ends. Trois choses sont vraies à son sujet, et aucune
n'est la faute du moteur.
*Elle coûte cher.* Chaque corps dynamique demande du travail à chaque pas,
soixante fois par seconde, que quelqu'un le regarde ou non. Quarante caisses,
ça va. Quatre mille, non, et sur une petite machine, quatre cents non plus.
*Elle est imprévisible.* Lancez votre jouet deux fois et les caisses
atterrissent autrement. C'est ce qui lui donne l'air vivant, et c'est aussi
pour ça qu'un jeu ne peut pas compter sur elle pour quoi que ce soit qui doit
se passer pareil à chaque fois. Une porte qui doit s'ouvrir, une plateforme
qui doit arriver, un saut que le joueur doit pouvoir réussir : tout ça, c'est
de l'arithmétique, ou un tween, ou un corps cinématique sur un trajet que vous
avez écrit. Ne confiez jamais une chose que le joueur doit faire à une
simulation qui peut la laisser tomber derrière une caisse.
*Elle est difficile à régler.* La masse, le frottement, le rebond, la forme et
la taille tirent tous les uns sur les autres, et « ça ne fait pas le bon
effet » a rarement une seule cause. Chaque heure que vous passez à donner un
bon ressenti à une caisse physique est une heure que vous n'avez pas passée
sur le jeu.
Sortez-la donc quand le désordre *est* le but : des choses qui basculent, des
débris qui volent, une pile qui se tasse, une boule qui cogne partout dans une
pièce, un véhicule qui roule sur un terrain accidenté, un ragdoll qui dégringole
un escalier. Personne ne peut écrire ça à la main, et personne ne peut vous
dire exactement à quoi ça doit ressembler.
Ne la sortez pas quand vous savez ce qui doit se passer. Une balle qui vole en
ligne droite, c'est deux lignes de la leçon deux. Une plateforme qui monte et
descend, c'est `math.sin`. Un joueur qui marche et saute comme les joueurs s'y
attendent demande un contrôleur de personnage (`playerNew`, dans la
référence), parce qu'une personne n'est pas une boîte et ne se comporte jamais
comme une boîte. Un ennemi qui patrouille demande un trajet. Un menu qui glisse
à l'écran demande un tween.
La règle qui sert le mieux : la physique pour les choses que le joueur
regarde, et l'arithmétique pour les choses que le joueur fait.
[#_try_it_24]
=== Essayez
. *Changez le monde.* Ajoutez `physicsSetGravity(0, -1.62, 0)` vers le haut du
fichier et tirez de nouveau. Puis essayez `0, -30, 0`. Les mêmes caisses,
une autre planète, une ligne.
. *Changez la boule.* Rendez la boule dix fois plus lourde avec
`bodySetMass(ball, 30)`, puis dix fois plus légère avec `0.3`, en gardant la
même vitesse. Puis remettez la masse et changez plutôt le `-14`. Deux façons
de frapper plus fort, et elles ne donnent pas la même sensation.
. *Changez les caisses en glace.* Ajoutez `bodySetFriction(crate, 0.02)` dans
`crateAt`, et ajoutez une ligne qui donne aussi au sol un faible frottement.
Regardez la pile refuser de tenir debout dès le départ.
. *Faussez la forme exprès.* Dans `crateAt`, changez les dimensions du corps en
`2, 2, 2` pendant que le maillage reste à `0.8`. Lancez, regardez les caisses
flotter et se bousculer de nulle part, puis appuyez sur Maj et regardez la
vue de débogage l'expliquer en une seule image.
. *Comptez les points comme il faut.* Utilisez `bodyIsResting` sur chaque
caisse pour remarquer quand le tir est terminé, et seulement alors comptez
combien sont renversées et ajoutez-le à un score. C'est la partie d'un jeu de
bowling qui compte les points.
[#_break_it_on_purpose_23]
=== Cassez-le exprès
Ajoutez sous le `bodyNew` du sol une ligne qui rend le sol lourd, ce qu'il est
raisonnable de croire que vous voulez :
[source,lua]
----
bodySetMass(floor, 200)
----
Singe arrête le jeu avec quelque chose comme :
----
32:bodySetMass: Node 1 is not a dynamic body, or the mass is not positive.
----
Le premier nombre est la ligne dans votre propre fichier, donc le vôtre sera
différent.
Le nombre après `Node` est le handle du nœud, le nombre que `nodeNew` a
renvoyé. La plainte est exacte : la masse appartient aux choses que la
simulation déplace, et un corps statique n'est déplacé par rien, donc il n'a
pas de masse à régler. Le demander est le signe que le corps n'est pas du bon
type pour ce que vous aviez en tête -- et si vous voulez vraiment un sol lourd
qu'on puisse déplacer, ce qu'il vous faut, c'est un corps dynamique avec une
grande masse, ou un corps cinématique que vous déplacez vous-même.
Le même message apparaît pour une masse de `0`, que l'on écrit en pensant
« sans poids ». « Sans poids » n'est pas une masse. C'est un monde sans
gravité, ou `playerSetGravityScale` sur un contrôleur de personnage.
[#_what_you_learned_24]
=== Ce que vous avez appris
* Un corps s'attache à un nœud, et à partir de là, le nœud et le corps ne font
qu'un.
* Un corps statique ne bouge jamais, un corps dynamique est déplacé par la
simulation, et un corps cinématique est déplacé par vous et pousse tout le
reste de côté.
* La forme de collision est une doublure du maillage, et c'est à vous de faire
en sorte que ses dimensions correspondent à ce que vous avez dessiné.
* Par défaut, la masse est le volume de la forme à la densité de l'eau, donc
réglez-la sur tout ce qui doit paraître léger.
* `bodySetVelocity` remplace le mouvement d'un corps, `bodyApplyImpulse` est un
coup, et `bodyApplyForce` est une poussée qui dure.
* Ne réglez pas la position d'un nœud dynamique ; supprimez-le et créez-en un
nouveau quand vous devez le faire repartir de zéro.
* `onCollision` signale chaque nouveau contact avec son point et sa vitesse, et
un corps déclencheur signale les arrivées et les départs par `onTrigger`.
* `bodyIsResting` vous dit quand tout s'est arrêté, ce qui est en général le
moment où un tour est fini.
* `physicsSetDebug` dessine la vue de la simulation elle-même, et elle répond à
la plupart des questions de physique plus vite que la lecture du code.
* La physique coûte cher, ne se répète pas, et demande beaucoup de réglages.
Servez-vous-en pour le désordre, pas pour les choses dont le jeu dépend.
[#_next_time_24]
=== La prochaine fois
Des caisses qui tombent, c'est une sorte de spectacle. L'autre sorte est faite
de mille petites choses qui vivent une demi-seconde : des étincelles, de la
fumée, du feu, de la pluie et de la poussière. La prochaine leçon parle des
émetteurs, et elle finit par une explosion. C'est la leçon vingt-cinq.