Aller au contenu
Forma Roblox

Rechercher dans la formation

Module 07 · Leçon 2 sur 6

Les Game Passes

Crée un Game Pass dans le Creator Hub, vérifie qui le possède avec UserOwnsGamePassAsync et propose l’achat en jeu pour offrir une traînée dorée exclusive.

  • ⏱️ 35 min
  • 📝 Quiz de 6 questions
  • 🔄 Vérifié le 24 octobre 2026
Sur cette page

🎯 Dans cette leçon, tu vas apprendre à…

  • Créer un Game Pass et récupérer son identifiant
  • Vérifier côté serveur qu’un joueur possède un pass, avec pcall
  • Proposer l’achat en jeu et réagir à la fin de l’achat
  • Appliquer et mémoriser l’avantage du pass de façon fiable

Dans la leçon précédente, tu as vu que l’Obby des Nuages vendrait une traînée dorée exclusive. C’est l’exemple parfait de Game Pass : un achat unique, un avantage permanent, purement cosmétique. Dans cette leçon, tu vas le créer, puis écrire le code qui le vend et le donne, sans jamais faire confiance au client (souviens-toi du module 4).

Game Pass ou Developer Product ?

Roblox propose deux grands types d’objets payants en jeu. Le choix est simple :

  • Game Pass (appelé « pass » dans le Creator Hub) : le joueur paie une seule fois et garde l’avantage pour toujours. Exemples : accès à une zone VIP, un objet d’avatar en jeu, un bonus permanent, notre traînée dorée.
  • Developer Product : le joueur peut l’acheter autant de fois qu’il veut. Exemples : monnaie, potions, « passer l’étape ». On le verra à la leçon 3.

Un bon réflexe : si la phrase « je l’achète une fois et c’est à moi » décrit l’objet, c’est un Game Pass.

Créer le Game Pass dans le Creator Hub

Ton jeu doit d’abord être publié et accessible sur Roblox (tu l’as fait au module 1). Ensuite, d’après la documentation officielle :

  1. Va dans Creations et sélectionne ton Obby des Nuages.
  2. Ouvre Monetization puis Passes.
  3. Clique sur Create pass.
  4. Ajoute une icône : 512 × 512 pixels maximum, au format .jpg, .png ou .bmp. L’icône est découpée en cercle, donc garde l’important au centre.
  5. Donne un nom et une description honnête (« Une traînée dorée suit ton personnage. Purement cosmétique. »).
  6. Choisis une catégorie (elle sert à ranger les objets dans la boutique en jeu).
  7. Clique sur Create pass.

Il te reste à fixer le prix et à mettre le pass en vente depuis la page du pass dans le Creator Hub. La documentation officielle ne détaille plus cette étape (octobre 2026) ; elle mentionne seulement une page Sales du pass, qui affiche notamment ses prix par pays. Les écrans du Creator Hub évoluent souvent : si un libellé a changé, cherche l’option de prix et de mise en vente dans la page du pass.

Récupérer l’identifiant du pass

Pour le script, tu as besoin de l’ID du pass : dans Monetization › Passes, survole la vignette du pass, clique sur le bouton ⋯ puis sur Copy Asset ID. Garde ce nombre de côté.

Les trois fonctions à connaître

Tout passe par le service MarketplaceService (le service des achats en jeu).

Fonction ou événement Rôle
MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId) Renvoie true si le joueur possède le pass. Fonction qui attend une réponse réseau : toujours dans un pcall.
MarketplaceService:PromptGamePassPurchase(player, gamePassId) Affiche la fenêtre d’achat officielle de Roblox au joueur.
MarketplaceService.PromptGamePassPurchaseFinished Événement déclenché quand cette fenêtre se ferme. Il reçoit (player, gamePassId, wasPurchased).

Deux détails de la documentation officielle à retenir :

  • UserOwnsGamePassAsync attend l’UserId du joueur (un nombre), pas l’objet Player.
  • PromptGamePassPurchaseFinished se déclenche aussi quand le joueur annule. Le troisième paramètre, wasPurchased, vaut true seulement si l’achat a réussi. Et cet événement doit être écouté côté serveur : côté client, ses valeurs ne doivent pas servir à valider quoi que ce soit.

Le script serveur : vérifier et donner l’avantage

Voici le cœur du système. Il fait trois choses :

  1. quand un joueur arrive, il vérifie s’il possède déjà le pass ;
  2. quand un joueur termine un achat réussi, il lui donne l’avantage tout de suite ;
  3. à chaque réapparition du personnage, il remet la traînée.

L’avantage est mémorisé dans un attribut du joueur (PossedeTrailDore), que les autres scripts (et l’interface) peuvent lire. Dans ton projet Rojo, ce Script est le fichier src/ServerScriptService/GamePassTrail.server.luau. Tu peux le faire écrire par Claude Code avec le prompt de l’encadré « Avec Claude Code » (après la section sur le bouton), mais un code d’achat se relit toujours : les explications ci-dessous sont ta grille de lecture.

-- Script (côté serveur) à placer dans ServerScriptService, nommé « GamePassTrail »
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local ID_PASS_TRAIL_DORE = 0 -- remplace 0 par l'ID de TON pass (Copy Asset ID)

-- Ajoute une traînée dorée au personnage (si elle n'y est pas déjà)
local function ajouterTrail(character: Model)
	local rootPart = character:WaitForChild("HumanoidRootPart", 5)
	if rootPart == nil or rootPart:FindFirstChild("TrailDore") then
		return
	end

	-- Une traînée a besoin de deux points d'attache (Attachments)
	local haut = Instance.new("Attachment")
	haut.Name = "TrailHaut"
	haut.Position = Vector3.new(0, 1, 0)
	haut.Parent = rootPart

	local bas = Instance.new("Attachment")
	bas.Name = "TrailBas"
	bas.Position = Vector3.new(0, -1, 0)
	bas.Parent = rootPart

	local trail = Instance.new("Trail")
	trail.Name = "TrailDore"
	trail.Attachment0 = haut
	trail.Attachment1 = bas
	trail.Color = ColorSequence.new(Color3.fromRGB(255, 210, 80))
	trail.Lifetime = 0.6
	trail.Parent = rootPart
end

-- Donne l'avantage : on le note dans un attribut, puis on l'applique
local function accorderPass(player: Player)
	player:SetAttribute("PossedeTrailDore", true)
	if player.Character then
		ajouterTrail(player.Character)
	end
end

-- Vérifie la possession du pass, avec quelques nouvelles tentatives
local function verifierPass(player: Player)
	for tentative = 1, 3 do
		local ok, resultat = pcall(function()
			return MarketplaceService:UserOwnsGamePassAsync(player.UserId, ID_PASS_TRAIL_DORE)
		end)
		if ok then
			if resultat == true then
				accorderPass(player)
			end
			return
		end
		warn("Vérification du pass impossible (essai " .. tentative .. ") : " .. tostring(resultat))
		task.wait(2)
	end
end

local function onPlayerAdded(player: Player)
	-- À chaque réapparition, on remet la traînée si le joueur a le pass
	player.CharacterAdded:Connect(function(character)
		if player:GetAttribute("PossedeTrailDore") then
			ajouterTrail(character)
		end
	end)
	verifierPass(player)
end

Players.PlayerAdded:Connect(onPlayerAdded)
-- Joueurs déjà présents si le script démarre en retard
for _, player in Players:GetPlayers() do
	task.spawn(onPlayerAdded, player)
end

-- Achat terminé : on ne donne l'avantage que si l'achat a RÉUSSI
MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, idPass, achete)
	if achete and idPass == ID_PASS_TRAIL_DORE then
		accorderPass(player)
	end
end)

Prends le temps de relire les points clés :

  • pcall entoure UserOwnsGamePassAsync : si Roblox ne répond pas, le script ne plante pas et réessaie deux fois.
  • On compare idPass à notre ID : si tu vends plusieurs passes, chacun doit avoir son propre traitement.
  • L’avantage est appliqué par le serveur. Un exploiteur peut modifier son propre client, pas l’attribut décidé par le serveur.

Le bouton de la boutique : proposer l’achat

Côté client, on affiche le vrai prix et on ouvre la fenêtre d’achat quand le joueur clique. Dans la boutique du module 5, crée un TextButton nommé BoutonTrailDore dans InterfaceObby › Boutique › Liste (le UIGridLayout le range avec les traînées), puis crée dedans un LocalScript nommé ScriptBouton avec ce code. Dans le script serveur et dans celui-ci, remplace 0 par l’ID de ton pass.

-- LocalScript placé dans le TextButton « BoutonTrailDore »
-- (StarterGui > InterfaceObby > Boutique > Liste > BoutonTrailDore)
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local ID_PASS_TRAIL_DORE = 0 -- le même ID que dans le script serveur
local joueur = Players.LocalPlayer
local bouton = script.Parent :: TextButton

-- Affiche le prix réel : il peut varier selon le pays ou une réduction
local ok, infos = pcall(function()
	return MarketplaceService:GetProductInfoAsync(ID_PASS_TRAIL_DORE, Enum.InfoType.GamePass)
end)
if ok and infos.PriceInRobux then
	bouton.Text = "Traînée dorée : " .. infos.PriceInRobux .. " Robux"
end

-- Si le joueur possède déjà le pass, on l'indique sur le bouton
local function mettreAJourBouton()
	if joueur:GetAttribute("PossedeTrailDore") then
		bouton.Text = "Traînée dorée : possédée"
	end
end

bouton.Activated:Connect(function()
	if joueur:GetAttribute("PossedeTrailDore") then
		return -- inutile de proposer un pass déjà possédé
	end
	MarketplaceService:PromptGamePassPurchase(joueur, ID_PASS_TRAIL_DORE)
end)

joueur:GetAttributeChangedSignal("PossedeTrailDore"):Connect(mettreAJourBouton)
mettreAJourBouton()

Ici, le client demande l’achat, mais c’est Roblox qui affiche la fenêtre et encaisse les Robux, puis notre script serveur qui donne la traînée. Le client ne peut rien s’attribuer lui-même.

Mise en cache : ne pas interroger Roblox en boucle

UserOwnsGamePassAsync est déjà mis en cache par Roblox : selon la documentation, les appels répétés répondent plus vite, et le cache est mis à jour quand PromptGamePassPurchaseFinished se déclenche. Un joueur qui rejoint un serveur après avoir acheté le pass est toujours reconnu comme propriétaire.

Notre script ajoute sa propre « mémoire » : l’attribut PossedeTrailDore. Résultat :

  • un seul appel réseau par joueur à son arrivée ;
  • tous les autres scripts (boutique, inventaire du module 5) lisent simplement l’attribut, sans attendre.

Tester ton Game Pass

Tester l’achat d’un pass demande un peu de méthode, car Studio ne simule pas vraiment l’achat de passes. La page officielle du kit « Season Passes » (documentation Roblox, consultée en octobre 2026) le dit clairement : pour tester l’achat d’un pass, il faut publier le jeu et tester dans le client Roblox. Une ancienne annonce du staff Roblox (DevForum, 2016) précisait déjà qu’aucun Robux n’est débité pour un achat fait dans Studio, mais que la vérification de possession d’un pass pouvait y renvoyer une valeur incorrecte.

Procède donc en deux temps :

  1. Dans Studio (Play) : vérifie que le bouton affiche le prix et ouvre la fenêtre d’achat, et que rien ne se passe si tu cliques sur Annuler. Pour tester l’effet lui-même, ajoute temporairement accorderPass(player) au début de onPlayerAdded : la traînée doit apparaître, revenir après une mort (réapparition), et le bouton doit afficher « possédée ». Retire cette ligne avant de publier !
  2. Dans le jeu publié : rejoins le jeu avec un compte qui possède réellement le pass pour tester la vérification à l’arrivée (UserOwnsGamePassAsync), et, si tu veux tester PromptGamePassPurchaseFinished en conditions réelles, fais un véritable achat (il coûte de vrais Robux : si tu es mineur, demande d’abord l’accord d’un parent). Ce dernier test est facultatif : personne n’est obligé de dépenser des Robux pour suivre cette leçon.

Ce qu’il faut retenir

  • Un Game Pass est un achat unique pour un avantage permanent ; on le crée dans Monetization › Passes et on copie son ID avec Copy Asset ID.
  • UserOwnsGamePassAsync(userId, gamePassId) vérifie la possession : toujours dans un pcall, côté serveur, à l’arrivée du joueur.
  • PromptGamePassPurchase(player, gamePassId) ouvre la fenêtre d’achat ; PromptGamePassPurchaseFinished se déclenche même en cas d’annulation : vérifie wasPurchased.
  • L’avantage est appliqué par le serveur et mémorisé (attribut), puis réappliqué à chaque réapparition.
  • Le prix affiché se lit avec GetProductInfoAsync, car il peut varier selon le joueur.

Sources et pour aller plus loin

Quiz : vérifie tes acquis

6 questions · réussite à partir de 70 % · valide chaque réponse pour voir l’explication

  1. Question 1QCMLequel de ces objets doit être vendu comme Game Pass plutôt que comme Developer Product ?
  2. Question 2Vrai ou fauxUserOwnsGamePassAsync peut échouer (problème réseau, service indisponible), il faut donc l’appeler dans un pcall.
  3. Question 3QCMOù faut-il écouter PromptGamePassPurchaseFinished pour donner l’avantage du pass ?
  4. Question 4Trouve le bugCe Script serveur doit donner la traînée dorée uniquement aux joueurs qui ont acheté le pass. Où est le bug ?
    local MarketplaceService = game:GetService("MarketplaceService")
    
    local ID_PASS_TRAIL_DORE = 123456789
    
    local function onAchatTermine(player, idPass, achete)
    	if idPass == ID_PASS_TRAIL_DORE then
    		player:SetAttribute("PossedeTrailDore", true)
    	end
    end
    
    MarketplaceService.PromptGamePassPurchaseFinished:Connect(onAchatTermine)
  5. Question 5Vrai ou fauxLe prix affiché pour un Game Pass peut être différent d’un joueur à l’autre, c’est pourquoi il vaut mieux le lire avec GetProductInfoAsync plutôt que l’écrire à la main dans l’interface.
  6. Question 6Trouve le bugTrouve la faille dans ce code généré par IA. Pour « simplifier », l’IA fait prévenir le serveur par le bouton de la boutique quand la fenêtre d’achat se ferme.
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    local ID_PASS_TRAIL_DORE = 123456789
    local remotes = ReplicatedStorage:WaitForChild("Remotes")
    local passAchete = remotes:WaitForChild("PassAchete") :: RemoteEvent
    
    -- Le LocalScript du bouton appelle passAchete:FireServer(idPass) après l'achat
    passAchete.OnServerEvent:Connect(function(player: Player, idPass: number)
    	if idPass == ID_PASS_TRAIL_DORE then
    		player:SetAttribute("PossedeTrailDore", true)
    	end
    end)