Catégories
Jeu vidéo ROBLOX

Un système de Checkpoints

Tu es le maître dun jeu d’un parcours d’obstacles (un Obby). Ton rôle est de surveiller les joueurs, de noter leur progression, et de les renvoyer au bon endroit s’ils tombent dans le vide.

Crée dans ton jeu des CheckPoints sur ton parcours, les checkpoints seront visibles ou non, puis programme un script qui reconnaitre tous les checkpoints et notera pour chaque joueur sa progression. En cas de respawn le joueur reviendra sur le dernier checkpoint validé.

Au tout début du script, on trouve une boîte appelée CONFIG. C’est le « panneau de contrôle » du jeu. C’est ici que tu peux changer facilement les règles sans toucher au reste du code.

  1. Quand un joueur rejoint le jeu (PlayerAdded) :
    • On initialise ses données (il commence au Checkpoint 1).
    • On écoute quand son personnage apparaît (CharacterAdded) pour le téléporter au bon checkpoint (au cas où il meurt et respawn).
  2. Quand un joueur quitte le jeu (PlayerRemoving) :
    • On supprime ses données pour libérer de la mémoire.
  3. Au démarrage du script :
    • On initialise tous les joueurs déjà présents.
    • On connecte tous les checkpoints.

Tu places 5 checkpoints dans un dossier appelé « Checkpoints » dans Workspace :

  • Checkpoint1
  • Checkpoint2
  • Checkpoint3
  • Checkpoint4
  • Checkpoint5
  1. Un joueur (123CODAGE) rejoint le jeu :
    • Ses données : { indexActuel = 0, checkpointPart = Checkpoint1 }.
    • Il spawn au Checkpoint1.
  2. 123CODAGE touche Checkpoint2 :
    • Le script vérifie : indexActuel + 1 = 12 == 1 + 1OK !
    • indexActuel passe à 2.
    • Checkpoint2 devient vert.
    • Message : « 123CODAGE a atteint le checkpoint 2/5″.
  3. 123CODAGE tombe dans le vide :
    • Il respawn au Checkpoint2 (dernier validé).
  4. 123CODAGE touche Checkpoint4 sans faire le 3 :
    • Le script vérifie : indexActuel + 1 = 34 != 3Bloqué ! (car sauvegarderOrdre = true).
  5. 123CODAGE termine tous les checkpoints :
    • Message : « 123CODAGE a terminé le parcours ! » 🎉

Saisie ce script CheckpointsService sous ServerScriptService :

-- Script : GestionCheckpoints
local Players = game:GetService("Players")
local DialogueModule = require(game.ReplicatedStorage.DialogueModule)

-- ═══════════════════════════════════════
-- CONFIGURATION
-- ═══════════════════════════════════════
local CONFIG = {
	dossierCheckpoints = "Checkpoints",  -- nom du dossier dans Workspace
	offsetRespawn      = Vector3.new(0, 3, 0), -- hauteur au-dessus du checkpoint
	afficherMessages   = true,
	sauvegarderOrdre   = true,  -- respecte l'ordre numérique des checkpoints
	checkpointValidated = BrickColor.Green(),
	checkpointInitial = BrickColor.DarkGray()
}

-- ═══════════════════════════════════════
-- RÉCUPÉRATION DES CHECKPOINTS
-- ═══════════════════════════════════════
local dossier = workspace:WaitForChild(CONFIG.dossierCheckpoints)

local function getCheckpointsTries()
	local liste = {}
	for _, cp in ipairs(dossier:GetChildren()) do
		if cp:IsA("BasePart") or cp:IsA("Model") then
			table.insert(liste, cp)
		end
	end
	-- Trier par nom numérique (Checkpoint1, Checkpoint2...)
	table.sort(liste, function(a, b)
		local numA = tonumber(a.Name:match("%d+")) or 0
		local numB = tonumber(b.Name:match("%d+")) or 0
		return numA < numB
	end)
	return liste
end

local checkpoints = getCheckpointsTries()

-- ═══════════════════════════════════════
-- DONNÉES PAR JOUEUR
-- ═══════════════════════════════════════
local progressionJoueurs = {}
-- { [userId] = { indexActuel = 1, checkpointPart = part } }

local function getPositionCheckpoint(cp)
	if cp:IsA("Model") then
		local primary = cp.PrimaryPart or cp:FindFirstChildOfClass("BasePart")
		return primary and primary.Position or Vector3.new(0, 0, 0)
	end
	return cp.Position
end

-- ═══════════════════════════════════════
-- RESPAWN AU DERNIER CHECKPOINT
-- ═══════════════════════════════════════
local function respawnAuCheckpoint(player)

	local data = progressionJoueurs[player.UserId]
	if not data then return end

	local character = player.Character
	if not character then return end

	local rootPart = character:FindFirstChild("HumanoidRootPart")
	if not rootPart then return end

	local position = getPositionCheckpoint(data.checkpointPart)
	rootPart.CFrame = CFrame.new(position + CONFIG.offsetRespawn)

end

-- ═══════════════════════════════════════
-- VALIDATION D'UN CHECKPOINT
-- ═══════════════════════════════════════
local function validerCheckpoint(player, indexCP)
	local data = progressionJoueurs[player.UserId]
	if not data then return false end

	-- Vérifier l'ordre si activé
	if CONFIG.sauvegarderOrdre and indexCP ~= data.indexActuel + 1 then
		return false -- ignore si ce n'est pas le suivant
	end

	if indexCP > #checkpoints or indexCP <= data.indexActuel then return false end
		
	data.indexActuel     = indexCP
	data.checkpointPart  = checkpoints[indexCP]

	if CONFIG.afficherMessages then
		local character = player.Character
		DialogueModule.creerDialogue(character, "🏁 " .. player.Name .. " a atteint le checkpoint " .. indexCP .. "/" .. #checkpoints)
	end

	-- Dernier checkpoint = fin du parcours
	if indexCP == #checkpoints then
		if CONFIG.afficherMessages then
			local character = player.Character
			DialogueModule.creerDialogue(character, "🎉 " .. player.Name .. " a terminé le parcours !")
		end
		-- Ajoute ici ta logique de fin (récompense, téléportation...)
	end
	
	return true

end

-- ═══════════════════════════════════════
-- CONNEXION DES TOUCHES DE CHAQUE CHECKPOINT
-- ═══════════════════════════════════════
local function connecterCheckpoints()
	for index, cp in ipairs(checkpoints) do
		local part = cp:IsA("Model") and (cp.PrimaryPart or cp:FindFirstChildOfClass("BasePart")) or cp

		if part then
			-- Couleur initiale pour visualiser l'ordre
			if CONFIG.checkpointInitial then
				part.BrickColor = CONFIG.checkpointInitial
			end

			part.Touched:Connect(function(hit)
				local character = hit.Parent
				local player = Players:GetPlayerFromCharacter(character)
				if not player then return end

				local data = progressionJoueurs[player.UserId]
				if not data then return end

				-- Déjà validé
				if data.indexActuel >= index then return end

				if validerCheckpoint(player, index) then

					-- Allumer le checkpoint pour ce joueur (visuel global simplifié)
					if CONFIG.checkpointValidated then
						part.BrickColor = CONFIG.checkpointValidated
					end
				end
			end)
		end
	end
end

-- 
-- GESTION DES JOUEURS
-- 
local function initialiserJoueur(player)
	progressionJoueurs[player.UserId] = {
		indexActuel    = 0,
		checkpointPart = checkpoints[1], -- spawn initial = checkpoint 1
	}

	player.CharacterAdded:Connect(function(character)
		task.wait(0.2) -- laisser le personnage se charger
		respawnAuCheckpoint(player)
	end)
end

-- supprimer la progression du joueur
local function nettoyerJoueur(player)
	progressionJoueurs[player.UserId] = nil
end

-- 
-- LANCEMENT
--
Players.PlayerAdded:Connect(initialiserJoueur)
Players.PlayerRemoving:Connect(nettoyerJoueur)

for _, player in ipairs(Players:GetPlayers()) do
	initialiserJoueur(player)
end

connecterCheckpoints()

Rangement des Checkpoints (getCheckpointsTries)

Dans Roblox, quand le jeu se lance, les objets ne s’installent pas toujours dans l’ordre. Le script doit donc faire le tri.

  • Il va chercher tous les blocs dans le dossier « Checkpoints ».
  • Il regarde leur nom (ex: « Checkpoint1 », « Checkpoint2 », « Checkpoint3 »).
  • Il extrait les chiffres grâce à une formule magique (%d+) et les trie du plus petit au plus grand dans une liste ordonnée

 Le carnet de notes du jeu (progressionJoueurs)

Le jeu a besoin d’une mémoire pour savoir où en est chaque joueur. Pour cela, il utilise un tableau (comme une liste de classe) :

lualocal progressionJoueurs = {}

Quand un joueur se connecte, le script lui crée une fiche :

  • Le bloc de réapparition (le tout premier checkpoint).
  • Son niveau actuel (commence à 0).

Le Respawn (La réapparition après une chute)

Quand ton personnage meurt ou réapparaît, la fonction respawnAuCheckpoint se déclenche :

  1. Le script cherche le nom du joueur dans son carnet de notes.
  2. Il trouve la position 3D (X, Y, Z) du dernier checkpoint validé.
  3. Il téléporte le joueur à cette position, en le surélevant légèrement (grâce à l’offset de 3 blocs défini dans la configuration) pour qu’il ne s’enfonce pas dans le bloc.

Toucher et Valider un Checkpoint (validerCheckpoint)

C’est ici que la magie opère quand tes pieds touchent une plaque :

  • Le détecteur (Touched) : Chaque checkpoint possède un capteur invisible. Dès qu’un joueur marche dessus, le script se réveille.
  • La vérification anti-triche : Si sauvegarderOrdre est activé, le script vérifie si le checkpoint touché est bien le suivant dans la liste (par exemple, passer du 2 au 3). Si tu essaies de sauter directement du 1 au 3, le jeu t’ignore !
  • La mise à jour : Si tout est bon :
    1. Le jeu met à jour ta fiche dans le carnet.
    2. Le checkpoint change de couleur (il devient vert pour te montrer visuellement que c’est bon).
    3. Un message s’affiche dans le chat grâce à un module de dialogue (ex:  » Joueur1 a atteint le checkpoint 3/5″).
    4. Si c’est le tout dernier checkpoint, une fête se déclenche ( » Joueur1 a terminé le parcours ! »).

L’arrivée et le départ des joueurs

Tout en bas du script, le jeu surveille les connexions :

  • Un joueur arrive (PlayerAdded) : On crée sa fiche de suivi et on prépare son personnage pour qu’il soit téléporté à son dernier checkpoint à chaque fois qu’il réapparaît.
  • Un joueur s’en va (PlayerRemoving) : On efface sa fiche du carnet de notes pour ne pas encombrer inutilement la mémoire du serveur du jeu (c’est le nettoyage).

Ce qu’il faut retenir (les concepts clés de programmation) :

  1. Les Variables (local ...) : Des boîtes pour ranger des informations (la couleur des blocs, les coordonnées…).
  2. Les Tableaux ({}) : Des listes pour ranger des groupes d’objets (les checkpoints dans l’ordre) ou des informations complexes (la liste des joueurs et leur niveau).
  3. Les Événements (.Touched.PlayerAdded) : Des déclencheurs qui disent au script : « Hé ! Quelque chose s’est passé, exécute le code maintenant ! »