Aller au contenu
Forma Roblox

Rechercher dans la formation

Module 05 · Leçon 4 sur 6

Créer des interfaces lisibles partout

Construis le compteur de pièces et la boutique de l’Obby des Nuages avec ScreenGui, Scale, AnchorPoint et les layouts, pour une interface lisible sur PC comme sur téléphone.

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

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

  • Construire une interface avec ScreenGui, Frame, TextLabel et TextButton
  • Rendre l’interface adaptable à toutes les tailles d’écran avec Scale, AnchorPoint et les contraintes
  • Respecter les zones de sécurité des écrans mobiles
  • Relier l’interface au jeu avec un LocalScript

Ton Obby des Nuages a des pièces et une boutique… mais pour l’instant, il faut la Command Bar pour acheter ! Dans cette leçon, tu construis une interface : un compteur de pièces toujours visible et une fenêtre de boutique. Surtout, tu apprends à la rendre lisible partout : sur un grand écran de PC comme sur un petit téléphone.

Les briques d’une interface

Une interface à l’écran se construit avec des objets « GUI » (Graphical User Interface, interface graphique) :

Objet Rôle
ScreenGui le conteneur : tout ce qu’il contient s’affiche à l’écran
Frame un cadre rectangulaire, pour regrouper d’autres éléments
TextLabel un texte non cliquable
TextButton un bouton avec du texte
ImageLabel une image (icône de pièce, par exemple)

Les ScreenGui se rangent dans StarterGui. Quand le personnage d’un joueur apparaît, Roblox copie leur contenu dans le dossier PlayerGui de ce joueur. Chaque joueur a donc sa propre copie de l’interface, gérée sur son ordinateur (côté client).

ResetOnSpawn : survivre aux chutes

Par défaut, un ScreenGui a ResetOnSpawn = true : il est supprimé et recopié à chaque réapparition du personnage. Dans un obby, on tombe sans arrêt ! Pour notre interface, mets ResetOnSpawn à false : elle n’est créée qu’une fois, et la boutique ne se referme pas toute seule à chaque chute.

Scale ou Offset : la clé de l’adaptation

La position (Position) et la taille (Size) d’un élément sont des UDim2, qui contiennent quatre nombres : {Scale X, Offset X}, {Scale Y, Offset Y}.

  • Scale : une fraction de la taille du parent. 0.5 = 50 %.
  • Offset : un nombre de pixels.

Les deux s’additionnent. Un bouton de taille {0, 200}, {0, 50} fait 200 × 50 pixels sur tous les écrans : correct sur PC, mais il peut prendre la moitié d’un téléphone. Avec {0.2, 0}, {0.08, 0}, il occupe toujours 20 % de la largeur et 8 % de la hauteur.

AnchorPoint : le point d’accroche

L’AnchorPoint indique quel point de l’élément est placé à la Position. Par défaut (0, 0) : le coin supérieur gauche. Pour centrer une fenêtre, mets AnchorPoint = (0.5, 0.5) et Position = {0.5, 0}, {0.5, 0} : le centre de la fenêtre se place au centre de l’écran. Pour un élément collé en bas à droite : AnchorPoint = (1, 1) et Position = {1, 0}, {1, 0}.

Les contraintes qui sauvent ta mise en page

Le Scale seul a un défaut : sur un écran très large, un bouton carré devient un rectangle étiré. Les modificateurs règlent ça. On les ajoute comme enfants d’un élément :

  • UIAspectRatioConstraint : impose un rapport largeur/hauteur (AspectRatio = 1 pour un carré, 3 pour un bandeau trois fois plus large que haut) ;
  • UITextSizeConstraint : impose une taille de texte minimale et maximale ;
  • UICorner : arrondit les coins ;
  • UIPadding : ajoute une marge intérieure.

Les layouts : ranger automatiquement

Un layout place tout seul les éléments frères :

  • UIListLayout : en ligne ou en colonne ;
  • UIGridLayout : en grille de cases de même taille (CellSize), idéal pour une boutique.

Avec un layout, plus besoin de placer chaque bouton à la main : ajoute un objet au catalogue, et il trouve sa place.

Le texte : lisible sans exagérer

La propriété TextScaled agrandit ou réduit le texte pour remplir son cadre. Utile, mais à utiliser avec modération : sur un petit cadre, le texte devient minuscule ; sur un grand écran, énorme. Associe-le à un UITextSizeConstraint. La documentation déconseille une MinTextSize inférieure à 9, illisible pour beaucoup de joueurs.

Pense aussi au contraste : du texte blanc sur un fond clair est pénible à lire. Un contour (UIStroke) ou un fond plus sombre aident beaucoup.

Les zones de sécurité des écrans

Les téléphones modernes ont des encoches (caméra) et des bords arrondis, et Roblox affiche ses propres boutons en haut de l’écran (menu, chat…). La propriété ScreenInsets d’un ScreenGui indique quelle zone ton interface doit respecter. D’après la documentation, au moment de la rédaction :

Valeur Effet Usage conseillé
CoreUISafeInsets (par défaut) évite la barre du haut de Roblox et les encoches interfaces avec des boutons
DeviceSafeInsets évite les encoches, mais pas la barre de Roblox
TopbarSafeInsets place le contenu dans la barre du haut, à droite des boutons de Roblox petits indicateurs dans la barre
None aucune marge : tout l’écran fonds d’écran, éléments non interactifs

Deux autres propriétés du ScreenGui existent : ClipToDeviceSafeArea (activée par défaut, elle coupe ce qui déborde de la zone sûre de l’appareil) et SafeAreaCompatibility, un mode de compatibilité pour les anciennes interfaces plein écran, que la documentation conseille d’éviter pour un nouveau projet au profit de ScreenInsets. Pour notre interface : laisse CoreUISafeInsets. On reviendra sur le mobile au module 10.

Construire l’interface de l’Obby des Nuages

Dans StarterGui, crée cette hiérarchie. Pour chaque objet, survole son futur parent dans l’Explorer, clique sur ⊕ (ou clic droit › Insert Object) et choisis le type indiqué entre parenthèses. Renomme ensuite l’objet exactement comme dans le schéma : le script les retrouve par leur nom.

StarterGui
└── InterfaceObby            (ScreenGui, ResetOnSpawn = false)
    ├── CompteurPieces       (Frame)
    │   ├── UICorner
    │   ├── UIAspectRatioConstraint   (AspectRatio = 3)
    │   └── Texte            (TextLabel)
    │       └── UITextSizeConstraint  (MinTextSize = 12)
    ├── BoutonBoutique       (TextButton, Text = « Boutique »)
    ├── Boutique             (Frame, Visible = false)
    │   ├── UICorner
    │   ├── UIPadding
    │   └── Liste            (Frame transparent)
    │       └── UIGridLayout
    └── ScriptInterface      (LocalScript)

Réglages conseillés (sélectionne l’objet, puis modifie ses propriétés dans la fenêtre Properties) :

  • InterfaceObby : décoche ResetOnSpawn.
  • CompteurPieces : AnchorPoint = (0, 0), Position = {0.02, 0}, {0.03, 0}, Size = {0.2, 0}, {0.08, 0}. Le UIAspectRatioConstraint le garde trois fois plus large que haut.
  • Texte : Size = {1, 0}, {1, 0} (tout le cadre), BackgroundTransparency = 1, TextScaled = true, et son enfant UITextSizeConstraint avec MinTextSize = 12.
  • BoutonBoutique : AnchorPoint = (0, 0.5), Position = {0.02, 0}, {0.5, 0}, Size = {0.15, 0}, {0.08, 0}, Text = Boutique : sur le bord gauche, à mi-hauteur. Évite les coins inférieurs : d’après la documentation, sur mobile, Roblox y affiche le joystick (en bas à gauche) et le bouton de saut (en bas à droite), et un bouton placé là serait caché par le pouce ou touché par erreur. On approfondira le mobile au module 10.
  • Boutique : AnchorPoint = (0.5, 0.5), Position = {0.5, 0}, {0.5, 0}, Size = {0.6, 0}, {0.6, 0}, et décoche Visible (la fenêtre est cachée au départ).
  • Liste : Size = {1, 0}, {1, 0}, BackgroundTransparency = 1. Dans son UIGridLayout : CellSize = {0.3, 0}, {0.4, 0} et CellPadding = {0.03, 0}, {0.03, 0}.

Les boutons des objets ne sont pas créés à la main : le script les fabrique à partir du Catalogue (leçon 3).

Le LocalScript qui relie tout

L’interface est côté client : on utilise donc un LocalScript. Placé dans un ScreenGui de StarterGui, il s’exécute une fois copié dans le PlayerGui du joueur. Crée le LocalScript ScriptInterface directement dans InterfaceObby et colle ce code. Tu peux aussi supprimer le LocalScript TestInventaire de la leçon 3 : l’interface le remplace.

-- LocalScript « ScriptInterface » dans StarterGui > InterfaceObby (côté client)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Catalogue = require(ReplicatedStorage:WaitForChild("Modules"):WaitForChild("Catalogue"))
local remotes = ReplicatedStorage:WaitForChild("Remotes")
local acheterObjet = remotes:WaitForChild("AcheterObjet") :: RemoteEvent
local equiperObjet = remotes:WaitForChild("EquiperObjet") :: RemoteEvent

local joueur = Players.LocalPlayer
local interface = script.Parent
local texteCompteur = interface:WaitForChild("CompteurPieces"):WaitForChild("Texte") :: TextLabel
local boutonBoutique = interface:WaitForChild("BoutonBoutique") :: TextButton
local fenetreBoutique = interface:WaitForChild("Boutique") :: Frame
local liste = fenetreBoutique:WaitForChild("Liste") :: Frame

-- 1. LE COMPTEUR : on lit les leaderstats (créées par le serveur)
local pieces = joueur:WaitForChild("leaderstats"):WaitForChild("Pieces") :: IntValue

local function majCompteur()
	texteCompteur.Text = `Pièces : {pieces.Value}`
end

pieces.Changed:Connect(majCompteur) -- à chaque changement de valeur
majCompteur() -- et une première fois au démarrage

-- 2. OUVRIR / FERMER LA BOUTIQUE
-- Activated fonctionne au clic de souris comme au toucher sur mobile
boutonBoutique.Activated:Connect(function()
	fenetreBoutique.Visible = not fenetreBoutique.Visible
end)

-- 3. LES BOUTONS DES OBJETS, fabriqués à partir du catalogue
local boutons: { [string]: TextButton } = {}

-- Texte d'un bouton selon l'état publié par le serveur (attributs, leçon 3)
local function texteBouton(idObjet: string): string
	local objet = Catalogue[idObjet]
	if joueur:GetAttribute("TrailEquipe") == idObjet then
		return `{objet.nom}\nÉquipé`
	elseif joueur:GetAttribute("Possede_" .. idObjet) == true then
		return `{objet.nom}\nÉquiper`
	end
	return `{objet.nom}\n{objet.prix} pièces`
end

local function majBoutons()
	for idObjet, bouton in boutons do
		bouton.Text = texteBouton(idObjet)
	end
end

for idObjet, objet in Catalogue do
	local bouton = Instance.new("TextButton")
	bouton.Name = idObjet
	bouton.BackgroundColor3 = objet.couleurDebut
	bouton.TextColor3 = Color3.fromRGB(30, 30, 50) -- texte foncé : bon contraste
	bouton.TextScaled = true

	local coins = Instance.new("UICorner")
	coins.Parent = bouton

	local tailleTexte = Instance.new("UITextSizeConstraint")
	tailleTexte.MinTextSize = 12
	tailleTexte.MaxTextSize = 28
	tailleTexte.Parent = bouton

	-- Le client DEMANDE ; c'est le serveur qui vérifie et décide
	bouton.Activated:Connect(function()
		if joueur:GetAttribute("TrailEquipe") == idObjet then
			equiperObjet:FireServer("") -- déjà équipé : on retire
		elseif joueur:GetAttribute("Possede_" .. idObjet) == true then
			equiperObjet:FireServer(idObjet)
		else
			acheterObjet:FireServer(idObjet)
		end
	end)

	bouton.Parent = liste -- le UIGridLayout le range automatiquement
	boutons[idObjet] = bouton
	joueur:GetAttributeChangedSignal("Possede_" .. idObjet):Connect(majBoutons)
end

joueur:GetAttributeChangedSignal("TrailEquipe"):Connect(majBoutons)
majBoutons()

Remarque bien : ce script n’ajoute jamais de pièces et ne donne jamais d’objet. Il affiche ce que le serveur a décidé (leaderstats, attributs) et envoie des demandes. Même si un exploiteur modifie ce script, il ne peut que demander, et le serveur refuse ce qui n’est pas valide.

Tester sur plusieurs écrans

Studio peut afficher ton jeu comme sur un téléphone ou une tablette, en portrait et en paysage, avec le Device Simulator : une barre d’outils placée au-dessus de la vue 3D, que tu ouvres depuis le menu Test › Device Simulator (au moment de la rédaction, octobre 2026). Son fonctionnement, son statut (sorti de bêta selon l’annonce officielle du 16 octobre 2026) et ses limites sont détaillés au module 10, dans Penser mobile d’abord. Vérifie au minimum :

  • que le compteur et le bouton restent visibles et ne passent pas sous la barre de Roblox, ni sous le joystick ou le bouton de saut ;
  • que le texte reste lisible sur le plus petit appareil ;
  • que les boutons sont assez grands pour un pouce.

Ce qu’il faut retenir

  • Un ScreenGui dans StarterGui est copié dans le PlayerGui de chaque joueur ; mets ResetOnSpawn = false pour une interface qui doit survivre aux chutes.
  • Le Scale (fraction du parent) rend l’interface adaptable ; l’Offset (pixels) est à réserver aux petits ajustements.
  • AnchorPoint choisit le point d’accroche ; UIAspectRatioConstraint, UITextSizeConstraint, UICorner, UIPadding et les layouts font le reste.
  • ScreenInsets = CoreUISafeInsets (par défaut) protège tes boutons des encoches et de la barre de Roblox.
  • Le LocalScript d’interface affiche l’état décidé par le serveur et envoie des demandes, sans jamais rien décider.

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 1QCMUn Frame a la taille {0.5, 0}, {0.1, 0}. Que se passe-t-il quand l’écran s’agrandit ?
  2. Question 2QCMComment centrer parfaitement une fenêtre de boutique à l’écran ?
  3. Question 3QCMQuelle valeur de ScreenInsets la documentation recommande-t-elle pour un ScreenGui qui contient des boutons ?
  4. Question 4Trouve le bugCe LocalScript doit afficher le nombre de pièces du joueur. Il provoque une erreur. Où est le bug ?
    local Players = game:GetService("Players")
    local joueur = Players.LocalPlayer
    local texte = script.Parent:WaitForChild("Texte") :: TextLabel
    local pieces = joueur:WaitForChild("leaderstats"):WaitForChild("Pieces") :: IntValue
    
    local function majCompteur()
    	texte.Text = "Pièces : " .. pieces
    end
    
    pieces.Changed:Connect(majCompteur)
    majCompteur()
  5. Question 5Vrai ou fauxActiver TextScaled sur tous les textes, sans contrainte, est la meilleure façon de garantir une interface lisible.
  6. Question 6Améliore ce promptTu veux que Claude Code crée l’interface de la boutique. Quelle version du prompt donnera le meilleur résultat ?
    Le prompt de départ
    fais-moi une jolie interface de boutique

    Choisis la meilleure version de ce prompt.