506 lines
20 KiB
Text
506 lines
20 KiB
Text
[#_lesson_14_playing_video]
|
||
== Leçon 14 : Lire de la vidéo
|
||
|
||
image::learn/14-video.png[La leçon terminée, 480]
|
||
|
||
Singe peut lire un film et faire tourner votre jeu à l'intérieur. L'image devient
|
||
le monde, et l'overlay sur lequel vous dessinez depuis la leçon un est posé
|
||
par-dessus. Dans cette leçon, vous allez lancer une vidéo, découvrir quelle
|
||
image est affichée, l'arrêter, et sauter à n'importe quel moment de votre choix.
|
||
|
||
Avant tout cela, une chose doit être dite clairement, parce qu'elle décide si
|
||
vous devez lire cette partie du livre ou non.
|
||
|
||
[#_a_game_plays_video_only_if_it_asks_to]
|
||
=== Un jeu ne lit de la vidéo que s'il le demande
|
||
|
||
Singe est un moteur de jeu. Tout ce que vous avez construit dans les première et
|
||
deuxième parties -- les sprites, le son, les collisions, le score, le dossier du
|
||
jeu -- c'est Singe tout entier, et rien de tout cela ne fait intervenir la
|
||
vidéo. Ce qu'ajoute la troisième partie est un supplément : un jeu peut demander
|
||
qu'un film soit lu derrière lui, et alors le film est le monde, au lieu d'un
|
||
canevas noir.
|
||
|
||
Rien d'autre ne change. `onOverlayUpdate` est toujours appelé soixante fois par
|
||
seconde. `overlayPrint` compte toujours en cellules de caractère. Vos sprites se
|
||
dessinent là où ils se dessinaient avant. Un jeu avec vidéo et un jeu sans vidéo
|
||
s'écrivent de la même façon, avec les mêmes fonctions, et partagent le même
|
||
livre.
|
||
|
||
Donc, si le jeu que vous voulez faire ne contient pas de vidéo, vous pouvez
|
||
refermer cette partie maintenant et passer à la quatrième. Rien dans la
|
||
quatrième partie ne dépend de quoi que ce soit dans la troisième, et les seules
|
||
leçons qui ont besoin de celle-ci sont les quatre qui la suivent.
|
||
|
||
Toujours là ? Bien.
|
||
|
||
[#_you_already_have_a_video]
|
||
=== Vous avez déjà une vidéo
|
||
|
||
Vous n'avez pas besoin d'en trouver une, d'en acheter une ou d'en fabriquer une.
|
||
La première fois que vous avez lancé Singe, à la leçon un, un dossier appelé
|
||
`Singe` est apparu dans votre dossier de travail. Ouvrez-le et regardez : parmi
|
||
les fichiers que le moteur y a déballés, il y a `menuBackground.mkv`. C'est la
|
||
toile de fond dont se sert le propre menu du moteur, elle fait 720 sur 480
|
||
pixels, et elle dure 420 images -- environ quatorze secondes.
|
||
|
||
C'est la vidéo de cette leçon et des deux suivantes.
|
||
|
||
Créez un dossier appelé `movie` dans votre dossier de travail, à côté des
|
||
dossiers des autres leçons, et dedans un fichier appelé `movie.singe`. C'est
|
||
toute la préparation qu'il y a à faire. Vous lancez Singe depuis le dossier de
|
||
travail, comme vous le faites depuis la leçon un, donc le dossier `Singe`, vidéo
|
||
comprise, est juste là à côté de votre jeu, et `Singe/menuBackground.mkv` est le
|
||
nom que vous donnerez au moteur dans un instant. Il n'y a rien à copier.
|
||
|
||
[#_type_this_in_2]
|
||
=== Tapez ceci
|
||
|
||
[source,lua]
|
||
----
|
||
dofile("Singe/Framework.singe")
|
||
|
||
|
||
function onOverlayUpdate()
|
||
overlayClear()
|
||
overlayPrint(2, 2, "Frame " .. discGetFrame())
|
||
return OVERLAY_UPDATED
|
||
end
|
||
|
||
|
||
discPlay()
|
||
----
|
||
|
||
Enregistrez-le.
|
||
|
||
[#_run_it_2]
|
||
=== Lancez-le
|
||
|
||
La vidéo ne fait pas partie de votre script, donc vous ne la chargez pas dans
|
||
votre script. Vous la donnez au moteur sur la ligne de commande, avec `-v` :
|
||
|
||
----
|
||
Singe -R -v Singe/menuBackground.mkv movie
|
||
----
|
||
|
||
`-v` est la forme courte de `--framefile`, et il nomme la vidéo que le jeu doit
|
||
lire. Le nom de votre script vient en dernier, comme toujours. `-R` est le même
|
||
`-R` que celui dont vous vous servez depuis la leçon un, et il relance toujours
|
||
le jeu quand vous enregistrez.
|
||
|
||
Demander, c'est tout l'intérêt de `-v`. Si vous déposez un fichier vidéo à côté
|
||
de votre script sans rien dire, le moteur le remarque, le dit dans la console,
|
||
et lance votre jeu sans lui. Un jeu n'est un jeu vidéo que lorsque quelqu'un le
|
||
dit.
|
||
|
||
Une fenêtre s'ouvre. Une boule de feu orange la remplit, un dragon se forme à
|
||
partir du feu, le mot `SINGE` apparaît en dessous, puis une grille magenta
|
||
s'éloigne en défilant sous un soleil orange. Dans le coin supérieur gauche,
|
||
par-dessus tout ça, un nombre compte.
|
||
|
||
Regardez-la jusqu'au bout. Puis regardez ce qui se passe à la fin.
|
||
|
||
[#_what_just_happened_10]
|
||
=== Ce qui vient de se passer
|
||
|
||
[source,lua]
|
||
----
|
||
discPlay()
|
||
----
|
||
|
||
Singe appelle la vidéo *le disque*, et chaque fonction qui la pilote commence
|
||
par `disc`. Le nom est historique : le moteur a commencé sa vie en faisant
|
||
tourner des jeux qui gardaient leur film sur un laserdisc, et le nom est resté
|
||
attaché à l'idée. C'est un fichier vidéo sur votre disque dur, et rien de plus.
|
||
|
||
Un jeu a au plus un disque, donc aucune des fonctions `disc` ne prend d'argument
|
||
pour dire lequel. Il n'y en a qu'un.
|
||
|
||
Cette ligne n'est dans aucune fonction. Elle est en bas du fichier, toute seule,
|
||
et elle s'exécute une fois quand Singe charge votre script -- avant le premier
|
||
`onOverlayUpdate`, avant tout le reste. Singe n'a pas de callback `onStartup`
|
||
parce qu'il n'en a pas besoin : un script Lua *est* une liste d'instructions, et
|
||
tout ce que vous écrivez hors d'une fonction se produit pendant la lecture du
|
||
fichier. Par convention, le code de démarrage va en bas, sous les fonctions.
|
||
|
||
Il vous faut `discPlay()` parce que le disque ne démarre pas tout seul. Quand le
|
||
jeu commence, le moteur gare le disque sur l'image un et le met en pause là.
|
||
Rien ne bouge tant que vous ne le demandez pas.
|
||
|
||
[source,lua]
|
||
----
|
||
overlayPrint(2, 2, "Frame " .. discGetFrame())
|
||
----
|
||
|
||
`discGetFrame` répond à une seule question : quelle image est affichée en ce
|
||
moment ? Il ne prend pas d'argument et renvoie un nombre.
|
||
|
||
Remarquez où ce nombre est dessiné. Colonne 2, ligne 2 -- exactement là où
|
||
`Hello!` est allé à la leçon un, et il tombe au même endroit à l'écran.
|
||
L'overlay sur lequel vous dessinez fait la moitié de la taille de la vidéo dans
|
||
chaque sens, ce qui, pour une vidéo de 720 sur 480, donne 360 sur 240,
|
||
c'est-à-dire exactement la taille à laquelle vous dessinez depuis treize
|
||
leçons. Vos coordonnées n'ont pas bougé. Si un jour vous lisez une vidéo
|
||
d'une autre taille, l'overlay change de taille avec elle, et
|
||
`overlaySetResolution` le règle à la taille que vous voulez ; l'entrée de la
|
||
Référence donne les détails.
|
||
|
||
C'est toute l'idée de l'overlay. La vidéo est en dessous. Votre dessin est
|
||
par-dessus, et partout où vous n'avez pas dessiné, le film transparaît. C'est
|
||
pourquoi `overlayClear` compte encore plus maintenant qu'avant : effacez
|
||
l'overlay et c'est le film que vous regardez.
|
||
|
||
[#_a_frame_is_an_address]
|
||
=== Une image est une adresse
|
||
|
||
Le nombre que renvoie `discGetFrame` est un numéro d'*image*. Une image, c'est
|
||
une seule vue fixe. Cette vidéo en contient 420 et en montre environ trente par
|
||
seconde, donc elle dure environ quatorze secondes.
|
||
|
||
Les images sont comptées à partir de zéro, donc cette vidéo a les images 0 à
|
||
419. Le nombre d'images et le dernier numéro ne sont pas les mêmes, ce qui
|
||
piège tout le monde une fois. `discGetFrameCount` vous donne le nombre --
|
||
420 -- et l'image la plus haute que vous pouvez demander est un de moins.
|
||
|
||
Ce qui compte avec un numéro d'image, c'est qu'il ne change jamais et ne dérive
|
||
jamais. L'image 110 est le moment où le dragon crache du feu, dans cette copie du
|
||
fichier, sur cette machine, aujourd'hui comme l'an prochain. Les secondes
|
||
dérivent, parce qu'une vidéo ne se lit pas toujours exactement à la cadence que
|
||
vous croyez. Un numéro d'image est l'adresse d'un moment, et les adresses sont
|
||
ce dont est fait un jeu vidéo : vous notez l'image où le dragon apparaît, et à
|
||
partir de là vous pouvez envoyer le film à cet endroit quand vous voulez.
|
||
|
||
Trouver ces numéros est un travail que vous faites une fois par vidéo, et le
|
||
script que vous avez écrit est l'outil qu'il faut : lancez le film, regardez le
|
||
compteur, notez les images où il se passe quelque chose. Tout ce que contient la
|
||
leçon seize repose sur une liste de numéros trouvés exactement de cette façon.
|
||
|
||
[#_what_happened_at_the_end]
|
||
=== Ce qui s'est passé à la fin
|
||
|
||
Le compteur a atteint 419, puis il est revenu à 0 et le feu était de retour.
|
||
|
||
Quand le disque arrive au bout, le moteur le relance depuis l'image zéro. Il ne
|
||
s'arrête pas, et il ne vous prévient pas. Un vrai jeu ne le laisse jamais
|
||
arriver là : il surveille le numéro d'image et envoie le disque ailleurs avant
|
||
que la fin n'arrive. Oublier de le faire, voilà pourquoi un jeu vidéo à moitié
|
||
fini se met soudain à jouer sa propre séquence de titre au milieu d'un combat.
|
||
|
||
[#_stopping_jumping_and_asking]
|
||
=== Arrêter, sauter et demander
|
||
|
||
Ajoutez cette fonction au-dessus de `onOverlayUpdate` :
|
||
|
||
[source,lua]
|
||
----
|
||
function onInputPressed(what)
|
||
if what == SWITCH_BUTTON1 then
|
||
if discGetState() == DISC_PLAYING then
|
||
discPause()
|
||
else
|
||
discPlay()
|
||
end
|
||
elseif what == SWITCH_LEFT then
|
||
discSearch(0)
|
||
elseif what == SWITCH_RIGHT then
|
||
discSkipForward(30)
|
||
elseif what == SWITCH_START1 then
|
||
discStop()
|
||
end
|
||
end
|
||
----
|
||
|
||
Enregistrez, et essayez chacune. Espace lit et met en pause. Gauche revient au
|
||
début. Droite saute environ une seconde plus loin. La touche `1` arrête
|
||
complètement le disque, et ce que vous obtenez est un écran bleu -- la couleur
|
||
qu'affichait un lecteur de laserdisc à l'arrêt, conservée parce qu'énormément de
|
||
jeux dessinaient leur tableau des scores dessus.
|
||
|
||
Cinq nouveaux appels, et ils se divisent en deux sortes.
|
||
|
||
*Demander.* `discGetState` renvoie l'un de quatre noms que le moteur a déjà
|
||
définis pour vous : `DISC_PLAYING`, `DISC_PAUSED`, `DISC_STOPPED` et
|
||
`DISC_EJECTED`. Ils sont comme `OVERLAY_UPDATED` -- pas de guillemets, parce que
|
||
ce sont des noms de valeurs et non du texte. Comparez avec `==`, comme vous avez
|
||
comparé des nombres à la leçon trois.
|
||
|
||
*Ordonner.* `discPlay` démarre ou reprend. `discPause` fige l'image affichée et
|
||
la laisse à l'écran. `discStop` est le plus lourd : l'image est remplacée par
|
||
l'écran bleu, `discGetFrame` répond `0` jusqu'à ce que le disque reparte, et
|
||
`discPause` et les appels de saut sont ignorés tant qu'il est arrêté. N'importe
|
||
lequel de `discPlay`, `discSearch` ou `discSkipToFrame` le fait repartir.
|
||
|
||
Deux appels envoient le disque à une image, et la différence entre eux est la
|
||
seule chose de cette leçon qui mérite d'être apprise par cœur :
|
||
|
||
* `discSearch(frame)` y va et s'y *met en pause*.
|
||
* `discSkipToFrame(frame)` y va et *lit* à partir de là.
|
||
|
||
Search, c'est pour vous garer sur une image et attendre. Skip, c'est pour
|
||
qu'une scène commence. `discSkipForward(30)` et `discSkipBackward(30)` sont la
|
||
même idée, mesurée depuis l'endroit où vous êtes plutôt que depuis le début, et
|
||
ils laissent le disque en lecture s'il lisait et en pause s'il était en pause.
|
||
|
||
Il existe aussi `discPauseAtFrame`, qui fait exactement ce que fait
|
||
`discSearch`, sous un nom plus ancien. La Référence le marque comme alias hérité.
|
||
Vous le verrez dans les scripts des autres ; écrivez `discSearch` dans les
|
||
vôtres.
|
||
|
||
[#_waiting_is_harder_than_it_sounds]
|
||
=== Attendre est plus difficile qu'il n'y paraît
|
||
|
||
Voici la phrase dont est fait chaque jeu vidéo :
|
||
|
||
____
|
||
Lire de l'image 0 à l'image 120, puis poser une question au joueur.
|
||
____
|
||
|
||
Essayez de l'écrire. Vous ne pouvez pas écrire « lire, puis attendre, puis
|
||
demander », parce que vous n'avez nulle part où l'écrire. Vous avez
|
||
`onOverlayUpdate`, que Singe appelle soixante fois par seconde et qui doit
|
||
rendre la main immédiatement, à chaque fois. Une fonction qui resterait là à
|
||
attendre arrêterait net le moteur : plus de dessin, plus de commandes, plus de
|
||
vidéo.
|
||
|
||
Donc vous ne pouvez pas dire « puis ». Vous devez transformer « puis » en une
|
||
variable qui se souvient d'où vous en êtes, et la vérifier à chacun de ces
|
||
soixante appels :
|
||
|
||
[source,lua]
|
||
----
|
||
local asking = false
|
||
|
||
|
||
function onOverlayUpdate()
|
||
overlayClear()
|
||
if not asking and discGetFrame() >= 120 then
|
||
discPause()
|
||
asking = true
|
||
end
|
||
if asking then
|
||
overlayPrint(2, 4, "Left or right?")
|
||
end
|
||
return OVERLAY_UPDATED
|
||
end
|
||
----
|
||
|
||
Ça marche. Lisez-le et vous voyez que ça marche. Mais remarquez ce que ça a
|
||
coûté : une variable, dont le seul travail est de se souvenir de l'endroit de la
|
||
phrase où vous en êtes, et deux instructions `if` pour la garder honnête.
|
||
|
||
Maintenant, ajoutez le reste de la scène. Le joueur répond, donc l'une de deux
|
||
choses se joue ensuite. Chacune finit quelque part et pose une autre question.
|
||
Un modeste jeu à embranchements a quarante de ces moments, et chacun veut sa
|
||
propre variable et son propre `if`. Vous finissez avec un script où l'histoire
|
||
n'est écrite nulle part -- elle est éparpillée entre une douzaine de drapeaux,
|
||
et pour savoir ce qui se passe après le dragon, il faut tous les lire.
|
||
|
||
Gardez cette idée en tête. La leçon quinze n'est rien d'autre que le remède.
|
||
|
||
[#_when_there_is_no_video]
|
||
=== Quand il n'y a pas de vidéo
|
||
|
||
Relancez votre script de la façon simple, sans `-v` :
|
||
|
||
----
|
||
Singe -R movie
|
||
----
|
||
|
||
Rien ne plante. Vous obtenez une fenêtre noire avec `Frame 0` dans le coin.
|
||
|
||
Ça vaut la peine de le comprendre, parce que c'est la moitié rassurante de la
|
||
promesse faite au début de cette leçon. Un jeu sans disque exécute quand même
|
||
chaque appel `disc` que vous faites ; ils ne font simplement rien d'utile. Le
|
||
moteur documente exactement ce qu'ils répondent, et ça ne change jamais :
|
||
`discGetFrame` et `discGetFrameCount` renvoient `0`, `discGetState` renvoie
|
||
`DISC_EJECTED`, `discGetWidth` et `discGetHeight` renvoient la taille du
|
||
canevas, et `discPlay`, `discPause`, `discSearch` et les autres sont notés dans
|
||
le journal et, pour le reste, ignorés.
|
||
|
||
Vous ne pouvez pas casser un script en appelant une fonction du disque quand il
|
||
n'y a pas de disque.
|
||
|
||
Quand un script a besoin de *savoir*, il interroge `SINGE_DISC`. C'est un autre
|
||
nom que le moteur définit avant que votre script s'exécute, et il vaut `true`
|
||
quand le jeu a une vidéo et `false` quand le monde est un simple canevas. C'est
|
||
la bonne façon d'écrire un jeu qui peut tourner dans les deux cas :
|
||
|
||
[source,lua]
|
||
----
|
||
if not SINGE_DISC then
|
||
overlayPrint(2, 2, "This game has no video.")
|
||
overlayPrint(2, 4, "Run it again with -v Singe/menuBackground.mkv")
|
||
return OVERLAY_UPDATED
|
||
end
|
||
----
|
||
|
||
`not` retourne une valeur vraie ou fausse, comme à la leçon trois.
|
||
|
||
[#_the_whole_thing_7]
|
||
=== Le tout
|
||
|
||
Mis bout à bout, avec une petite fonction qui transforme l'état en mots et une
|
||
image à laquelle s'arrêter, voici le script terminé. C'est un chercheur
|
||
d'images : l'outil dont vous vous servirez chaque fois que vous rencontrerez une
|
||
nouvelle vidéo.
|
||
|
||
[source,lua]
|
||
----
|
||
dofile("Singe/Framework.singe")
|
||
|
||
local stopAt = 400
|
||
local held = false
|
||
|
||
|
||
local function stateName()
|
||
local state = discGetState()
|
||
|
||
if state == DISC_PLAYING then
|
||
return "playing"
|
||
elseif state == DISC_PAUSED then
|
||
return "paused"
|
||
elseif state == DISC_STOPPED then
|
||
return "stopped"
|
||
end
|
||
return "not there"
|
||
end
|
||
|
||
|
||
function onInputPressed(what)
|
||
if what == SWITCH_BUTTON1 then
|
||
if discGetState() == DISC_PLAYING then
|
||
discPause()
|
||
else
|
||
discPlay()
|
||
end
|
||
elseif what == SWITCH_LEFT then
|
||
discSearch(0)
|
||
held = false
|
||
elseif what == SWITCH_RIGHT then
|
||
discSkipForward(30)
|
||
elseif what == SWITCH_START1 then
|
||
discStop()
|
||
held = false
|
||
end
|
||
end
|
||
|
||
|
||
function onOverlayUpdate()
|
||
local frame = discGetFrame()
|
||
|
||
overlayClear()
|
||
if not SINGE_DISC then
|
||
overlayPrint(2, 2, "This game has no video.")
|
||
overlayPrint(2, 4, "Run it again with -v Singe/menuBackground.mkv")
|
||
return OVERLAY_UPDATED
|
||
end
|
||
|
||
if not held and frame >= stopAt then
|
||
discPause()
|
||
held = true
|
||
end
|
||
|
||
overlayPrint(2, 2, "Frame " .. frame .. " of " .. discGetFrameCount())
|
||
overlayPrint(2, 4, "The disc is " .. stateName() .. ".")
|
||
overlayPrint(2, 6, "Space plays and pauses. Left rewinds.")
|
||
overlayPrint(2, 7, "Right jumps on a second. 1 stops the disc.")
|
||
if held then
|
||
overlayPrint(2, 9, "Holding at frame " .. stopAt .. ".")
|
||
end
|
||
|
||
return OVERLAY_UPDATED
|
||
end
|
||
|
||
|
||
discPlay()
|
||
----
|
||
|
||
`held` est le même genre de variable que `asking` plus haut, et il est là pour
|
||
une raison que vous pouvez vérifier : retirez-le, laissez `if frame >= stopAt then discPause()
|
||
end`, puis essayez d'appuyer sur espace pour continuer au-delà de l'image 400.
|
||
Impossible. Le disque reprend pendant un soixantième de seconde et le
|
||
`onOverlayUpdate` suivant le remet en pause, pour toujours. `held`, c'est la
|
||
façon de dire « j'ai déjà fait ça une fois ».
|
||
|
||
[#_try_it_14]
|
||
=== Essayez
|
||
|
||
. *Trouvez le dragon.* Lancez-le, regardez le compteur, et notez l'image où le
|
||
dragon apparaît pour la première fois, l'image où il crache du feu, et l'image
|
||
où la grille envahit l'écran. Vous voudrez ces trois numéros à la leçon seize.
|
||
. *Avancez pas à pas.* Changez `discSkipForward(30)` en `discSkipForward(1)` et
|
||
gardez la flèche droite enfoncée. Changez maintenant `stopAt` pour une image
|
||
au milieu du dragon, et servez-vous de `discSearch` et de la touche fléchée
|
||
pour tomber exactement dessus.
|
||
. *Faites-le boucler.* Changez l'arrêt pour qu'au lieu de mettre en pause à
|
||
l'image 400, il appelle `discSkipToFrame(0)`. Vous avez construit un mode
|
||
attraction : une scène qui se joue sans fin jusqu'à ce que quelqu'un appuie
|
||
sur un bouton.
|
||
. *Demandez une image qui n'existe pas.* Changez la ligne `SWITCH_RIGHT` en
|
||
`discSkipToFrame(100000)`. Ça ne plante pas. Lisez l'entrée de la Référence
|
||
pour `discSkipToFrame` et trouvez la phrase qui dit pourquoi.
|
||
. *Jouez-le sans la vidéo.* Lancez `Singe -R movie` puis appuyez sur toutes les
|
||
touches. Rien de ce que vous pressez ne peut le casser.
|
||
|
||
[#_break_it_on_purpose_13]
|
||
=== Cassez-le exprès
|
||
|
||
L'erreur qui coûte le plus de temps ici n'est pas une erreur de Lua. Lancez
|
||
ceci :
|
||
|
||
----
|
||
Singe -R -v menuBackground.mkv movie
|
||
----
|
||
|
||
Le chemin est faux -- il part du dossier de travail, et la vidéo y est dans le
|
||
dossier `Singe`, pas en vrac -- et Singe refuse de démarrer. Au lieu de ça, il affiche toute sa liste
|
||
d'options de ligne de commande, ce qui est alarmant la première fois, puis ceci,
|
||
tout à la fin :
|
||
|
||
----
|
||
Error: Unable to locate video.
|
||
----
|
||
|
||
Un mur d'options comme celui-là veut toujours dire la même chose : le moteur
|
||
n'est jamais allé jusqu'à votre jeu. Ignorez le mur et lisez la dernière ligne.
|
||
|
||
Celle-là est facile. Sa version la moins utile, c'est de lancer sans aucun `-v`
|
||
et d'oublier que vous l'avez fait. Il n'y a pas d'erreur, parce qu'un jeu sans
|
||
vidéo est un jeu parfaitement valable. Vous obtenez un écran noir et un compteur
|
||
bloqué sur `0`, et vous cherchez le bogue dans votre script pendant vingt
|
||
minutes.
|
||
|
||
C'est à ça que sert le bloc `SINGE_DISC` du script terminé. Trois lignes, et le
|
||
jeu vous dit ce qui ne va pas au lieu de bouder. Mettez une vérification comme
|
||
celle-là dans chaque script que vous écrivez qui a besoin d'une vidéo.
|
||
|
||
[#_what_you_learned_14]
|
||
=== Ce que vous avez appris
|
||
|
||
* Un jeu ne lit de la vidéo que s'il le demande, et tout le reste de Singe marche
|
||
de la même façon dans les deux cas.
|
||
* Singe appelle la vidéo le disque. Un jeu en a un, et les fonctions `disc` ne
|
||
prennent pas de handle parce qu'il n'y a rien à choisir.
|
||
* La vidéo se donne sur la ligne de commande avec `-v`, pas dans le script.
|
||
* L'overlay est dessiné par-dessus l'image, et le film transparaît partout où
|
||
vous n'avez pas dessiné.
|
||
* Une image est une vue fixe, comptée à partir de zéro, et un numéro d'image est
|
||
l'adresse d'un moment.
|
||
* `discGetFrame` demande où en est le disque ; `discGetFrameCount` dit combien
|
||
il y a d'images ; `discGetState` dit ce que fait le disque.
|
||
* `discPlay`, `discPause` et `discStop` le pilotent. `discSearch` va à une image
|
||
et se met en pause ; `discSkipToFrame` va à une image et lit.
|
||
* Un disque qui arrive au bout repart de l'image zéro sans vous prévenir.
|
||
* Sans disque, chaque appel `disc` est inoffensif, et `SINGE_DISC` est la façon
|
||
dont un script sait dans quel genre de jeu il se trouve.
|
||
* Vous ne pouvez pas attendre dans `onOverlayUpdate`, donc « puis » doit
|
||
s'écrire sous la forme d'une variable.
|
||
|
||
[#_next_time_14]
|
||
=== La prochaine fois
|
||
|
||
Ce dernier point est le point sensible. Un jeu vidéo est une liste
|
||
d'étapes -- lis ceci, demande cela, lis l'autre -- et vous venez de voir que le
|
||
modèle à callbacks ne sait pas dire « puis » sans un drapeau et un `if` pour
|
||
chaque étape. La leçon quinze vous montre l'autre façon dont Singe vous laisse
|
||
écrire un script, où « puis » est une nouvelle ligne.
|