Lua est un langage de script et à ce titre sera retrouvé la plupart du temps inclus dans une application comme CraftStudio ou bien des jeux.
Le Lua permet ainsi à des utilisateurs extérieurs (vous, qui voulez créer votre jeu avec CraftStudio ou un moddeur qui voudrait un jeu) d'agir sur l'application en utilisant une partie du code source de celle-ci tout en étant dans un environnement contrôlé par le développeur.
La simplicité, la légèreté et la grande compatibilité du Lua avec de nombreux systèmes en font un langage très utilisé dans le domaine des jeux vidéos. Il est en effet utilisé par -ou utilisable dans- :
le Garry's Mod
le Cry Engine (le moteur des jeux Crysis, entre autre)
les jeux Eufloria, Don't Starve, Natural Selection 2, World of Warcraft (pour ne citer qu'eux)
le développement sur PSP et DS et bien sur CraftStudio !
Lua/Les boucles
While
while [condition] do [segment de code] end
While en anglais signifie "tant que". La boucle lit et continue de lire le segment de code écrit entre les mots clés do et end tant que la condition est vraie. Lorsque le programme arrive au mot-clé while, il évalue la condition. Si elle est vraie, il rentre dans la boucle, lit le code jusqu'au mot clé end puis reviens au mot clé whilepuis réévalue la condition et ainsi de suite... Le programme ne s’arrêtera pas de boucler tant que la condition reste vraie. Lorsqu'elle deviens fausse, le programme termine de lire le segment de code, puis continue à la suite de la boucle lorsqu'il se rend compte que la condition est devenue fausse.
Exemple :
print( "Début de la boucle" ) i = 1
while i <= 10 do -- "tant que la valeur de i est inférieure ou égale à 10"
print(i) i = i + 1 end
print( "Fin de la boucle" ) -- ce code vous affiche : -- Début de la boucle -- 1
-- 2 -- ... -- 9
-- 10
-- Fin de la boucle
Ici la condition de la boucle est que la variable i soit strictement inférieure à 11. Mais elle est incrémentée dans la boucle, c'est à dire que quelque soit sa valeur, on lui ajoute 1 à chaque tour. i devient égale à 11 à la fin du tour où elle a débuté avec la valeur 10. À ce moment là, la condition de la boucle n'est plus vrai et le programme continue de lire le script à la suite de la boucle.
Repeat
repeat
[segment de code] until [condition]
Repeat Until signifie "répète jusqu'à ce que". C'est un peu l'inverse d'une boucle While. Avec cette boucle, le segment de code est lut toujours au moins une fois et jusqu'à ce que la condition soit vraie. C'est à dire que à l'inverse de la boucle While le programme va boucler tant que la condition est fausse. L'autre point important avec cette boucle c'est que comme la condition est située après le segment de code, celui-ci sera toujours lu au moins une fois, même si la condition est vraie avant même de commencer à lire la boucle.
print( "Début de la boucle" ) i = 1
repeat print(i) i = i + 1
until i > 10 -- vous noterez que i > 10 est l'inverse i <= 10 (la condition utilisée plus haut avec while)
print( "Fin de la boucle" )
-- ce code vous affiche la même chose qu'avec la boucle while
Break, casser une boucle
Il est possible de sortir d'une boucle et de la stopper à n'importe quel moment grâce au mot clé break. Lorsque le programme le lit, il s’arrête immédiatement de lire la boucle, sans finir le segment de code ni réévaluer la condition.
print( "Début de la boucle" ) i = 1 while i < 11 do print(i) if i == 5 then break end end
print( "Fin de la boucle" ) -- ce code vous affiche : -- Début de la boucle -- 1 -- 2 -- 3 -- 4 -- 5
-- Fin de la boucle
Break fonctione avec toutes les boucles : While, Repeat et la dernière boucle, For.
For
For numérique
Dans les exemples ci-dessus, nous avons créé et incrémenté manuellement une variable puis nous avons vérifié sa valeur pour déterminer si la boucle devait continuer à être lue. Cette version de la boucle For permet de faire exactement ça, mais de manière plus condensée.
for variable = [début], [fin] do [segment de code]
end
-- ou bien :
for variable = [début], [fin], [pas] do [segment de code]
end
[début], [fin] et [pas] sont à remplacer par des nombres. Le pas est la valeur qui est ajoutée à la variable à chaque fin de tour de boucle. Si il n'est pas indiqué, il vaut par défaut +1. Exemple : for i = 1, 10 do print( i ) end -- vous affiche : -- 1 -- 2 -- ... -- 9 -- 10 for i = 1, 10, 2 do print( i ) end -- vous affiche : -- 1 -- 3 -- 5 -- 7 -- 9
La valeur de début peut être supérieure à la valeur de fin, mais dans ce cas le pas doit obligatoirement être indiqué et être négatif (afin que la boucle compte à rebours). for i = 10, 1, -2 do print( i ) end -- vous affiche : -- 10 -- 8 -- 6 -- 4 -- 2
Les boucles For numériques peuvent déjà être utilisées pour parcourir des tableaux numérotés assez facilement. Il y a juste deux "trucs" à se rappeler du chapitre sur les tableaux. D'une part que la taille d'un tableau, qui correspond justement au dernier l'index de celui-ci se récupère en faisant précéder le nom du tableau d'un croisillon. Et d'autre part, qu'il est possible d'écrire une variable entre les crochets lors de la lecture dans le tableau; c'est alors la valeur de la variable qui est utilisée comme clé.
tableau = { "un", "deux", "trois" }
for i = 1, #tableau do -- ici, #tableau vaut 3 print( tableau[i] )
-- i va successivement prendre la valeur 1 puis 2 puis 3 -- donc on va successivement afficher la valeur de tableau[1] puis tableau[2] puis tableau[3]
end
-- vous affiche : -- un
-- deux -- trois
Vous pouvez rajouter des valeurs dans le tableau et vous verrez qu'elles s'afficheront sans devoir quoi que ce soit au code de la boucle.
L'inconvénient est que ce type de boucle ne peut pas (ou pas correctement) afficher le contenu des tableaux lorsque les clés ne sont pas numériques (ou sont numériques mais pas continguës).
C'est précisément à cela que sert la boucle For spécialisée dans les tableaux.
For pour les tableaux
Là encore il y aura deux utilisations légèrement différentes, l'une pour les tableaux numérotés dont les clés sont contiguës, et l'autre pour n'importe quels tableaux. for cle, valeur in ipairs( [tableauNumérique] ) do
[code] end
-- ou bien
for cle, valeur in pairs( [tableau] ) do [code]
end
La différence entre les deux est l'utilisation des itérateurs ipairs() ou pairs(). Sachez juste que ce sont des fonctions qui vont "présenter" le contenus du tableau à la boucle. Le tableau à parcourir doit être placé entre les parenthèses. Les
variables cle et valeur peuvent être nommées autrement mais la boucle s'attend à trouver deux variables séparées par une virgule entre les mots clés for et in. Le fonctionnement de la boucle est très simple : à chaque tour, les
variables cle et valeur sont remplies respectivement avec la clé et la valeur de l'entrée (de la "ligne") du tableau qui est actuellement lue par la boucle.
ipairs()
C'est l'itérateur spécialisé pour les tableaux numérotés. Le résultat est exactement le même qu'avec la boucle for numérique comme dans le dernier exemple.
t = { "un", "deux", "trois" } for cle, valeur in ipairs(t) do print( cle, valeur )
-- souvenez vous que la fonction print() peut afficher plusieurs valeurs à la fois
-- dans l'appel (ci-dessus), les variables sont séparées par des virgules
-- dans la fenètre "Runtime Report", les valeurs seront séparées par un point d'interrogation.
end
-- vous affiche : -- 1?un
-- 2?deux -- 3?trois
Mais ici aussi, la boucle s’arrête lorsqu'elle rencontre la première clé qui n'est pas associée à une valeur, même si d'autre clés supérieure existent. La seule différence à l'usage entre cette boucle et le dernier exemple de boucle numérique est la manière dont on accède à la clé et à la valeur.
-- les deux boucles ci-dessous parcourent les tableaux exactement de la même manière
for i = 1, #tableau do print( i, tableau[i] ) end
for cle, valeur in ipairs(tableau) do print( cle, valeur )
end
pairs()
Cet itérateur fait dans la simplicité : il parcourt toutes les clés du tableau, quelque soit leur type.
Exemple :
tableau = { [{}] = "tableau", "un", deux = "deux", [3] = "trois", }
for cle, valeur in pairs(tableau) do print( cle, valeur )
end
-- vous affiche quelque chose comme : -- 1?un
-- 3?trois
-- table: 051329D0?tableau -- deux?deux
Conclusion
Les boucles permettent de lire plusieurs fois un segment de code selon une condition. La boucle For est particulièrement adaptée pour parcourir un tableau et exposer toutes ses entrées (quelque soit leur nombre) au même segment de code.
Récapitulatif des boucles : -- while et repeat
while [tant que condition est vraie] do [code]
end repeat [code]
until [tant que condition est fausse]
-- boucle For numérotée pour itérer facilement sur un nombre for variable = [début], [fin], [pas] do
[code] end
-- ou bien (spécifier le pas est optionnel) :
for variable = [début], [fin] do -- le pas est ici égal à +1 [code]
end
-- utilisation pour parcourir un tableau numéroté : for variable = 1, #tableau do
-- 'variable' est la clé
-- 'tableau[variable]' est la valeur end
-- boucle For avec itérateur pour parcourir un tableau : -- ipairs() pour les tableaux numérotés
for cle, valeur in ipairs( [tableauNuméroté] ) do [code]
end
-- pairs() pour parcourir n'importe quelle clé for cle, valeur in pairs( [tableau] ) do
[code] end
Les fonctions
Les fonctions sont un des éléments fondamentaux des langages de programmation. Mais c'est aussi une notion qui n'est pas forcément facile à comprendre pour des débutants, n'hésitez donc pas à relire le chapitre autant de fois que nécessaire. Je vais commencer par faire deux analogies qui peuvent vous aider à comprendre ce qu'est une fonction :
Une fonction c'est un peu comme un recette de cuisine : il y a des ingrédients au début, sur lesquels on travaille en les transformant afin de produire quelque chose à la fin. Une deuxième bonne analogie est celle de l'usine : tout comme une fonction, une usine créé
un produit finis (ce qui sort de l'usine, qui y a été construit) à partir de matières premières (ce qui y rentre, parfois dans un état très différent de celui dans lequel il sera utilisé). L'usine transforme et assemble les matières premières afin de construire le produit finis. Sinon, en terme informatique voici comment l'on peut décrire une fonction :
Une fonction est un segment de code qui porte un nom et peut être exécuté à volonté et sur demande lorsqu'elle est appelée par son nom.
Le code qui appelle une fonction peut optionnellement lui transmettre des données lors de l'appel. Une fonction peut donc recevoir des données lors de son appel via ses arguments.
Une fonction peut optionnellement retourner des valeurs, c'est à dire que le code ayant appelée une fonction peut recevoir ces valeurs en retour.
D'une manière générale, les fonctions servent à réutiliser un segment de code en
modifiant éventuellement son comportement grâce aux données d'entrée -les arguments- et éventuellement à récupérer le fruit de son travail via les valeurs qu'elle peut retourner.
Un segment de code qui porte un nom
Déclarer (créer) une fonction utilise le mot clé function ainsi que deux parenthèses : function [le nom de la fonction]()
[corps de la fonction] end
Rappelez-vous qu'en Lua, les fonctions ne sont que des variables comme les autres, mais de type "function". On peut donc également déclarer une fonction de la manière suivante (exactement comme lorsque l'on donne une valeur à une variable) :
[le nom de la fonction] = function() [corps de la fonction]
end
Néanmoins, j'utiliserais toujours la première notation dans les exemples de ce chapitre. Copiez/collez la fonction suivante dans votre script, nous allons l'utiliser et la tout au long du chapitre :
function Multiplier() nombre = 5
facteur = 2
resultat = nombre * facteur print( resultat )
end
-- cette fonction multiplie simplement un nombre par un facteur et affiche le résultat
Appeler une fonction
Pour appeler une fonction, il suffit d'écrire son nom suivit des parenthèses : Multiplier()
-- cela vous affiche : -- 10
Comme dit dans l'introduction, le code qui est contenu dans la fonction "Multiplier" est exécuté lorsque celle-ci est appelée. C'est comme si le programme remplaçait dans votre script "Multiplier()" par le code qu'il trouve dans la fonction.
Vous voyez ici l'un des avantages à utiliser des fonctions : en un seul appel à notre fonction (qui fait 12 caractères), nous avons fait appel au travail nécessitant normalement 80 caractères. C'est en quelque sorte un raccourcit qui vous permet de remplacer tout un segment de code par un seul mot (le nom de la fonction).
L’intérêt peut ici paraitre limité puisque notre fonction ne fait pas encore beaucoup de choses mais lorsqu'elles font plusieurs dizaines voire centaines de lignes, on est content lorsque l'on n'a pas à tout réécrire plusieurs fois.
Les arguments
La vraie magie des fonctions est que le code qu'elle contient n'est pas statique. On peut transmettre des variables aux fonctions lorsqu'elles sont appelées et ainsi faire varier leur comportement ou bien rendre le même code utilisable dans différentes occasions.
Prenez notre fonction Multiplier : actuellement elle ne sert vraiment à rien puisqu'elle ne fait que multiplier 5 par 2. Voyons plutôt comment l'utiliser pour multiplier n'importe quel nombre par n'importe quel facteur.
Manipuler les arguments n'est pas difficile : il suffit de les écrire entre les parenthèses en les séparant par des virgules si il y en a plusieurs, tant lors de l'appel que lors de la définition de la fonction. Exemple :
-- appel de la fonction avec deux arguments (dont les valeurs sont "un" et "deux")
maVariable = "deux"
MaFonction("un", maVariable) -- définition
function MaFonction(argument1, argument2)
-- 'argument1' est une variable (dont vous choisissez le nom) qui existe dans la fonction uniquement et qui prend la première valeur passée à la fonction lors de l'appel (ici, c'est "un")
-- 'argument2' prend la deuxième valeur passée à la fonction lors de l'appel ("deux")
end
Le nombre d'argument n'est pas limité. Si vous passez lors de l'appel moins d'arguments que la fonction n'est capable d'en recevoir, ceux pour lesquels aucune valeur n'aura été passée vaudront simplement nil dans la fonction. A l'inverse, si vous passez lors de l'appel plus d'arguments que la fonction n'est capable d'en recevoir, les arguments en trop seront simplement ignorés. Exemple :
MaFonction("un")
function MaFonction(argument1, argument2) -- argument1 vaut "un"
-- argument2 vaut nil end
-- ou à l'inverse :
function MaFonction(argument1, argument2) -- argument1 vaut "un"
-- argument2 vaut "deux"
-- la valeur "trois" est juste ignorée end
Mettons ça en pratique avec notre fonction Mutiplier : fonction Multiplier(nombre, facteur)
resultat = nombre * facteur print(resultat)
end
Multiplier(4, 3) -- affiche 12 Multiplier(8, 8) -- affiche 64
Et c'est tout. Grâce aux arguments, nous avons transformé une fonction statique et inutile en quelque chose d'un peu plus flexible (bien que dans cet exemple, l'utilité de la fonction reste toujours limitée).
Nombre variable d'argument
Il est possible de dire à une fonction qu'elle est susceptible de recevoir un nombre variable d'arguments. Il suffit d'écrire à la place du nom des variables trois point les uns à la suite des autres :
function MaFonction(...) --
end
-- ou bien
function MaFonction(argument1, argument2, ...)
-- les trois points peuvent être précédés d'autant d'arguments que vous le souhaitez
end
Dans ce cas, il existe automatiquement une variable arg à l'intérieur de la fonction qui est une table contenant les arguments, quelque soit leur nombre. Démonstration avec cette fonction :
function AfficheArguments(...)
-- rappelez vous du chapitre sur les boucles, -- ici on itère simplement sur la variable arg -- et affiche les valeurs qu'elle contient
-- donc les les arguments que la fonction a reçut for i, argument in ipairs(arg) do
print(argument) end end AfficheArguments("un", 2) -- affiche : -- un -- 2
AfficheArguments(1, "deux", true, {}) -- affiche :
-- 1 -- deux -- true
Retourner des valeurs
Retourner une valeur permet de récupérer le fruit du travail de la fonction. La fonction donne la valeur au code qui l'a appelée afin que celui-ci puisse continuer son
cheminement avec l'information dont il avait besoin.
Pour cela cela il suffit d'utiliser dans la fonction le mot clé return suivit de la valeur (ou de la variable) à retourner. Exemple :
fonction Multiplier(nombre, facteur) resultat = nombre * facteur
return resultat -- "print(resultat)" a été remplacé par "return resultat"
end
Multiplier(5, 3) -- n'affiche rien puisque la fonction ne fait plus que retourner le résultat, au lieu de l'afficher comme avant resultat = Multiplier(5, 3) -- ici la valeur retournée par la fonction est mise dans la variable resultat
print(resultat) -- affiche 15
Stopper la fonction
Le mot clé return a aussi pour effet d'arrêter l'exécution de la fonction quelque soit son emplacement dans le code de celle-ci (en plus d'éventuellement retourner la ou les valeurs qui le suivent).
Un petit exemple pour vous démontrer l'effet du sur l'exécution de la fonction : function Compte()
for i=1, 10 do print(i) end
print("J'ai terminé de compter.") end
Compte()
-- comme dans le chapitre sur les boucles, cela affiche : -- 1
-- ... -- 10
-- J'ai terminé de compter
Maintenant arrêtont simplement la boucle lorsque i est égal à 5 : function Compte() for i=1, 10 do if i == 5 then break end print(i) end
print("J'ai terminé de compter.") end Compte() -- cela affiche : -- 1 -- ... -- 4
-- j'ai terminé de compter
Lorsque le mot clé break est lu, cela arrête seulement la boucle et permet à la fonction de poursuivre et terminer son exécution. Maintenant enfin, utilisons le mot clé return à la place de break : function Compte() for i=1, 10 do if i == 5 then return end print(i) end
print("J'ai terminé de compter.") end Compte() -- cela affiche : -- 1 -- ... -- 4
Comme vous le constatez, cela n'affiche pas "J'ai terminé de compter" car la fonction s’arrête complètement sans poursuivre son exécution dès qu'elle lit le mot clé return.
Retourner plusieurs valeurs
C'est assez rare pour le noter, les fonctions en Lua ont également la capacité de retourner plusieurs valeurs à la fois. Il suffit de les séparer par des virgules après le mot clé return. Exemple :
function MaFonction() deux = "deux" return "un", deux end
variable1, variable2 = MaFonction() -- souvenez-vous que c'est ainsi que l'on déclare plusieurs variables en une seule ligne -- variable1 vaut "un", variable2 vaut "deux"
Les objets
Nous avons vu dans le chapitre sur les tableaux qu'ils peuvent contenir n'importe quelle type de donnée, ils peuvent donc contenir des fonctions. Ce que l'on appel un objet est typiquement un tableau qui contient (entre autre) des fonctions. Placer des fonctions dans un objet est autant une question d'organisation du code que de contrôle de la manière dont les fonctions sont appelées.
Ainsi dans CraftStudio, chaque objet de jeu dans vos scènes est représenté par un objet (un tableau en Lua) qui contient les informations propres à cet objet de jeu en particulier et sur lequel vous pouvez appeler toute une série de fonctions utiles pour leur
manipulation. De la même manière, l'objet "CraftStudio" contient des fonctions d'utilité générique ainsi que d'autre objets plus spécialisés comme l'objet "CraftStudio.Input" qui contient les fonctions relatives au contrôles (clavier/souris) de votre jeu.
-- Déclaration d'une fonction dans un objet (dans un tableau) Objet = {
-- dans le constructeur de l'objet Fonction = function()
end } -- ou encore Objet.Fonction = function() print("une fonction") end -- ou encore Objet["Fonction"] = function() print("une fonction") end
-- ou bien (le plus courant) function Objet.Fonction() print("une fonction") end
-- Appel de la fonction : Objet["Fonction"]()
-- ou bien (le plus courant)
Objet.Fonction() -- affiche "une fonction"
Aucun de ces exemples ne devraient vous interloquer, puisqu'ils ont tous été vus dans le chapitre sur les tables. Il s'agit juste ici de crée une entrée dans le tableau avec une fonction pour valeur. Ce qu'il y a à remarquer, c'est que dans trois des exemples (dont les deux indiqués comme étant les plus courants), le nom de l'objet et de la fonction sont séparés par un point, exactement comme avec une variable contenue dans un tableau. Or concernant uniquement les fonctions, vous verrez très fréquemment une syntaxe équivalente, mais avec un double point, au lieu d'un simple point :
-- déclaration :
function Objet:Fonction() --
end
-- notez que la syntaxe ci-dessous n'est PAS autorisée, seule celle ci-dessus l'est
Objet:Fonction = function()
-- seule l'utilisation d'un simple point est autorisée avec cette syntaxe
end
-- appel :
Objet:Fonction()
La différence entre les deux syntaxes n'est pas énorme mais il convient de bien la comprendre.
La syntaxe avec le double point est ce que l'on appel un sucre syntaxique (un raccourci) pour la syntaxe avec un seul point.
-- lors de la déclaration :
-- est équivalent à écrire
function Objet.Fonction(self, arg1, arg2) end -- lors de l'appel
Objet:Fonction("arg1", "arg2") -- est équivalent à écrire
Objet.Fonction(Objet, "arg1", "arg2") -- l'objet est explicitement passé comme premier argument
En d'autre thermes, déclarer une fonction avec le double point fait exister
automatiquement la variable self dans la fonction. Dans ce cas, la valeur de self sera toujours celle du premier argument (qui dépend de la syntaxe utilisée lors de l'appel !). Lorsque la fonction est appelée avec le double point, le premier argument n'est en fait pas le premier argument après la parenthèse ouvrante (c'est à dire "arg1"), mais est l'objet sur lequel la fonction est appelée (c'est à dire "Objet" dans notre exemple). -- exemple 1 : déclaration avec le simple point
function Objet.Fonction(arg1, arg2, arg3) -- la fonction est déclarée avec un simple point, self n'existe pas automatiquement print(self, arg1, arg2, arg3)
end
Objet:Fonction("arg1", "arg2") -- affiche quelque chose comme "nil table: 04FB5C48 arg1 arg2" car self n'existe pas mais le premier argument est l'objet.
Objet.Fonction("arg1", "arg2") -- affiche quelque chose comme "nil arg1 arg2 nil" car self n'existe pas et l'objet n'est pas le premier argument.
-- exemple 2 : cas inverse : déclaration avec le double point function Objet:Fonction(arg1, arg2, arg3) -- self existera et prendra la valeur du premier argument, que ce soit l'objet ou n'importe quelle autre valeur
print(self, arg1, arg2, arg3) end
Objet:Fonction("arg1", "arg2") -- affichera quelque chose comme "table: 050A5C20 arg1 arg2 nil" car self existe et prend la valeur du premier argument qui se trouve être l'objet
Objet.Fonction("arg1", "arg2") -- affichera quelque chose comme "arg1 arg2 nil nil" car self existe et prend la valeur du premier argument qui n'est pas l'objet
Pour résumer : Définition avec deux points : la variable "self" existe dans la fonction et prend la valeur de son premier argument. Appel avec deux points : L'objet est
implicitement passé en tant que premier argument à la fonction.
Conclusion
Autant que les variables, les conditions, les tableaux et les boucles, les fonctions font partie des structures qui confèrent au langages de programmation (et donc à nous, programmeur) la capacité de créer un programme interactif fonctionnant sur un ordinateur. Suivant les langages, il existe encore bien d'autres éléments. C'est également le cas en Lua, mais vous avez d'ors et déjà les bases pour commencer à créer un jeu complet avec les seules informations extraites de ces tutoriels.