Tables & Sauvegardes

Objectif : Maîtriser les tables (tableaux/dictionnaires) et apprendre à sauvegarder les données des joueurs avec DataStore. Leur progression ne sera plus jamais perdue !

Stocker et Retrouver

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 !

1. Les Tableaux (Arrays)

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)  -- 4

1.2 Parcourir un Tableau

local 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 vs pairs

ipairs ? Pour les tableaux (indices numériques ordonnés)
pairs ? Pour les dictionnaires (clés personnalisées)

2. Les Dictionnaires

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 = nil

2.2 Parcourir un Dictionnaire

local 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 = 5

2.3 Tables Imbriquées

Tu 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"

3. Sauvegarder avec DataStore

Le DataStoreService permet de sauvegarder des données sur les serveurs Roblox. Quand le joueur revient, ses données sont restaurées !

⚠️ Important

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.

3.1 Structure de Base

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)

3.2 Sauvegarder à la Déconnexion

-- 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)

Pourquoi pcall() ?

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 !

3.3 UpdateAsync (Plus Sûr)

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)
💻 Sandbox Luau
Simulateur v2.0
1 2 3 4 5 6 7 8 9 10
💡 Expérimente avec les tables...

Quiz : Tables & Sauvegardes

5 questions pour valider tes acquis

1. À quel index commence un tableau en Luau ?

  • 0
  • 1
  • -1
  • Ça dépend

2. Quelle fonction ajoute un élément à la fin d'un tableau ?

  • table.add()
  • table.push()
  • table.insert()
  • table.append()

3. Quelle boucle utilise-t-on pour les dictionnaires ?

  • for i = 1, #table do
  • ipairs()
  • pairs()
  • foreach()

4. Quelle méthode charge les données depuis un DataStore ?

  • :LoadAsync()
  • :GetAsync()
  • :ReadAsync()
  • :FetchAsync()

5. Pourquoi utilise-t-on pcall() avec les DataStores ?

  • Pour aller plus vite
  • C'est obligatoire
  • Pour gérer les erreurs sans crash
  • Pour sauvegarder plusieurs joueurs
Exercice Pratique

Configure le TweenService

Place les étapes dans l'ordre pour créer une animation fluide !

15-50 Points
TweenInfo.new()
Configurer l'animation
tween:Play()
Lancer l'animation
GetService("TweenService")
Importer le service
:Create(part, info, goal)
Créer le tween
1
Étape initiale...
2
Définition...
3
Création...
4
Exécution...
💾
Étape 9/10

Collecte & Sauvegarde

Implémente la collecte des ressources et sauvegarde la progression du joueur pour qu'il ne perde rien en quittant !

1
2
3
4
5
6
7
8
9
10
Prérequis

Modules 1 à 8 complétés (DataStore, Tables)

Système de Données

1
Création des Collectibles

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)
2
Script de Sauvegarde (DataStore)

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)
Bonus : Auto-Save

Ajoute une boucle while true do pour sauvegarder automatiquement toutes les 60 secondes !

Sauvegarde opérationnelle !

Prochaine étape ? Module 10 : Animations & Publication

💾
🚀 SURVIE SPATIALE - Étape 9/10

Système de Sauvegarde Avancé

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 !

1
2
3
4
5
6
7
8
9
10
Objectif : Créer un système de sauvegarde robuste avec tables, DataStore et pcall pour protéger les données des joueurs.
1
📋 Définir la Structure des Données

On va sauvegarder toutes les stats du joueur dans une table organisée. Crée un script GestionSauvegarde dans ServerScriptService :

📁 ServerScriptService
📜 GestionSauvegarde
📜 GestionnaireRessources
-- 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 }
Tables imbriquées : ameliorations est une table dans une table ! C'est parfait pour organiser des catégories de données.
2
📥 Charger les Données du Joueur

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 end
fusionnerTables est crucial ! Si tu ajoutes un nouveau champ dans DONNEES_DEFAUT plus tard, les anciens joueurs l'auront automatiquement avec la valeur par défaut.
3
💾 Sauvegarder les Données

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 end
4
🔗 Gérer Connexion/Déconnexion

Connecte 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)
5
⏱️ Auto-Save Périodique

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)
6
🔧 API pour les Autres Scripts

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 GestionSauvegarde
Concepts du cours appliqués :
Tables imbriquées → Organisation des données
pcall() → Gestion d'erreurs sécurisée
DataStore → Persistance des données
pairs() → Itération sur les tables
BindToClose → Sauvegarde avant fermeture
Bonus : Améliorations
  • Ajoute un système de récompense basé sur les jours survécus absents (bonus de reconnexion)
  • Crée un leaderboard des joueurs avec le plus de jours survécus
  • Implémente une boutique d'améliorations qui utilise les cristaux
  • Ajoute la sauvegarde de la position du joueur dans la station
💾

Données sécurisées !

Tes joueurs ne perdront plus jamais leur progression ! → Module 10 : Game Loop & Boucle de survie finale