Objectif : Maîtriser les tables (tableaux/dictionnaires) et apprendre à sauvegarder les données des joueurs avec DataStore. Leur progression ne sera plus jamais perdue !
La mémoire de ton jeu
Les tables te permettent de stocker plusieurs valeurs ensemble (inventaires, listes de joueurs, configurations...). Et les DataStores sauvegardent ces données même quand le joueur quitte le jeu !
Un tableau est une liste ordonnée d'éléments. Chaque élément a un numéro (index) qui commence à 1 en Luau (pas 0 !).
Créer et utiliser un tableau
-- Créer un tableau
local fruits = {"Pomme", "Banane", "Orange", "Fraise"}
-- Accéder aux éléments (index commence à 1 !)
print(fruits[1]) -- "Pomme"
print(fruits[3]) -- "Orange"
-- Modifier un élément
fruits[2] = "Kiwi"
print(fruits[2]) -- "Kiwi"
-- Ajouter à la fin
table.insert(fruits, "Mangue")
-- Supprimer le dernier
table.remove(fruits)
-- Nombre d'éléments
print(#fruits) -- 4local inventaire = {"Épée", "Bouclier", "Potion", "Clé"}
-- Méthode 1 : for classique
for i = 1, #inventaire do
print(i .. ": " .. inventaire[i])
end
-- Méthode 2 : ipairs (plus élégant)
for index, item in ipairs(inventaire) do
print(index .. ": " .. item)
end
-- Sortie :
-- 1: Épée
-- 2: Bouclier
-- 3: Potion
-- 4: Cléipairs ? Pour les tableaux (indices numériques ordonnés)
pairs ? Pour les dictionnaires (clés personnalisées)
Un dictionnaire associe des clés à des valeurs. Plus lisible qu'un tableau quand tu veux nommer tes données !
Créer un dictionnaire
-- Dictionnaire = {} avec des clés
local joueur = {
nom = "Alex",
niveau = 15,
coins = 250,
premium = true
}
-- Accéder aux valeurs
print(joueur.nom) -- "Alex"
print(joueur["niveau"]) -- 15 (syntaxe alternative)
-- Modifier une valeur
joueur.coins = joueur.coins + 100
print(joueur.coins) -- 350
-- Ajouter une nouvelle clé
joueur.xp = 1500
-- Supprimer une clé
joueur.premium = nillocal stats = {
force = 10,
agilite = 8,
intelligence = 12,
chance = 5
}
-- Utilise pairs() pour les dictionnaires
for cle, valeur in pairs(stats) do
print(cle .. " = " .. valeur)
end
-- Sortie (ordre non garanti) :
-- force = 10
-- intelligence = 12
-- agilite = 8
-- chance = 5Tu peux mettre des tables dans des tables pour créer des structures complexes !
local joueur = {
nom = "Alex",
stats = {
force = 10,
agilite = 8
},
inventaire = {"Épée", "Potion", "Carte"}
}
-- Accès imbriqué
print(joueur.stats.force) -- 10
print(joueur.inventaire[1]) -- "Épée"Le DataStoreService permet de sauvegarder des données sur les serveurs Roblox. Quand le joueur revient, ses données sont restaurées !
Les DataStores ne fonctionnent qu'en jeu publié ou en mode Team Test. En Play Solo, ils ne marchent pas ! Active aussi "Enable Studio Access to API Services" dans Game Settings.
Système de sauvegarde complet
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
-- Crée ou récupère le DataStore
local dataStore = DataStoreService:GetDataStore("PlayerData")
-- Quand un joueur rejoint
Players.PlayerAdded:Connect(function(player)
local userId = player.UserId
local key = "Player_" .. userId
-- Essaie de charger les données
local success, data = pcall(function()
return dataStore:GetAsync(key)
end)
if success then
if data then
print("Données chargées pour " .. player.Name)
-- Restaure les données (ex: coins)
player.leaderstats.Coins.Value = data.coins or 0
else
print("Nouveau joueur : " .. player.Name)
end
else
warn("Erreur de chargement : " .. data)
end
end)-- Quand un joueur quitte
Players.PlayerRemoving:Connect(function(player)
local userId = player.UserId
local key = "Player_" .. userId
-- Prépare les données à sauvegarder
local dataToSave = {
coins = player.leaderstats.Coins.Value,
level = player.leaderstats.Level.Value,
lastSave = os.time()
}
-- Sauvegarde avec pcall (gestion d'erreur)
local success, error = pcall(function()
dataStore:SetAsync(key, dataToSave)
end)
if success then
print("Données sauvegardées pour " .. player.Name)
else
warn("Erreur de sauvegarde : " .. error)
end
end)Les DataStores peuvent échouer (serveur surchargé, problème réseau...). pcall() (protected call) attrape les erreurs sans crasher ton script. TOUJOURS utiliser pcall avec les DataStores !
UpdateAsync est plus sûr que SetAsync car il évite les conflits si le joueur fait plusieurs sauvegardes rapidement.
local success, error = pcall(function()
dataStore:UpdateAsync(key, function(oldData)
-- oldData = les données actuelles
local newData = oldData or {}
newData.coins = player.leaderstats.Coins.Value
newData.level = player.leaderstats.Level.Value
return newData -- Retourne les nouvelles données
end)
end)5 questions pour valider tes acquis
Implémente la collecte des ressources et sauvegarde la progression du joueur pour qu'il ne perde rien en quittant !
Modules 1 à 8 complétés (DataStore, Tables)
Crée 3 Parts (Oxygène, Énergie, Minerai) dans le Workspace.
Ajoute ce script dans chaque Part pour gérer la collecte :
local objet = script.Parent
local typeRessource = "Oxygene" -- Change pour chaque objet
local valeur = 10
objet.Touched:Connect(function(hit)
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if player then
-- On récupère l'attribut actuel
local actuel = player:GetAttribute(typeRessource) or 0
-- On ajoute la valeur
player:SetAttribute(typeRessource, actuel + valeur)
-- L'objet disparaît
objet:Destroy()
end
end)Dans ServerScriptService, crée un script "GestionDonnees" :
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local monDataStore = DataStoreService:GetDataStore("MaSauvegardeV1")
Players.PlayerAdded:Connect(function(player)
local id = "Player_" .. player.UserId
-- Charger les données
local success, data = pcall(function()
return monDataStore:GetAsync(id)
end)
if success and data then
-- Restaurer les attributs
player:SetAttribute("Oxygene", data.Oxygene)
player:SetAttribute("Energie", data.Energie)
print("💾 Données chargées !")
else
-- Nouveau joueur : valeurs par défaut
player:SetAttribute("Oxygene", 100)
player:SetAttribute("Energie", 100)
end
end)
Players.PlayerRemoving:Connect(function(player)
local id = "Player_" .. player.UserId
-- Créer la table de sauvegarde
local dataToSave = {
Oxygene = player:GetAttribute("Oxygene"),
Energie = player:GetAttribute("Energie")
}
-- Sauvegarder
pcall(function()
monDataStore:SetAsync(id, dataToSave)
end)
print("💾 Données sauvegardées !")
end)Ajoute une boucle while true do pour sauvegarder automatiquement toutes les 60 secondes !
Prochaine étape ? Module 10 : Animations & Publication
Sauvegarde la progression complète du joueur : énergie, cristaux collectés, jours survécus, et améliorations débloquées. Plus personne ne perdra ses données !
On va sauvegarder toutes les stats du joueur dans une table organisée. Crée un script GestionSauvegarde dans ServerScriptService :
-- GestionSauvegarde.lua local DataStoreService = game:GetService("DataStoreService") local Players = game:GetService("Players") -- DataStore pour la station spatiale local stationDataStore = DataStoreService:GetDataStore("StationSpatiale_V1") -- DONNÉES PAR DÉFAUT pour un nouveau joueur local DONNEES_DEFAUT = { -- Ressources energie = 100, energieMax = 100, cristaux = 0, -- Progression joursSurvecus = 0, cristauxTotaux = 0, -- Améliorations débloquées ameliorations = { capaciteEnergie = 1, -- Niveau 1 par défaut vitesseRecharge = 1, efficacitePortes = 1 }, -- Stats de jeu tempsJeuTotal = 0, -- En secondes derniereConnexion = 0 -- Timestamp }ameliorations est une table dans une table ! C'est parfait pour organiser des catégories de données.Utilise pcall pour protéger contre les erreurs de réseau et fusionne avec les valeurs par défaut :
-- FONCTION : Fusionner tables (ajoute les clés manquantes) local function fusionnerTables(base, ajouts) local resultat = {} -- Copier la base for cle, valeur in pairs(base) do if type(valeur) == "table" then resultat[cle] = fusionnerTables(valeur, ajouts[cle] or {}) else resultat[cle] = ajouts[cle] or valeur end end return resultat end -- FONCTION : Charger les données d'un joueur local function chargerDonnees(player) local cle = "Joueur_" .. player.UserId local succes, donnees = pcall(function() return stationDataStore:GetAsync(cle) end) if succes and donnees then -- Fusionner avec défauts (au cas où on ajoute des champs) local donneesCompletes = fusionnerTables(DONNEES_DEFAUT, donnees) print("✅ Données chargées pour", player.Name) return donneesCompletes else print("🆕 Nouveau joueur :", player.Name) return fusionnerTables(DONNEES_DEFAUT, {}) end endDONNEES_DEFAUT plus tard, les anciens joueurs l'auront automatiquement avec la valeur par défaut.Crée une fonction de sauvegarde sécurisée avec plusieurs tentatives :
-- Cache des données en mémoire local cacheJoueurs = {} -- FONCTION : Sauvegarder les données d'un joueur local function sauvegarderDonnees(player) local cle = "Joueur_" .. player.UserId local donnees = cacheJoueurs[player.UserId] if not donnees then warn("⚠️ Pas de données à sauvegarder pour", player.Name) return false end -- Mettre à jour le timestamp donnees.derniereConnexion = os.time() -- 3 tentatives de sauvegarde for tentative = 1, 3 do local succes = pcall(function() stationDataStore:SetAsync(cle, donnees) end) if succes then print("💾 Sauvegarde réussie pour", player.Name) return true end warn("❌ Tentative", tentative, "échouée, réessai...") task.wait(1) end warn("🔴 Échec sauvegarde pour", player.Name) return false endConnecte les événements pour charger au join et sauvegarder au leave :
-- Quand un joueur rejoint Players.PlayerAdded:Connect(function(player) local donnees = chargerDonnees(player) cacheJoueurs[player.UserId] = donnees -- Appliquer les attributs pour le HUD player:SetAttribute("Energie", donnees.energie) player:SetAttribute("EnergieMax", donnees.energieMax) player:SetAttribute("Cristaux", donnees.cristaux) player:SetAttribute("JoursSurvecus", donnees.joursSurvecus) end) -- Quand un joueur quitte Players.PlayerRemoving:Connect(function(player) -- Synchroniser les attributs vers le cache local donnees = cacheJoueurs[player.UserId] if donnees then donnees.energie = player:GetAttribute("Energie") or donnees.energie donnees.cristaux = player:GetAttribute("Cristaux") or donnees.cristaux donnees.joursSurvecus = player:GetAttribute("JoursSurvecus") or donnees.joursSurvecus end sauvegarderDonnees(player) cacheJoueurs[player.UserId] = nil -- Libérer la mémoire end) -- Sauvegarde avant fermeture du serveur game:BindToClose(function() for _, player in pairs(Players:GetPlayers()) do sauvegarderDonnees(player) end end)Ajoute une boucle qui sauvegarde automatiquement toutes les 2 minutes :
-- Auto-save toutes les 120 secondes task.spawn(function() while true do task.wait(120) print("🔄 Auto-save en cours...") for _, player in pairs(Players:GetPlayers()) do -- Synchroniser depuis les attributs local donnees = cacheJoueurs[player.UserId] if donnees then donnees.energie = player:GetAttribute("Energie") or donnees.energie donnees.cristaux = player:GetAttribute("Cristaux") or donnees.cristaux donnees.tempsJeuTotal = donnees.tempsJeuTotal + 120 end sauvegarderDonnees(player) end end end)Expose des fonctions pour que les autres scripts puissent modifier les données (cristaux collectés, énergie, etc.) :
-- Module de fonctions publiques local GestionSauvegarde = {} function GestionSauvegarde.ajouterCristaux(player, quantite) local donnees = cacheJoueurs[player.UserId] if donnees then donnees.cristaux = donnees.cristaux + quantite donnees.cristauxTotaux = donnees.cristauxTotaux + quantite player:SetAttribute("Cristaux", donnees.cristaux) end end function GestionSauvegarde.incrementerJour(player) local donnees = cacheJoueurs[player.UserId] if donnees then donnees.joursSurvecus = donnees.joursSurvecus + 1 player:SetAttribute("JoursSurvecus", donnees.joursSurvecus) end end function GestionSauvegarde.getDonnees(player) return cacheJoueurs[player.UserId] end return GestionSauvegardeTables imbriquées → Organisation des donnéespcall() → Gestion d'erreurs sécuriséeDataStore → Persistance des donnéespairs() → Itération sur les tablesBindToClose → Sauvegarde avant fermetureTes joueurs ne perdront plus jamais leur progression ! → Module 10 : Game Loop & Boucle de survie finale