548 lines
20 KiB
Text
548 lines
20 KiB
Text
[#_lesson_16_branching]
|
||
== Leçon 16 : Les embranchements
|
||
|
||
image::learn/16-branching.png[La leçon terminée, 480]
|
||
|
||
Un jeu vidéo à embranchements est un film avec des bifurcations. Une scène se
|
||
joue, le joueur choisit, et c'est une autre scène qui se joue ensuite. Faites ça
|
||
vingt ou trente fois et vous avez un jeu auquel les gens rejoueront pendant des
|
||
années pour essayer de trouver la fin qu'ils n'ont pas encore vue.
|
||
|
||
Dans cette leçon, vous allez en construire un. Il a quatre scènes, deux choix,
|
||
un minuteur sur chaque choix, une mort, et trois vies. Le code qui le fait
|
||
tourner fait une trentaine de lignes, et il en ferait toujours une trentaine si
|
||
le jeu avait deux cents scènes -- parce que le jeu lui-même ne sera pas du tout
|
||
écrit en code.
|
||
|
||
[#_imagine_it_is_a_dragon]
|
||
=== Imaginez que c'est un dragon
|
||
|
||
La vidéo que tout le monde a, c'est `Singe/menuBackground.mkv`, les mêmes 420
|
||
images que vous pilotez depuis la leçon quatorze. C'est une boule de feu, un
|
||
dragon en papier, le mot `SINGE`, puis une grille magenta qui défile au loin
|
||
sous un soleil orange, pour toujours.
|
||
|
||
Ce n'est pas un drame. Il n'y a personne dedans qui soit en danger, et le
|
||
coucher de soleil se moque bien de ce que vous décidez.
|
||
|
||
Jouez le jeu quand même. Le dragon est assez réel pour deux minutes, et tout ce
|
||
que vous allez écrire marche exactement pareil quand les images sont celles d'un
|
||
cascadeur qui tombe d'une falaise. Voici la carte, et les numéros d'image sont
|
||
ceux que vous avez trouvés vous-même à la leçon quatorze :
|
||
|
||
[cols="1,1,3",options="header"]
|
||
|===
|
||
| Scène | Images | Ce qui se passe
|
||
| `arrive` | 0 à 119 | Du feu, et le dragon se pose. *Gauche ou droite ?*
|
||
| `sunset` | 120 à 259 | Il crache du feu. *Se baisser à gauche ou courir à droite ?*
|
||
| `pit` | 260 à 339 | Vous êtes dévoré. Vous perdez une vie.
|
||
| `escape` | 340 à 410 | La grille s'éloigne et vous vous en êtes sorti.
|
||
|===
|
||
|
||
À gauche, c'est toujours le droit chemin, pour ainsi dire : à gauche depuis
|
||
`arrive`, on va à `sunset`, à gauche depuis `sunset`, on arrive à `escape`. À
|
||
droite, c'est le `pit` les deux fois, et pareil si vous mettez trop de temps.
|
||
|
||
[#_the_map_is_data_not_code]
|
||
=== La carte, ce sont des données, pas du code
|
||
|
||
Voici le jeu tout entier, et c'est une table.
|
||
|
||
[source,lua]
|
||
----
|
||
local scenes = {
|
||
arrive = {
|
||
first = 0,
|
||
last = 119,
|
||
ask = 30,
|
||
prompt = "The dragon lands. Left or Right?",
|
||
left = "sunset",
|
||
right = "pit",
|
||
goesTo = "pit"
|
||
},
|
||
sunset = {
|
||
first = 120,
|
||
last = 259,
|
||
ask = 170,
|
||
prompt = "It breathes fire. Duck Left or run Right?",
|
||
left = "escape",
|
||
right = "pit",
|
||
goesTo = "pit"
|
||
},
|
||
pit = {
|
||
first = 260,
|
||
last = 339,
|
||
death = true,
|
||
goesTo = "arrive"
|
||
},
|
||
escape = {
|
||
first = 340,
|
||
last = 410
|
||
}
|
||
}
|
||
----
|
||
|
||
Une table avec des noms sur la gauche au lieu de numéros, exactement comme à la
|
||
leçon six, et chaque valeur dedans est une autre table. Chacune de ces tables
|
||
intérieures est un *enregistrement* : une scène, décrite champ par champ.
|
||
|
||
`scenes.arrive` est la première scène. `scenes.arrive.first` vaut `0`.
|
||
`scenes.arrive.left` est la chaîne `"sunset"`, qui est le nom d'une autre scène
|
||
de la même table -- et c'est ainsi que les bifurcations sont notées. Une scène
|
||
ne contient pas la scène qui la suit. Elle contient le *nom* de la scène qui la
|
||
suit, et ce nom est cherché le moment venu.
|
||
|
||
Les champs veulent dire ceci :
|
||
|
||
`first` et `last`:: Les images entre lesquelles la scène se déroule.
|
||
`ask`:: L'image sur laquelle la question apparaît. Le joueur a de `ask` à
|
||
`last` pour répondre, et c'est ça, le minuteur.
|
||
`prompt`:: Ce que dit la question.
|
||
`left` et `right`:: La scène à laquelle mène chaque réponse.
|
||
`goesTo`:: Où va la scène quand personne n'a choisi -- parce que le joueur n'a
|
||
plus eu de temps, ou parce qu'il n'y avait pas de question au départ. Pour
|
||
`pit`, qui est une mort, c'est là que repart la nouvelle tentative.
|
||
`death`:: Présent, et à `true`, seulement dans une scène qui coûte une vie.
|
||
|
||
Trois choses méritent d'être remarquées à propos de cette table.
|
||
|
||
La première, c'est que c'est le document de conception. Vous pouvez la donner à
|
||
quelqu'un qui n'a jamais écrit une ligne de Lua, et il saura y lire le jeu.
|
||
Quand vous voulez que le dragon offre trois chemins, vous ajoutez un champ.
|
||
Quand vous voulez une autre scène, vous ajoutez un autre enregistrement. Vous
|
||
n'allez pas chercher le code qui joue les scènes, parce que vous n'y touchez
|
||
pas.
|
||
|
||
La deuxième, c'est que les enregistrements n'ont pas tous la même forme. `pit`
|
||
n'a ni `ask`, ni `prompt`, ni `left` -- il n'y a rien à y choisir. `escape` n'a
|
||
pas de `goesTo`, parce que c'est la fin et qu'elle ne mène nulle part. Un champ
|
||
qui n'est pas dans une table se lit comme `nil`, le mot que vous avez rencontré
|
||
à la leçon trois pour « il n'y a rien », et demander `scenes.escape.goesTo` n'est
|
||
pas une erreur. C'est `nil`, et `nil` est une réponse. Le code qui lit ces
|
||
enregistrements doit s'y attendre, et dans une minute vous verrez la seule ligne
|
||
qui s'y attend.
|
||
|
||
La troisième, c'est ce que la table ne contient *pas* : rien dedans ne dessine,
|
||
ne joue, ni n'attend. C'est une description. Cette séparation, c'est tout le
|
||
secret de cette leçon.
|
||
|
||
[#_one_function_that_plays_any_scene]
|
||
=== Une fonction qui joue n'importe quelle scène
|
||
|
||
Voici la moitié code, et il n'y a qu'une seule fonction d'une taille notable.
|
||
Elle est écrite dans le modèle à fil d'exécution de la leçon quinze, parce qu'un
|
||
jeu à embranchements est la chose la plus séquentielle qui soit.
|
||
|
||
[source,lua]
|
||
----
|
||
local function playScene(name)
|
||
local scene = scenes[name]
|
||
|
||
discSkipToFrame(scene.first)
|
||
while discGetFrame() < scene.last do
|
||
local frame = discGetFrame()
|
||
local asking = scene.ask ~= nil and frame >= scene.ask
|
||
|
||
if not asking then
|
||
answer = nil
|
||
elseif answer == "left" or answer == "right" then
|
||
return scene[answer]
|
||
end
|
||
drawScene(scene, frame, asking)
|
||
singeYield()
|
||
end
|
||
discPause()
|
||
|
||
if scene.death then
|
||
lives = lives - 1
|
||
if lives == 0 then
|
||
ending = "The dragon wins. Game over."
|
||
return nil
|
||
end
|
||
waitForGo("Press the space bar to try again.")
|
||
end
|
||
|
||
return scene.goesTo
|
||
end
|
||
----
|
||
|
||
Elle prend le nom d'une scène, la joue, et renvoie le nom de la scène qui vient
|
||
ensuite. C'est tout son contrat, et le jeu tient alors en trois lignes :
|
||
|
||
[source,lua]
|
||
----
|
||
function singeMain()
|
||
local scene = "arrive"
|
||
|
||
while scene ~= nil do
|
||
scene = playScene(scene)
|
||
end
|
||
waitForGo(ending .. " Space to quit.")
|
||
end
|
||
----
|
||
|
||
Commencez à `arrive`. Jouez la scène que vous tenez, quelle qu'elle soit, et
|
||
gardez ce qu'elle vous rend. Quand elle rend `nil`, il n'y a pas de scène
|
||
suivante, la boucle s'arrête, et la partie est finie.
|
||
|
||
[#_the_whole_thing_8]
|
||
=== Le tout
|
||
|
||
Créez `dragon.singe` dans votre dossier `movie` et tapez ceci. C'est le plus
|
||
long script du livre jusqu'ici, et chacun de ses morceaux a été expliqué, sauf
|
||
les deux petites fonctions utilitaires, qui viennent après.
|
||
|
||
[source,lua]
|
||
----
|
||
local FPS = 30
|
||
|
||
local scenes = {
|
||
arrive = {
|
||
first = 0,
|
||
last = 119,
|
||
ask = 30,
|
||
prompt = "The dragon lands. Left or Right?",
|
||
left = "sunset",
|
||
right = "pit",
|
||
goesTo = "pit"
|
||
},
|
||
sunset = {
|
||
first = 120,
|
||
last = 259,
|
||
ask = 170,
|
||
prompt = "It breathes fire. Duck Left or run Right?",
|
||
left = "escape",
|
||
right = "pit",
|
||
goesTo = "pit"
|
||
},
|
||
pit = {
|
||
first = 260,
|
||
last = 339,
|
||
death = true,
|
||
goesTo = "arrive"
|
||
},
|
||
escape = {
|
||
first = 340,
|
||
last = 410
|
||
}
|
||
}
|
||
|
||
local answer = nil
|
||
local ending = "You got out alive."
|
||
local lives = 3
|
||
|
||
|
||
local function drawScene(scene, frame, asking)
|
||
overlayClear()
|
||
overlayPrint(2, 2, "Lives: " .. lives)
|
||
if scene.death then
|
||
overlayPrint(2, 4, "That did not go well.")
|
||
elseif asking then
|
||
overlayPrint(2, 4, scene.prompt)
|
||
overlayPrint(2, 6, math.ceil((scene.last - frame) / FPS) .. " seconds left.")
|
||
end
|
||
end
|
||
|
||
|
||
local function waitForGo(text)
|
||
answer = nil
|
||
while answer ~= "go" do
|
||
overlayClear()
|
||
overlayPrint(2, 2, "Lives: " .. lives)
|
||
overlayPrint(2, 4, text)
|
||
singeYield()
|
||
end
|
||
end
|
||
|
||
|
||
local function playScene(name)
|
||
local scene = scenes[name]
|
||
|
||
discSkipToFrame(scene.first)
|
||
while discGetFrame() < scene.last do
|
||
local frame = discGetFrame()
|
||
local asking = scene.ask ~= nil and frame >= scene.ask
|
||
|
||
if not asking then
|
||
answer = nil
|
||
elseif answer == "left" or answer == "right" then
|
||
return scene[answer]
|
||
end
|
||
drawScene(scene, frame, asking)
|
||
singeYield()
|
||
end
|
||
discPause()
|
||
|
||
if scene.death then
|
||
lives = lives - 1
|
||
if lives == 0 then
|
||
ending = "The dragon wins. Game over."
|
||
return nil
|
||
end
|
||
waitForGo("Press the space bar to try again.")
|
||
end
|
||
|
||
return scene.goesTo
|
||
end
|
||
|
||
|
||
function onInputPressed(what)
|
||
if what == SWITCH_LEFT then
|
||
answer = "left"
|
||
elseif what == SWITCH_RIGHT then
|
||
answer = "right"
|
||
elseif what == SWITCH_BUTTON1 then
|
||
answer = "go"
|
||
end
|
||
end
|
||
|
||
|
||
function singeMain()
|
||
local scene = "arrive"
|
||
|
||
while scene ~= nil do
|
||
scene = playScene(scene)
|
||
end
|
||
waitForGo(ending .. " Space to quit.")
|
||
end
|
||
|
||
|
||
dofile("Singe/Framework.singe")
|
||
----
|
||
|
||
Lancez-le :
|
||
|
||
----
|
||
Singe -R -v Singe/menuBackground.mkv movie/dragon.singe
|
||
----
|
||
|
||
Jouez quatre ou cinq fois. Allez deux fois à gauche et sortez-vous-en. Allez à
|
||
droite et faites-vous dévorer. Restez les bras croisés et faites-vous dévorer
|
||
quand même. Perdez vos trois vies.
|
||
|
||
[#_what_just_happened_12]
|
||
=== Ce qui vient de se passer
|
||
|
||
[source,lua]
|
||
----
|
||
discSkipToFrame(scene.first)
|
||
while discGetFrame() < scene.last do
|
||
----
|
||
|
||
Une scène, c'est un saut suivi d'une attente, c'est-à-dire le schéma de la leçon
|
||
quinze, avec les nombres qui sortent de l'enregistrement au lieu d'être tapés.
|
||
`discSkipToFrame` va à une image et lit à partir de là, et c'est exactement ce
|
||
qu'est un changement de scène.
|
||
|
||
Le numéro d'image est juste dès l'instant où l'appel se termine, donc le `while`
|
||
ne croit jamais qu'il est encore dans la scène que vous venez de quitter.
|
||
|
||
[source,lua]
|
||
----
|
||
local asking = scene.ask ~= nil and frame >= scene.ask
|
||
----
|
||
|
||
La seule ligne qui est prête pour un champ manquant, et la raison pour laquelle
|
||
la scène de mort peut se passer de `ask`.
|
||
|
||
`and` veut dire que les deux moitiés doivent être vraies, comme à la leçon
|
||
trois, et Lua vérifie d'abord la moitié gauche. Si `scene.ask` vaut `nil`, la
|
||
moitié gauche est fausse, Lua s'arrête là, et la comparaison de droite n'a
|
||
jamais lieu. Et c'est tant mieux, parce que comparer `nil` à un nombre est une
|
||
erreur, et vous la rencontrerez à la fin de cette leçon.
|
||
|
||
`asking` est la réponse à « la question est-elle à l'écran en ce moment ? »,
|
||
calculée une fois par image et utilisée trois fois.
|
||
|
||
[source,lua]
|
||
----
|
||
if not asking then
|
||
answer = nil
|
||
elseif answer == "left" or answer == "right" then
|
||
return scene[answer]
|
||
end
|
||
----
|
||
|
||
Tant que la question n'est pas affichée, tout ce sur quoi le joueur appuie est
|
||
jeté. Il le faut, sinon un joueur qui reste appuyé sur la flèche gauche pendant
|
||
le film répond à une question qu'il n'a pas lue.
|
||
|
||
Une fois la question affichée, une réponse termine aussitôt la scène.
|
||
`return scene[answer]` mérite qu'on s'y arrête : `answer` contient la chaîne
|
||
`"left"` ou `"right"`, et `scene["left"]` est la même chose que `scene.left`.
|
||
Mettre le nom entre crochets au lieu de l'écrire après un point vous permet de
|
||
chercher un champ dont le nom se trouve dans une variable. Deux lignes de code
|
||
pour les deux réponses, et pour cinq réponses, ce serait toujours deux lignes.
|
||
|
||
Le `elseif` explique aussi pourquoi seuls `"left"` et `"right"` passent. La
|
||
barre d'espace met `answer` à `"go"`, et `scene["go"]` vaut `nil`, donc un appui
|
||
égaré sur la barre d'espace pendant une question terminerait la partie en
|
||
silence. Dites ce que vous acceptez.
|
||
|
||
[source,lua]
|
||
----
|
||
discPause()
|
||
----
|
||
|
||
Juste après la boucle, et facile à oublier. La scène a atteint sa dernière image
|
||
mais le disque n'a aucune idée que la scène est finie -- il continue tout droit
|
||
dans les images qui se trouvent venir ensuite dans le fichier, ce qui, dans un
|
||
vrai jeu, est le milieu d'une scène venue de tout à fait ailleurs. Arrêtez-le
|
||
dès que vous n'en voulez plus.
|
||
|
||
[source,lua]
|
||
----
|
||
if scene.death then
|
||
lives = lives - 1
|
||
----
|
||
|
||
`scene.death` vaut `nil` pour toutes les scènes sauf `pit`, et `nil` compte
|
||
comme faux dans un `if`, donc ceci se lit « si cette scène était une mort ».
|
||
Vous n'avez pas à écrire `death = false` dans les trois autres enregistrements,
|
||
et vous ne devriez pas le faire : l'absence du champ le dit tout aussi bien, et
|
||
il n'y a alors qu'un seul endroit qui décide quelles scènes sont mortelles.
|
||
|
||
La nouvelle tentative, ce sont les deux lignes en dessous. Retirez une vie, et
|
||
s'il en reste, restez sur l'image et attendez la barre d'espace. Ensuite,
|
||
`return scene.goesTo` renvoie le joueur à `arrive`, et c'est l'enregistrement
|
||
qui le sait. Changez `goesTo` en `"sunset"` et la nouvelle tentative devient un
|
||
point de contrôle au lieu d'un recommencement -- un mot, et la difficulté du
|
||
jeu change.
|
||
|
||
[source,lua]
|
||
----
|
||
overlayPrint(2, 6, math.ceil((scene.last - frame) / FPS) .. " seconds left.")
|
||
----
|
||
|
||
Le minuteur. `scene.last - frame` est le nombre d'images qui restent, et diviser
|
||
par le nombre d'images dans une seconde le transforme en secondes. `math.ceil`
|
||
arrondit un nombre à l'entier supérieur, donc le compte à rebours affiche `3`,
|
||
`2`, `1` et jamais `0.4` ni `2.966666`.
|
||
|
||
`FPS` vaut `30` parce que cette vidéo tourne à un peu plus de trente images par
|
||
seconde. C'est le seul nombre du script qui soit une estimation, et c'est une
|
||
estimation nommée, en haut du fichier, plutôt qu'un `30` enfoui au milieu d'une
|
||
ligne, pour que, quand vous changez de vidéo, il y ait exactement une chose à
|
||
changer.
|
||
|
||
Remarquez par rapport à quoi le minuteur est mesuré : le film. Le joueur fait
|
||
la course contre la vidéo, pas contre une horloge, donc la question ne peut
|
||
jamais durer plus longtemps que le plan auquel elle appartient. C'est pour ça
|
||
que les jeux à embranchements comptent en images.
|
||
|
||
[#_why_not_write_it_out_by_hand]
|
||
=== Pourquoi ne pas tout écrire à la main
|
||
|
||
Vous pourriez écrire ce jeu comme une seule longue fonction. Lire les images 0
|
||
à 119 ; si gauche, lire 120 à 259 ; si encore gauche, lire 340 à 410 ; sinon
|
||
lire 260 à 339, retirer une vie, revenir au début. Pour quatre scènes, ce serait
|
||
plus court que ce que vous avez.
|
||
|
||
Pour quarante scènes, ce serait une catastrophe, et ça vaut la peine de dire
|
||
précisément pourquoi.
|
||
|
||
Les numéros d'image seraient éparpillés dans le code, chacun écrit une fois et
|
||
jamais nommé, donc changer l'endroit où commence une scène voudrait dire
|
||
retrouver chaque endroit où elle est mentionnée. La forme de l'histoire serait
|
||
enfouie dans la forme des instructions `if`, donc personne ne pourrait voir le
|
||
jeu sans tout lire. Et ajouter une scène voudrait dire modifier le code qui
|
||
joue, c'est-à-dire le code qui marche déjà -- chaque nouvelle scène, une
|
||
occasion de plus d'en casser une ancienne.
|
||
|
||
La version que vous avez écrite a un morceau de code qui joue les scènes et un
|
||
morceau de données qui dit ce que sont les scènes. Ajouter une scène ne touche
|
||
que les données. Ça vaut bien la demi-heure de plus que ça vous a coûté, et
|
||
c'est la même idée que celle qu'il y a sous le fichier `games.dat` de la leçon
|
||
treize : décrivez la chose, puis écrivez un seul morceau de code qui lit la
|
||
description.
|
||
|
||
[#_try_it_16]
|
||
=== Essayez
|
||
|
||
. *Ajoutez une fin.* Donnez à `escape` une question et une deuxième scène de fin
|
||
qui réutilise des images prises quelque part dans le film. Seule la table
|
||
change.
|
||
. *Rendez-le équitable.* Baissez `ask` dans les deux scènes pour que le joueur
|
||
ait cinq secondes au lieu de trois, puis montez-le pour qu'il n'en ait qu'une.
|
||
Trouvez le nombre que vous publieriez vraiment.
|
||
. *Déplacez le point de contrôle.* Changez le `goesTo` de `pit` en `"sunset"` et
|
||
jouez. Puis réfléchissez à ce qui se passe si un joueur meurt dans `arrive` --
|
||
et demandez-vous si la scène de mort devrait savoir d'où elle vient plutôt que
|
||
de se le faire dire.
|
||
. *Comptez ce que le joueur a vu.* Ajoutez à côté de `scenes` une table qui note
|
||
quels noms de scène ont été joués, et affichez leur nombre dans le coin. Vous
|
||
avez maintenant un jeu qui peut dire « vous avez trouvé trois des cinq fins ».
|
||
. *Cassez la carte.* Changez le `left` de `arrive` en `"cave"`, une scène qui
|
||
n'existe pas. Lancez, allez à gauche, et lisez ce que dit Lua. Cette erreur est
|
||
la plus courante dans un jeu piloté par les données, et connaître sa forme
|
||
fait gagner une heure.
|
||
|
||
[#_break_it_on_purpose_15]
|
||
=== Cassez-le exprès
|
||
|
||
Retirez le garde-fou de la ligne `asking`, pour qu'elle devienne :
|
||
|
||
[source,lua]
|
||
----
|
||
local asking = frame >= scene.ask
|
||
----
|
||
|
||
Lancez et allez à droite. La scène `arrive` se joue très bien, parce que
|
||
`arrive` a un `ask`. Puis la scène `pit` commence, qui n'en a pas, et le jeu
|
||
s'arrête avec ceci :
|
||
|
||
----
|
||
Error executing function 'onOverlayUpdate': movie/dragon.singe:68: attempt to compare nil with number
|
||
stack traceback:
|
||
[C]: in function 'error'
|
||
Singe/Framework.singe:603: in function 'onOverlayUpdate'
|
||
----
|
||
|
||
Lisez-le comme la leçon un vous l'a appris. Le fichier et la ligne viennent en
|
||
premier, et ils sont parfaitement justes : la ligne 68 est celle que vous venez
|
||
de modifier. La plainte, c'est le reste : vous avez demandé à Lua si un nombre
|
||
était plus grand que `nil`, et Lua n'en a aucune idée. `scene.ask` vaut `nil`
|
||
parce que l'enregistrement `pit` n'a pas de champ `ask`, et un champ qui n'est
|
||
pas là vaut `nil`.
|
||
|
||
Une partie de ce message mérite une explication, parce que c'est un mensonge
|
||
par omission. La fonction nommée est `onOverlayUpdate`, que vous n'avez pas
|
||
écrite. C'est celle du framework, celle dont la leçon quinze disait qu'elle
|
||
était installée pour faire tourner votre `singeMain`. L'erreur est passée par
|
||
elle, donc c'est le nom que le moteur avait sous la main, et le traceback en
|
||
dessous -- la liste des fonctions qui étaient en train de s'exécuter, la plus
|
||
intérieure en premier -- ne peut montrer que le framework qui fait suivre votre
|
||
erreur. Ignorez les deux. Le fichier et la ligne du haut sont à vous, et ce
|
||
sont eux qui disent vrai.
|
||
|
||
[#_what_you_learned_16]
|
||
=== Ce que vous avez appris
|
||
|
||
* Un jeu à embranchements est un ensemble de scènes, dont chacune nomme les
|
||
scènes qui viennent après elle.
|
||
* Gardez les scènes sous forme de données -- une table d'enregistrements -- et le
|
||
code qui les joue reste une seule fonction, quelle que soit la taille du jeu.
|
||
* Un enregistrement contient le nom de la scène suivante, pas la scène, et ce
|
||
nom est cherché quand on en a besoin.
|
||
* Un champ qui n'est pas dans un enregistrement vaut `nil`, ce qui n'est pas une
|
||
erreur, et le code qui lit des enregistrements doit s'y attendre.
|
||
* `scene[answer]` cherche le champ dont le nom est dans une variable ;
|
||
`scene.left` et `scene["left"]` sont le même champ.
|
||
* Réglez le minuteur sur la vidéo : un choix dure d'une image à une autre, donc
|
||
il ne peut jamais durer plus longtemps que son propre plan.
|
||
* Jetez tout ce sur quoi le joueur a appuyé avant que la question apparaisse.
|
||
* Mettez le disque en pause dès qu'une scène se termine, sinon il continue dans
|
||
les images de la scène suivante.
|
||
* Un champ absent qui tient lieu de faux -- `death` dans trois enregistrements
|
||
sur quatre -- garde la description courte et honnête.
|
||
* Comparer `nil` avec un nombre est une erreur, et `and` est la façon de
|
||
l'éviter.
|
||
|
||
[#_next_time_16]
|
||
=== La prochaine fois
|
||
|
||
Votre jeu demande gauche ou droite. Les machines d'arcade où ce genre de jeu a
|
||
grandi demandaient quelque chose de plus difficile : visez la chose à l'écran et
|
||
tirez-lui dessus. La leçon dix-sept parle des pistolets optiques -- l'endroit où
|
||
le joueur vise, ce que le jeu en fait, et le calibrage dont une vraie borne ne
|
||
peut pas se passer.
|