1. Introduction à Lua
Lua est un langage de programmation léger, rapide et extensible, conçu comme langage de script embarquable. Il est utilisé dans de nombreux domaines : jeux vidéo, systèmes embarqués, configuration de serveurs et bien plus.
Un peu d'histoire
Lua a été créé en 1993 à la Pontifícia Universidade Católica do Rio de Janeiro (PUC-Rio) au Brésil par Roberto Ierusalimschy, Waldemar Celes et Luiz Henrique de Figueiredo. Le nom "Lua" signifie "Lune" en portugais. Lua est distribué sous licence MIT et reste l'un des langages de scripting les plus performants au monde.
Domaines d'utilisation
- Développement de jeux vidéo : LÖVE (framework 2D), Roblox, World of Warcraft, Garry's Mod.
- Embarqué : Intégration dans des applications C/C++ grâce à sa bibliothèque standard mince.
- Serveurs web : Configuration de nginx via le module lua-nginx-module (OpenResty).
- Outillage : Wireshark, nmap, PostgreSQL utilisent Lua pour leurs extensions.
Le REPL (Read-Eval-Print Loop)
L'interpréteur Lua fournit un REPL interactif pour tester du code en direct. Tapez lua dans votre terminal pour le lancer.
-- Lancer le REPL en tapant 'lua' dans le terminal
-- puis tapez du code Lua directement :
> 2 + 2
4
> "Bonjour" .. " " .. "Lua"
Bonjour Lua
> print("Hello World!")
Hello World!
> os.date("%Y-%m-%d")
"2026-07-22"
Votre premier script Lua
Créez un fichier hello.lua et exécutez-le avec lua hello.lua.
-- hello.lua : Mon premier script Lua
print("Bonjour le monde !")
print("Bienvenue dans Lua 5.4")
Règles de syntaxe de base
Lua est un langage sensible à la casse (les majuscules et minuscules comptent). Nom, NOM et nom sont trois variables différentes.
-- Commentaire sur une seule ligne avec --
--[[ Commentaire multi-lignes
Ceci est un commentaire
sur plusieurs lignes
--]]
-- La fin d'une instruction est marquée par un retour à la ligne
-- ou un point-virgule (optionnel)
print("A") ; print("B") -- point-virgule pour séparer sur une ligne
Récapitulatif
- Lua est un langage léger, rapide et extensible créé en 1993 à la PUC-Rio.
- Très utilisé dans le jeu vidéo (LÖVE, Roblox), l'embarqué et la configuration.
- Le REPL permet de tester du code en interactif avec
lua. - Les commentaires utilisent
--(une ligne) et--[[ ]](multi-lignes). - Lua est sensible à la casse.
2. Variables et types de données
Comprendre les variables et les types est fondamental. Lua possède un système de types dynamique avec huit types natifs.
Variables globales et locales
En Lua, toute variable créée sans mot-clé est globale. Utilisez toujours local pour déclarer vos variables — c'est une meilleure pratique tant en termes de performance que de portée.
-- Variable globale (à éviter)
nom = "Alice"
-- Variable locale (recommandé)
local age = 25
local ville = "Paris"
-- Les variables non déclarées valent nil par défaut
print(inexistant) --> nil
Les types de données
Lua possède huit types de valeurs : nil, boolean, number, string, function, userdata, thread et table.
-- nil : absence de valeur
local x = nil
-- boolean : true ou false
local estVrai = true
local estFaux = false
-- number : entiers et flottants (Lua 5.4 distingue les deux)
local entier = 42 -- type : integer
local flottant = 3.14 -- type : float
local grand = 1e6 -- notation scientifique = float
-- string : chaînes de caractères
local prenom = "Marie"
local message = 'Aussi entre guillemets simples'
La fonction type()
La fonction type() retourne le type d'une valeur sous forme de chaîne.
print(type(nil)) --> "nil"
print(type(true)) --> "boolean"
print(type(42)) --> "integer" (Lua 5.4)
print(type(3.14)) --> "float" (Lua 5.4)
print(type("salut")) --> "string"
print(type(print)) --> "function"
print(type({})) --> "table"
Assignation multiple
Lua permet d'assigner plusieurs variables en une seule ligne.
-- Assignation multiple
local a, b, c = 1, 2, 3
print(a, b, c) --> 1 2 3
-- Échange de valeurs sans variable temporaire
a, b = b, a
print(a, b) --> 2 1
-- Trop de valeurs = ignorées, pas assez = nil
local x, y = 10 -- x=10, y=nil
local p, q, r = 1, 2 -- p=1, q=2, r=nil
Conversion de types
Les fonctions tostring() et tonumber() permettent de convertir entre types.
-- tostring : convertir en chaîne
local nb = 42
local txt = tostring(nb)
print(txt .. " est un texte") --> "42 est un texte"
print(tostring(true)) --> "true"
print(tostring(nil)) --> "nil"
-- tonumber : convertir en nombre
print(tonumber("123")) --> 123
print(tonumber("3.14")) --> 3.14
print(tonumber("abc")) --> nil (conversion échoue)
print(tonumber(true)) --> nil
Entiers et flottants en Lua 5.4
Lua 5.4 introduit une distinction stricte entre entiers (integer) et flottants (float). C'est un changement majeur par rapport à Lua 5.3 et avant.
local i = 5 -- integer
local f = 5.0 -- float
print(type(i)) --> "integer"
print(type(f)) --> "float"
print(i == f) --> true (comparaison de valeur)
-- Division entière avec //
print(7 // 2) --> 3 (integer)
print(7 / 2) --> 3.5 (float)
Récapitulatif
- Toujours utiliser
localpour déclarer vos variables. - Les types principaux :
nil,boolean,number(integer/float),string,table,function. type()retourne le type d'une valeur.- Lua 5.4 distingue
integeretfloat. tostring()ettonumber()pour les conversions.
3. Les opérateurs
Lua fournit un ensemble complet d'opérateurs pour les calculs, les comparaisons, la logique et la manipulation de chaînes.
Opérateurs arithmétiques
local a, b = 10, 3
print(a + b) -- Addition : 13
print(a - b) -- Soustraction : 7
print(a * b) -- Multiplication : 30
print(a / b) -- Division : 3.3333333333333
print(a // b) -- Division entière : 3
print(a % b) -- Modulo (reste) : 1
print(a ^ 2) -- Puissance : 100
print(-a) -- Négation : -10
Opérateurs de comparaison
print(5 == 5) -- Égal : true
print(5 ~= 3) -- Différent : true (pas de != en Lua)
print(5 < 3) -- Inférieur : false
print(5 > 3) -- Supérieur : true
print(5 <= 5) -- Inférieur ou = : true
print(5 >= 6) -- Supérieur ou = : false
Opérateurs logiques
En Lua, and, or et not ne retournent pas forcément true ou false — ils retournent les opérandes elles-mêmes.
-- not : inverse la valeur logique
print(not true) --> false
print(not nil) --> true
-- and : retourne le premier opérande faux, ou le dernier si tout est vrai
print(true and "oui") --> "oui"
print(nil and "non") --> nil
print(false and "non") --> false
-- or : retourne le premier opérande vrai, ou le dernier si tout est faux
print(nil or "défaut") --> "défaut"
print("oui" or "non") --> "oui"
L'opérateur de concaténation (..)
local prenom = "Jean"
local nom = "Dupont"
local nomComplet = prenom .. " " .. nom
print(nomComplet) --> "Jean Dupont"
-- Concaténation avec des nombres (conversion automatique)
print("J'ai " .. 5 .. " pommes") --> "J'ai 5 pommes"
L'opérateur de longueur (#)
-- Longueur d'une chaîne
print(#"Bonjour") --> 7
print(#"") --> 0
-- Longueur d'un tableau
local fruits = {"pomme", "banane", "cerise"}
print(#fruits) --> 3
Précédence des opérateurs
De la précédence la plus élevée (en haut) à la plus faible (en bas) :
^(puissance, associatif à droite)not,#,-(unaire)*,/,//,%+,-(binaire)..(concaténation, associatif à droite)<,<=,>,>=,~=,==andor
4. Les conditions (if/elseif/else)
Les structures conditionnelles permettent d'exécuter différentes parties du code selon des conditions.
La structure if/then/end
local age = 18
if age >= 18 then
print("Vous êtes majeur.")
end
if/elseif/else
local note = 14
if note >= 16 then
print("Très bien")
elseif note >= 14 then
print("Bien")
elseif note >= 12 then
print("Assez bien")
elseif note >= 10 then
print("Passable")
else
print("Insuffisant")
end
-- Affiche : "Bien"
Conditions imbriquées
local connecte = true
local admin = false
if connecte then
if admin then
print("Panneau d'administration")
else
print("Espace membre")
end
else
print("Veuillez vous connecter.")
end
Les valeurs truthy et falsy
En Lua, seuls false et nil sont considérés comme faux. Tout le reste est vrai, y compris 0, "" (chaîne vide) et {} (table vide).
if 0 then print("0 est truthy en Lua !") end
if "" then print("La chaîne vide est truthy !") end
if {} then print("Une table vide est truthy !") end
if nil then print("Jamais affiché") else print("nil est falsy") end
Le pattern ternaire avec and/or
Lua n'a pas d'opérateur ternaire ? :, mais on peut simuler le même comportement avec and et or.
local age = 20
local statut = (age >= 18) and "majeur" or "mineur"
print(statut) --> "majeur"
-- Fonctionne comme : condition ? valeurSiVrai : valeurSiFaux
local x = 5
local resultat = x > 0 and "positif" or "négatif"
print(resultat) --> "positif"
Récapitulatif
- La structure est
if condition then ... elseif ... else ... end. - En Lua, seuls
falseetnilsont falsy. 0,""et{}sont truthy.- On peut simuler un ternaire avec
condition and valVrai or valFaux.
5. Les boucles (for, while, repeat)
Les boucles permettent de répéter des instructions. Lua propose plusieurs types de boucles adaptés à différents besoins.
La boucle for numérique
La boucle for numérique itère de start à stop avec un step optionnel (par défaut : 1).
-- Boucle de 1 à 5 (pas de 1 par défaut)
for i = 1, 5 do
print(i)
end
-- Affiche : 1, 2, 3, 4, 5
-- Boucle avec un pas personnalisé
for i = 0, 10, 2 do
print(i)
end
-- Affiche : 0, 2, 4, 6, 8, 10
-- Boucle descendante
for i = 5, 1, -1 do
print(i)
end
-- Affiche : 5, 4, 3, 2, 1
La boucle for générique (pairs/ipairs)
La boucle for générique parcourt un itérateur. Les plus courants sont pairs() et ipairs().
-- ipairs : itère sur un tableau ordonné (de l'index 1)
local fruits = {"pomme", "banane", "cerise"}
for index, valeur in ipairs(fruits) do
print(index, valeur)
end
-- 1 pomme, 2 banane, 3 cerise
-- pairs : itère sur toutes les clés d'une table (hash + array)
local personne = {nom = "Lucie", age = 22, ville = "Lyon"}
for cle, valeur in pairs(personne) do
print(cle .. " = " .. tostring(valeur))
end
La boucle while
local compteur = 1
while compteur <= 5 do
print("Tour n°" .. compteur)
compteur = compteur + 1
end
La boucle repeat/until
La boucle repeat exécute le code au moins une fois, puis vérifie la condition avec until.
local n = 1
repeat
print(n)
n = n * 2
until n > 100
-- Affiche : 1, 2, 4, 8, 16, 32, 64
break et goto
break sort d'une boucle. goto avec des labels (introduit en Lua 5.2) permet de sauter dans le code.
-- break : sortir de la boucle
for i = 1, 100 do
if i == 5 then
print("On arrête à 5 !")
break
end
print(i)
end
-- goto et labels (Lua 5.2+)
for i = 1, 10 do
if i == 3 then
goto fin
end
print(i)
end
::fin::
print("Terminé")
Récapitulatif
for i = start, stop, step do: boucle numérique.for k, v in pairs(table) do: itère sur toutes les paires clé/valeur.for i, v in ipairs(table) do: itère sur un tableau ordonné.while condition do: boucle tant que la condition est vraie.repeat ... until condition: boucle au moins une fois.breaksort de la boucle,gotosaute à un label.
6. Les fonctions
Les fonctions sont des valeurs de première classe en Lua. Elles peuvent être assignées à des variables, passées en argument et retournées par d'autres fonctions.
Syntaxe de base
-- Déclaration d'une fonction
local function direBonjour(nom)
print("Bonjour, " .. nom .. " !")
end
direBonjour("Alice") --> "Bonjour, Alice !"
-- Équivalent avec assignation de variable
local direBonjour2 = function(nom)
print("Bonjour, " .. nom .. " !")
end
Valeurs de retour multiples
Une fonction peut retourner plusieurs valeurs en même temps.
local function diviser(a, b)
return a // b, a % b -- quotient et reste
end
local quotient, reste = diviser(17, 5)
print(quotient, reste) --> 3 2
-- Si on ne récupère qu'une valeur, les autres sont ignorées
local q = diviser(17, 5)
print(q) --> 3
Les arguments variables (vararg)
Le syntaxe ... permet de créer des fonctions acceptant un nombre variable d'arguments.
local function somme(...)
local args = {...}
local total = 0
for _, v in ipairs(args) do
total = total + v
end
return total
end
print(somme(1, 2, 3, 4, 5)) --> 15
Les closures (fermetures)
Une closure est une fonction qui capture les variables de son environnement lexical.
local function créerCompteur()
local n = 0
return function()
n = n + 1
return n
end
end
local compteur = créerCompteur()
print(compteur()) --> 1
print(compteur()) --> 2
print(compteur()) --> 3
Les fonctions comme valeurs
-- Les fonctions peuvent être stockées dans des tableaux
local operations = {
addition = function(a, b) return a + b end,
multiplication = function(a, b) return a * b end,
}
print(operations.addition(3, 4)) --> 7
print(operations.multiplication(3, 4)) --> 12
-- Fonction passée en argument (callback)
local function appliquer(fn, x)
return fn(x)
end
local doubler = function(x) return x * 2 end
print(appliquer(doubler, 5)) --> 10
Récapitulatif
- Les fonctions se déclarent avec
function ... end. - Elles peuvent retourner plusieurs valeurs avec
return a, b. ...capture les arguments variables.- Les closures capturent les variables de leur environnement.
- Les fonctions sont des valeurs de première classe.
7. Les tables (arrays et dictionnaires)
La table est la seule structure de données en Lua. Elle remplace les tableaux, les dictionnaires, les ensembles, les objets et bien plus.
Tables de style array (indexées à partir de 1 !)
En Lua, les tableaux commencent à l'index 1, pas 0 !
local fruits = {"pomme", "banane", "cerise"}
print(fruits[1]) --> "pomme" (pas fruits[0] !)
print(fruits[2]) --> "banane"
print(#fruits) --> 3 (longueur du tableau)
Manipulation des tableaux
local liste = {10, 20, 30}
-- table.insert : ajouter un élément
table.insert(liste, 40) -- ajoute à la fin
table.insert(liste, 2, 15) -- insère à l'index 2
print(table.concat(liste, ", ")) --> "10, 15, 20, 30, 40"
-- table.remove : supprimer un élément
table.remove(liste, 1) -- supprime l'élément à l'index 1
print(table.concat(liste, ", ")) --> "15, 20, 30, 40"
Tables de style hash (dictionnaires)
local personne = {
nom = "Alice",
age = 30,
ville = "Marseille",
["clé avec espaces"] = "valeur spéciale",
}
print(personne.nom) --> "Alice"
print(personne["age"]) --> 30 (syntaxe alternative)
print(personne.ville) --> "Marseille"
Tables mixtes
Une table peut mélanger style array et hash dans la même table.
local joueur = {
"Épée", -- [1] = "Épée"
"Bouclier", -- [2] = "Bouclier"
nom = "Héros", -- clé nommée
pv = 100, -- clé nommée
}
print(#joueur) --> 2 (compte seulement les clés numériques)
print(joueur.nom) --> "Héros"
Les fonctions utilitaires de table
-- table.sort : trier un tableau
local nombres = {5, 2, 8, 1, 9, 3}
table.sort(nombres)
print(table.concat(nombres, ", ")) --> "1, 2, 3, 5, 8, 9"
-- Tri personnalisé avec une fonction de comparaison
local mots = {"banane", "cerise", "abricot"}
table.sort(mots, function(a, b) return a > b end)
print(table.concat(mots, ", ")) --> "cerise, banane, abricot"
-- table.pack / table.unpack
local t = table.pack(1, 2, 3, 4, 5)
print(t.n) --> 5
local a, b, c = table.unpack(t, 2, 4)
print(a, b, c) --> 2 3 4
pairs() vs ipairs()
ipairs()parcourt les clés numériques consécutives à partir de 1. S'arrête au premier trou.pairs()parcourt toutes les clés (numériques et nommées), dans un ordre non garanti.
local t = {10, 20, nom = "test", 30}
-- ipairs s'arrête au trou (manque l'index 4 puis le champ "nom")
for i, v in ipairs(t) do
print(i, v)
end
-- 1 10, 2 20 (s'arrête car index 3 est "nom" pas un number)
-- pairs parcourt tout
for k, v in pairs(t) do
print(k, v)
end
-- 1 10, 2 20, nom test, 4 30
Récapitulatif
- La table est la seule structure de données de Lua.
- Les tableaux commencent à l'index 1.
table.insert(),table.remove(),table.sort(),table.concat()sont les fonctions clés.ipairs()pour les tableaux ordonnés,pairs()pour toutes les clés.
8. Les strings et conversion
Les chaînes de caractères en Lua sont immuables. La bibliothèque string offre de nombreuses fonctions pour les manipuler.
Création et concaténation
-- Différentes façons de créer une chaîne
local s1 = "Bonjour"
local s2 = 'Aussi avec des guillemets simples'
local s3 = [[Chaîne multi-ligne
qui peut contenir
plusieurs lignes
sans échapper les retours à la ligne]]
-- Concaténation avec ..
local nom = "Lua"
local version = 5.4
print(nom .. " " .. tostring(version)) --> "Lua 5.4"
La fonction # (longueur)
print(#"Hello") --> 5
print(#"") --> 0
print(#"café") --> 4 (octets, pas caractères UTF-8)
La bibliothèque string
local s = "Bonjour le monde"
-- string.sub : extraire une sous-chaîne
print(string.sub(s, 1, 7)) --> "Bonjour"
print(string.sub(s, -6)) --> "monde" (depuis la fin)
-- string.find : chercher un motif
local debut, fin = string.find(s, "monde")
print(debut, fin) --> 11 16
-- string.gsub : remplacement global
local nouveau = string.gsub(s, "monde", "Lua")
print(nouveau) --> "Bonjour le Lua"
-- string.upper / string.lower
print(string.upper("hello")) --> "HELLO"
print(string.lower("HELLO")) --> "hello"
-- string.rep : répéter une chaîne
print(string.rep("ha", 3)) --> "hahaha"
-- string.reverse : inverser
print(string.reverse("abc")) --> "cba"
string.format : formatage
string.format fonctionne comme printf en C.
local nom = "Alice"
local age = 25
local pi = 3.14159
print(string.format("Bonjour %s, vous avez %d ans.", nom, age))
--> "Bonjour Alice, vous avez 25 ans."
print(string.format("Pi = %.2f", pi))
--> "Pi = 3.14"
-- Codes de format courants :
-- %s : chaîne, %d : entier, %f : flottant
-- %x : hexadécimal, %o : octal, %% : pourcentage littéral
print(string.format("Hex : %x, Oct : %o", 255, 255))
--> "Hex : ff, Oct : 377"
Strings multi-lignes avec [[ ]]
local sql = [[
SELECT nom, age
FROM utilisateurs
WHERE ville = 'Paris'
ORDER BY nom
]]
print(sql)
-- Pour ignorer l'indentation, utilisez [= n [ ... ]= n ]
local texte = [=[
Ceci est indenté
mais le résultat ne le sera pas
]=]
Récapitulatif
- Les chaînes sont immuables en Lua.
- La concaténation se fait avec
... #retourne la longueur en octets.string.sub,string.find,string.gsub,string.formatsont les fonctions principales.[[ ]]permet les chaînes multi-lignes.
9. Les métatables et l'héritage
Les métatables sont l'un des concepts les plus puissants de Lua. Elles permettent de redéfinir le comportement des opérations sur les tables, et constituent la base de la programmation orientée objet en Lua.
setmetatable et getmetatable
local monTable = {a = 1, b = 2}
local mt = {} -- la métatable
setmetatable(monTable, mt)
print(getmetatable(monTable)) --> table: 0x... (la table mt)
__index : intercepter les accès
L'event __index est déclenché quand on accède à une clé inexistante dans une table.
local personne = {nom = "Alice"}
local mt = {
__index = function(table, cle)
return "Clé '" .. cle .. "' non trouvée"
end
}
setmetatable(personne, mt)
print(personne.nom) --> "Alice" (clé existante)
print(personne.age) --> "Clé 'age' non trouvée"
print(personne.ville) --> "Clé 'ville' non trouvée"
-- __index peut aussi être une table (plus courant)
local defauts = {couleur = "bleu", taille = 10}
local obj = {nom = "Widget"}
setmetatable(obj, {__index = defauts})
print(obj.couleur) --> "bleu" (trouvé via __index)
__newindex : intercepter les écritures
local donnees = {}
local mt = {
__newindex = function(table, cle, valeur)
print("Interdit de modifier '" .. cle .. "'")
end
}
setmetatable(donnees, mt)
donnees.nom = "test" --> "Interdit de modifier 'nom'"
Redéfinir les opérateurs arithmétiques
local Vecteur = {}
Vecteur.__index = Vecteur
function Vecteur.new(x, y)
return setmetatable({x = x, y = y}, Vecteur)
end
function Vecteur:__add(autre)
return Vecteur.new(self.x + autre.x, self.y + autre.y)
end
function Vecteur:__sub(autre)
return Vecteur.new(self.x - autre.x, self.y - autre.y)
end
function Vecteur:__mul(scalaire)
return Vecteur.new(self.x * scalaire, self.y * scalaire)
end
function Vecteur:__tostring()
return string.format("(%d, %d)", self.x, self.y)
end
function Vecteur:__len()
return math.sqrt(self.x^2 + self.y^2)
end
local v1 = Vecteur.new(3, 4)
local v2 = Vecteur.new(1, 2)
print(v1 + v2) --> (4, 6)
print(v1 - v2) --> (2, 2)
print(v1 * 2) --> (6, 8)
print(#v1) --> 5 (norme)
__call : rendre la table appelable
local Timer = {}
Timer.__index = Timer
function Timer.new(secondes)
return setmetatable({duree = secondes, restant = secondes}, Timer)
end
function Timer:__call()
if self.restant > 0 then
self.restant = self.restant - 1
return true
else
return false
end
end
local t = Timer.new(3)
print(t()) --> true (2 restant)
print(t()) --> true (1 restant)
print(t()) --> true (0 restant)
print(t()) --> false (terminé)
Héritage et POO
local Animal = {}
Animal.__index = Animal
function Animal.new(nom, espece)
return setmetatable({nom = nom, espece = espece}, Animal)
end
function Animal:parler()
print(self.nom .. " fait un bruit.")
end
-- Héritage : Chien hérite d'Animal
local Chien = setmetatable({}, {__index = Animal})
Chien.__index = Chien
function Chien.new(nom)
local self = Animal.new(nom, "Chien")
return setmetatable(self, Chien)
end
function Chien:parler()
print(self.nom .. " fait Wouf !")
end
local rex = Chien.new("Rex")
rex:parler() --> "Rex fait Wouf !"
print(rex.espece) --> "Chien" (hérité d'Animal)
Récapitulatif
- Les métatables modifient le comportement des tables avec des "events".
__indexintercepte les lectures de clés inexistantes.__newindexintercepte les écritures.__add,__sub,__mul,__tostring,__call,__lenredéfinissent les opérateurs.- L'héritage s'implémente avec
__indexchaîné.
10. Les coroutines
Les coroutines sont des fonctions suspendables qui peuvent reprendre leur exécution à un point précis. Elles sont particulièrement utiles pour les générateurs, les patrons producteur-consommateur et les dialogues dans les jeux.
Création et exécution
-- Créer une coroutine
local co = coroutine.create(function()
print("Début")
coroutine.yield() -- suspendre
print("Reprise !")
end)
print(coroutine.status(co)) --> "suspended"
coroutine.resume(co) --> Affiche "Début"
print(coroutine.status(co)) --> "suspended"
coroutine.resume(co) --> Affiche "Reprise !"
print(coroutine.status(co)) --> "dead"
Transmission de données
coroutine.resume peut envoyer des données à la coroutine, et coroutine.yield peut en retourner.
local co = coroutine.create(function(prenom)
print("Bonjour, " .. prenom)
local age = coroutine.yield("Quel âge as-tu ?") -- renvoie la question
print("Tu as " .. age .. " ans.")
end)
local ok, msg = coroutine.resume(co, "Alice")
print(msg) --> "Quel âge as-tu ?"
ok, msg = coroutine.resume(co, 25)
-- Affiche "Tu as 25 ans."
Le pattern producteur-consommateur
-- Producteur : génère des nombres
local function producteur(max)
for i = 1, max do
coroutine.yield(i * 10)
end
end
-- Consommateur : traite les données
local co = coroutine.create(function()
producteur(5)
end)
while coroutine.status(co) ~= "dead" do
local ok, valeur = coroutine.resume(co)
if ok and valeur then
print("Reçu :", valeur)
end
end
-- Affiche : 10, 20, 30, 40, 50
coroutine.wrap : alternative plus simple
coroutine.wrap retourne une fonction au lieu d'une coroutine. Les erreurs sont propagées directement.
local function compter(n)
for i = 1, n do
coroutine.yield(i)
end
end
local gen = coroutine.wrap(compter)
print(gen(5)) --> 1
print(gen()) --> 2
print(gen()) --> 3
print(gen()) --> 4
print(gen()) --> 5
Coroutines asymétriques vs symétriques
- Asymétriques (Lua) : On reprend toujours une coroutine avec
resumedepuis l'appelant. La coroutine suspend avecyield. - Symétriques : On peut transférer le contrôle directement d'une coroutine à une autre (Lua n'implémente pas ce modèle nativement).
-- Exemple : dialogue asynchrone pour un jeu
local function dialogueNPC()
print("NPC : Salut aventurier !")
coroutine.yield() -- attend la réponse du joueur
print("NPC : Tu veux une quête ?")
coroutine.yield() -- attend la réponse
print("NPC : Va tuer 10 slimes !")
end
local npc = coroutine.create(dialogueNPC)
coroutine.resume(npc) -- NPC parle
io.read() -- joueur appuie sur Entrée
coroutine.resume(npc) -- NPC continue
io.read() -- joueur appuie sur Entrée
coroutine.resume(npc) -- NPC donne la quête
Récapitulatif
coroutine.create(fn)crée une coroutine.coroutine.resume(co, ...)démarre ou reprend une coroutine.coroutine.yield(...)suspend l'exécution et retourne des valeurs.coroutine.status(co)retourne "suspended", "running" ou "dead".coroutine.wrap(fn)retourne une fonction appelable.
11. Modules et packages
Le système de modules de Lua permet d'organiser le code en fichiers réutilisables. C'est essentiel pour tout projet d'une certaine taille.
Le mot-clé require()
require() charge un module. Il cherche le fichier dans les chemins définis par package.path.
-- Charger le module "math" (déjà fourni par Lua)
local math = require("math")
print(math.sqrt(16)) --> 4
-- Charger un module personnalisé
local utils = require("utils")
Créer un module
Un module Lua est simplement un fichier qui retourne une table contenant les fonctions et valeurs à exposer.
-- fichier : utils.lua
local Utils = {}
function Utils.majuscule(texte)
return string.upper(texte)
end
function Utils.chevaucher(t1, t2)
for _, v in ipairs(t1) do
for _, v2 in ipairs(t2) do
if v == v2 then return true end
end
end
return false
end
return Utils
-- fichier : main.lua
local Utils = require("utils")
print(Utils.majuscule("bonjour")) --> "BONJOUR"
local a = {1, 2, 3}
local b = {3, 4, 5}
print(Utils.chevaucher(a, b)) --> true
package.loaded et package.path
-- package.loaded contient tous les modules déjà chargés
for nom, mod in pairs(package.loaded) do
print(nom)
end
-- package.path définit où require() cherche les modules
print(package.path)
-- Contient quelque chose comme :
-- ./?.lua;/usr/local/share/lua/5.4/?.lua;...
-- Le ? est remplacé par le nom du module
Isolation des modules
Chaque module a son propre environnement. Les variables local d'un module ne sont pas visibles de l'extérieur.
-- fichier : config.lua
local CONFIG = {}
-- Variable privée (pas accessible de l'extérieur)
local secret = "ma_clé_123"
-- Variable publique (accessible via le module)
CONFIG.version = "1.0.0"
CONFIG.debug = true
function CONFIG.getSecret()
return secret
end
return CONFIG
-- fichier : main.lua
local Config = require("config")
print(Config.version) --> "1.0.0"
print(Config.debug) --> true
print(Config.secret) --> nil (variable privée)
print(Config.getSecret()) --> "ma_clé_123" (via fonction publique)
Le pattern mod.lua
Pour les gros projets, on utilise souvent un répertoire mod/ avec un fichier mod/init.lua.
-- Structure de fichiers :
-- projet/
-- main.lua
-- mod/
-- init.lua
-- utils.lua
-- mod/init.lua
local M = {}
M.utils = require("mod.utils")
return M
-- main.lua
local mod = require("mod")
print(mod.utils) -- accès au sous-module
Récapitulatif
require("nom")charge et retourne un module.- Un module est un fichier Lua qui retourne une table.
package.pathdéfinit les répertoires de recherche.- Les variables
localdans un module sont privées. - Un module ne charge qu'une seule fois (cache dans
package.loaded).
12. Projet final
Maintenant que vous maîtrisez Lua 5.4, il est temps de créer un projet complet ! Nous allons construire un mini jeu de RPG textuel utilisant toutes les notions apprises : tables, fonctions, métatables, coroutines et modules.
Ce que vous devez implémenter
- Tables pour l'inventaire, les statistiques du joueur et les ennemis.
- Fonctions pour les actions (attaquer, fuir, utiliser objet).
- Métatables pour les entités (joueur, ennemis) avec héritage.
- Coroutines pour les dialogues et événements scriptés.
- Modules pour organiser les objets, les combats et l'inventaire.
Le module des entités (entities.lua)
-- entities.lua : Module des entités de jeu
local Entite = {}
Entite.__index = Entite
function Entite.new(nom, pv, attaque)
return setmetatable({
nom = nom,
pv = pv,
pvMax = pv,
attaque = attaque,
}, Entite)
end
function Entite:estVivant()
return self.pv > 0
end
function Entite:subirDegats(degats)
self.pv = self.pv - degats
if self.pv < 0 then self.pv = 0 end
print(self.nom .. " subit " .. degats .. " dégâts. PV : " .. self.pv)
end
function Entite:__tostring()
return string.format("%s [PV: %d/%d, ATK: %d]",
self.nom, self.pv, self.pvMax, self.attaque)
end
-- Héritage : Joueur hérite d'Entite
local Joueur = setmetatable({}, {__index = Entite})
Joueur.__index = Joueur
function Joueur.new(nom)
local self = Entite.new(nom, 100, 15)
self.inventaire = {}
self.xp = 0
self.niveau = 1
return setmetatable(self, Joueur)
end
function Joueur:ajouterObjet(objet)
table.insert(self.inventaire, objet)
end
function Joueur:gagnerXP(montant)
self.xp = self.xp + montant
if self.xp >= self.niveau * 50 then
self.niveau = self.niveau + 1
self.pvMax = self.pvMax + 20
self.pv = self.pvMax
self.attaque = self.attaque + 3
print("*** Niveau supérieur ! Niveau " .. self.niveau .. " ***")
end
end
-- Héritage : Monstre hérite d'Entite
local Monstre = setmetatable({}, {__index = Entite})
Monstre.__index = Monstre
function Monstre.new(nom, pv, attaque, xp)
local self = Entite.new(nom, pv, attaque)
self.xp = xp
return setmetatable(self, Monstre)
end
Entite.Joueur = Joueur
Entite.Monstre = Monstre
return Entite
Le module des objets (items.lua)
-- items.lua : Module des objets
local Objets = {
potion = {
nom = "Potion de soin",
type = "consommable",
effet = function(cible)
cible.pv = math.min(cible.pv + 30, cible.pvMax)
print(cible.nom .. " récupère 30 PV !")
end,
},
epee = {
nom = "Épée de fer",
type = "arme",
bonus = 5,
},
bouclier = {
nom = "Bouclier en bois",
type = "armure",
bonus = 3,
},
}
function Objets.obtenir(cle)
return Objets[cle]
end
return Objets
Les coroutines pour les dialogues
-- dialogues.lua : Module de dialogues avec coroutines
local Dialogues = {}
function Dialogues.marchand()
return coroutine.create(function()
print("--- Le Marchand ---")
print("Marchand : Bienvenue ! J'ai des objets à vendre.")
coroutine.yield({action = "menu"})
print("Marchand : La potion te soigne de 30 PV.")
coroutine.yield({action = "vendre", objet = "potion", prix = 20})
print("Marchand : Reviens quand tu veux !")
coroutine.yield({action = "fin"})
end)
end
function Dialogues.boss()
return coroutine.create(function()
print("--- Le Dragon ---")
print("Dragon : Tu oses défier le Dragon de Feu ?!")
coroutine.yield()
print("Dragon : GOUTTE MON FEU !")
coroutine.yield()
print("Dragon : Impréssionnant... Tu es digne de ce combat !")
end)
end
return Dialogues
Le programme principal (main.lua)
-- main.lua : Programme principal du RPG
local math = require("math")
local Entite = require("entities")
local Objets = require("items")
local Dialogues = require("dialogues")
-- Créer le joueur
local joueur = Entite.Joueur.new("Héros")
joueur:ajouterObjet(Objets.obtenir("potion"))
-- Créer les ennemis
local ennemis = {
Entite.Monstre.new("Gobelin", 30, 8, 15),
Entite.Monstre.new("Squelette", 50, 12, 25),
Entite.Monstre.new("Dragon de Feu", 120, 20, 100),
}
-- Fonction de combat
local function combat(j, e)
print("\n=== COMBAT : " .. j.nom .. " vs " .. e.nom .. " ===")
print(e)
while j:estVivant() and e:estVivant() do
-- Tour du joueur
local degats = math.random(1, j.attaque)
e:subirDegats(degats)
if not e:estVivant() then
print(e.nom .. " est vaincu !")
j:gagnerXP(e.xp)
return true
end
-- Tour de l'ennemi
degats = math.random(1, e.attaque)
j:subirDegats(degats)
if not j:estVivant() then
print(j.nom .. " a été vaincu...")
return false
end
print(" " .. j .. " | " .. e)
end
end
-- Boucle principale du jeu
print("========================================")
print(" RPG LUA 5.4 - LE DÉFI DU DRAGON")
print("========================================")
print()
-- Dialoguer avec le marchand (coroutine)
local dialog = Dialogues.marchand()
coroutine.resume(dialog)
coroutine.resume(dialog)
coroutine.resume(dialog)
-- Combattre les monstres
for i, ennemi in ipairs(ennemis) do
print("\n--- Étape " .. i .. " ---")
-- Dialogue du boss avant le dernier combat
if i == 3 then
local bossDialog = Dialogues.boss()
coroutine.resume(bossDialog)
coroutine.resume(bossDialog)
coroutine.resume(bossDialog)
end
local victoire = combat(joueur, ennemi)
if not victoire then
print("\nGame Over... Vous avez péri.")
return
end
end
print("\n========================================")
print(" FÉLICITATIONS ! Vous avez vaincu le Dragon !")
print(" Niveau final : " .. joueur.niveau)
print("========================================")
Conseils pour réussir
- Commencez par les modules séparément avant de les assembler dans main.lua.
- Testez chaque module individuellement avec
lua -i. - Expérimentez : ajoutez des sorts, des pièges, un système de défense.
- Utilisez
string.formatpour un affichage plus soigné. - Pensez à gérer les erreurs avec
pcallpour les inputs utilisateur. - Relisez les leçons précédentes si un concept n'est pas clair.