Objectif : Maîtriser les animations fluides avec TweenService, créer des interactions immersives avec ProximityPrompt, et publier ton jeu pour le monde entier !
Les détails qui font la différence
Tu as appris à coder des systèmes puissants. Maintenant, on va les rendre beaux et fluides ! TweenService pour les animations, ProximityPrompt pour les interactions, et enfin : la publication !
À ce stade, l'objectif n'est plus seulement "que ça fonctionne" mais que l'expérience soit propre, fluide et compréhensible pour un vrai joueur. Le polish, c'est la différence entre un prototype et un jeu qu'on recommande.
Avant la publication, vérifie toujours : clarté de l'objectif, feedback visuel/sonore, performances stables, absence de blocages critiques, et parcours joueur lisible dès la première minute.
1) Onboarding rapide (le joueur comprend quoi faire).
2) UX cohérente (prompts, boutons, messages).
3) Tests solo + multijoueur sans erreur bloquante.
4) Boucle de jeu claire et gratifiante.
Les animations, prompts et retours sonores ne sont pas du "bonus" : ils guident le joueur, réduisent la frustration et augmentent les chances qu'il revienne jouer.
Utilise le polish pour clarifier l'action, pas pour distraire. Chaque effet doit avoir une fonction : signaler, confirmer, orienter ou récompenser.
Définition : Système d'animation fluide de propriétés.
Pourquoi : Améliorer la lisibilité des actions et le ressenti.
Quand l'utiliser : Portes, transitions UI, feedbacks visuels, objets dynamiques.
Anti-pattern : Animer tout en permanence sans priorité UX.
Exercice guidé : Anime une porte avec ouverture, fermeture et état cohérent.
Résumé : L'animation doit servir la compréhension du joueur.
Définition : Interaction contextuelle par proximité.
Pourquoi : Rendre les interactions claires sans UI intrusive.
Quand l'utiliser : Coffres, PNJ, leviers, stations de craft.
Anti-pattern : Prompts partout avec distances excessives.
Exercice guidé : Configure un prompt avec validation serveur et feedback visuel.
Résumé : Un prompt bien calibré fluidifie l'exploration et l'action.
Définition : Passage du prototype local à une expérience publique.
Pourquoi : Garantir qualité, sécurité et stabilité avant diffusion.
Quand l'utiliser : Après cycle complet de tests fonctionnels et UX.
Anti-pattern : Publier sans checklist ni plan de suivi.
Exercice guidé : Réalise une checklist de sortie puis corrige 3 points critiques.
Résumé : Une bonne publication est un processus, pas un clic.
TweenService crée des transitions douces entre deux valeurs. Fini les mouvements brusques ! Parfait pour portes, UI, effets visuels...
Structure d'un Tween
local TweenService = game:GetService("TweenService")
local part = script.Parent
-- 1. Configurer les options du tween
local tweenInfo = TweenInfo.new(
2, -- Durée (secondes)
Enum.EasingStyle.Quad, -- Style d'animation
Enum.EasingDirection.Out, -- Direction
0, -- Répétitions (0 = une fois)
false, -- Reverse ?
0 -- Délai avant de démarrer
)
-- 2. Définir les propriétés finales
local goal = {
Position = Vector3.new(0, 10, 0),
Transparency = 0.5
}
-- 3. Créer et jouer le tween
local tween = TweenService:Create(part, tweenInfo, goal)
tween:Play()
L'EasingStyle définit la courbe de l'animation :
Vitesse constante
Accélération douce
Effet rebond
Effet élastique
local TweenService = game:GetService("TweenService")
local door = script.Parent
local isOpen = false
local openPosition = door.Position + Vector3.new(0, 7, 0)
local closedPosition = door.Position
local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
local function toggleDoor()
local goal
if isOpen then
goal = {Position = closedPosition}
else
goal = {Position = openPosition}
end
local tween = TweenService:Create(door, tweenInfo, goal)
tween:Play()
isOpen = not isOpen
end
-- Connecte à un ClickDetector ou ProximityPrompt
door.ClickDetector.MouseClick:Connect(toggleDoor)
TweenService marche aussi sur les GuiObject ! Tu peux
animer Position, Size,
BackgroundTransparency
pour des interfaces fluides.
On a vu les bases du ProximityPrompt. Voyons maintenant les options avancées pour des interactions immersives !
Configuration complète
local prompt = Instance.new("ProximityPrompt")
prompt.Parent = script.Parent
-- Texte et apparence
prompt.ActionText = "Ouvrir" -- Texte du bouton
prompt.ObjectText = "Coffre au trésor" -- Nom de l'objet
prompt.KeyboardKeyCode = Enum.KeyCode.E -- Touche clavier
-- Comportement
prompt.HoldDuration = 0.5 -- Temps à maintenir (0 = instant)
prompt.MaxActivationDistance = 10 -- Distance d'activation
prompt.RequiresLineOfSight = true -- Doit voir l'objet
-- Style
prompt.Style = Enum.ProximityPromptStyle.Default -- ou Custom
prompt.UIOffset = Vector2.new(0, -50) -- Décalage de l'UI
-- Événements
prompt.Triggered:Connect(function(player)
print(player.Name .. " a interagi !")
end)
prompt.PromptShown:Connect(function(style)
print("Prompt visible")
end)
prompt.PromptHidden:Connect(function()
print("Prompt caché")
end)
-- Script dans un collectible (pièce, gemme...)
local item = script.Parent
local prompt = item:FindFirstChild("ProximityPrompt")
prompt.ActionText = "Ramasser"
prompt.ObjectText = "Pièce d'or (+10)"
prompt.HoldDuration = 0
prompt.Triggered:Connect(function(player)
-- Ajoute les coins
local coins = player.leaderstats.Coins
coins.Value = coins.Value + 10
-- Effet de ramassage
local sound = Instance.new("Sound")
sound.SoundId = "rbxassetid://12345678" -- ID du son
sound.Parent = item
sound:Play()
-- Animation de disparition
local TweenService = game:GetService("TweenService")
local tween = TweenService:Create(item, TweenInfo.new(0.3), {
Transparency = 1,
Size = Vector3.new(0, 0, 0)
})
tween:Play()
tween.Completed:Wait()
item:Destroy()
end)
Ton jeu est prêt ! Voici comment le partager avec le monde entier.
File ? Publish to Roblox (ou Ctrl+Shift+P). Donne un nom et une description à ton jeu.
Home ? Game Settings. Configure les permissions, l'icône, les thumbnails, et les options de monétisation.
Dans Game Settings ? Permissions, passe le jeu de "Private" à "Public".
L'apparence de ta page de jeu attire les joueurs ! Voici les tailles recommandées :
512 x 512 px
1920 x 1080 px
30 sec max
Types de monétisation
Tu as terminé la formation LearnBlox ! Tu sais maintenant créer des jeux Roblox avec des scripts Luau, des interfaces, des sauvegardes, et plus encore. Continue à apprendre et à créer !
5 questions pour valider tes acquis
Ajoute du "Juice" à ton jeu avec des animations fluides (TweenService) et publie-le pour que le monde entier puisse y jouer !
Modules 1 à 9 complétés (TweenService)
Modifie ton script HUD (Module 8) pour utiliser
TweenService
au lieu de changer la taille instantanément.
Fais flotter tes objets (Oxygène, etc.) dans les airs !
File > Publish to Roblox
🎉 Ton jeu est maintenant en ligne sur Roblox !
Ajoute une musique d'ambiance et des sons quand tu ramasses un
objet ! Utilise SoundService.
Tu es maintenant un développeur Roblox confirmé. Bravo !
Le moment est venu d'assembler tous les systèmes ! Crée la boucle de survie avec cycle jour/nuit, événements aléatoires, et écran de victoire à 100 jours.
Un "jour" dans le jeu dure 60 secondes. Crée un
script GameLoop dans
ServerScriptService :
-- GameLoop.lua - Le cœur du jeu !
local
Players =
game:GetService("Players") local
ReplicatedStorage =
game:GetService("ReplicatedStorage") local
Lighting =
game:GetService("Lighting") local
TweenService =
game:GetService("TweenService")
-- Configuration
local DUREE_JOUR =
60
-- Secondes par jour
local JOUR_VICTOIRE =
100
-- Jours pour gagner
local ENERGIE_PAR_JOUR =
15
-- Énergie consommée par jour
-- RemoteEvents
local nouveauJourEvent =
Instance.new("RemoteEvent") nouveauJourEvent.Name =
"NouveauJour"
nouveauJourEvent.Parent =
ReplicatedStorage
local victoireEvent =
Instance.new("RemoteEvent") victoireEvent.Name =
"Victoire" victoireEvent.Parent =
ReplicatedStorage
local gameOverEvent =
Instance.new("RemoteEvent") gameOverEvent.Name =
"GameOver" gameOverEvent.Parent =
ReplicatedStorage
Utilise TweenService pour faire tourner le soleil et créer une ambiance dynamique :
-- FONCTION : Animer le passage du temps
local function
animerCycleJour()
-- Matin (6h) → Midi (12h) → Soir (18h) → Nuit (0h)
local tempsDepart =
Lighting.ClockTime
local tweenInfo =
TweenInfo.new( DUREE_JOUR,
-- Durée totale
Enum.EasingStyle.Linear,
-- Linéaire
Enum.EasingDirection.InOut )
-- Faire avancer l'horloge de 24h
local tween =
TweenService:Create(Lighting, tweenInfo, { ClockTime = tempsDepart +
24 }) tween:Play() tween.Completed:Wait()
-- Remettre à 0 pour éviter des valeurs énormes
Lighting.ClockTime = tempsDepart
end
Crée la boucle principale qui gère chaque jour, consomme l'énergie et vérifie les conditions de victoire/défaite :
-- FONCTION : Traiter un nouveau jour pour un joueur
local function
traiterNouveauJour(player)
local energie = player:GetAttribute("Energie")
or 0
local jours = player:GetAttribute("JoursSurvecus")
or 0
-- Consommer de l'énergie
energie = energie - ENERGIE_PAR_JOUR
-- Vérifier si énergie épuisée (GAME OVER)
if energie <=
0
then gameOverEvent:FireClient(player, { joursVecus = jours, cause =
"Énergie épuisée"
})
return false
end
-- Incrémenter les jours
jours = jours + 1
-- Vérifier VICTOIRE
if jours >= JOUR_VICTOIRE
then victoireEvent:FireClient(player, { joursVecus = jours, cristaux = player:GetAttribute("Cristaux")
or 0
})
return true
end
-- Mettre à jour les attributs
player:SetAttribute("Energie", energie) player:SetAttribute("JoursSurvecus", jours)
-- Notifier le client
nouveauJourEvent:FireClient(player,
jours)
return true
end
-- BOUCLE PRINCIPALE
while true do
-- Animer le jour (60 secondes)
animerCycleJour()
-- Traiter chaque joueur
for _, player
in
pairs(Players:GetPlayers())
do
traiterNouveauJour(player)
end
print("🌅 Nouveau jour !") end
Ajoute des événements aléatoires pour pimenter le gameplay :
-- Table des événements possibles
local EVENEMENTS = { { nom =
"Pluie de météorites", description
=
"⚠️ La station subit des impacts !", effet = function(player)
local perte =
math.random(5, 15)
local energie = player:GetAttribute("Energie")
or
0 player:SetAttribute("Energie",
math.max(0, energie - perte)) end, chance
= 15
-- 15% de chance }, { nom =
"Bonus solaire", description =
"☀️ Les panneaux captent plus d'énergie !", effet = function(player)
local bonus =
math.random(10, 25)
local energie = player:GetAttribute("Energie")
or 0
local max = player:GetAttribute("EnergieMax")
or
100 player:SetAttribute("Energie",
math.min(max, energie + bonus))
end, chance =
20 }, { nom =
"Découverte de cristaux",
description =
"💎 Un filon de cristaux apparaît !", effet = function(player)
local bonus =
math.random(3, 8)
local cristaux = player:GetAttribute("Cristaux")
or
0 player:SetAttribute("Cristaux", cristaux + bonus)
end, chance =
10
} }
-- FONCTION : Tenter un événement aléatoire
local function
tenterEvenement(player)
for _, event
in
pairs(EVENEMENTS)
do
if
math.random(100) <= event.chance
then event.effet(player)
-- Notifier le client
ReplicatedStorage.EvenementAleatoire:FireClient(player, event)
return
end
end
end
tenterEvenement(player) dans la boucle
traiterNouveauJour pour ajouter du suspense !
Crée un LocalScript dans
StarterPlayerScripts
pour afficher l'écran de fin :
-- EcranFin.lua (LocalScript)
local
ReplicatedStorage =
game:GetService("ReplicatedStorage") local
TweenService =
game:GetService("TweenService") local
Players =
game:GetService("Players")
local player =
Players.LocalPlayer
local gui = player.WaitForChild("PlayerGui")
-- FONCTION : Créer l'écran de fin
local function
afficherEcranFin(victoire, data)
local screen =
Instance.new("ScreenGui") screen.Name = "EcranFin"
screen.Parent = gui
local fond =
Instance.new("Frame") fond.Size = UDim2.new(1, 0,
1, 0)
fond.BackgroundColor3 = victoire
and
Color3.fromRGB(16, 185,
129)
-- Vert victoire
or
Color3.fromRGB(239, 68,
68)
-- Rouge défaite
fond.BackgroundTransparency = 1
fond.Parent = screen
-- Animation d'apparition
local tween =
TweenService:Create(fond,
TweenInfo.new(0.5, Enum.EasingStyle.Quad),
{BackgroundTransparency = 0.3} )
tween:Play()
-- Texte principal
local titre =
Instance.new("TextLabel") titre.Text = victoire and
"🏆 VICTOIRE !"
or
"💀 GAME OVER" titre.Font =
Enum.Font.GothamBold
titre.TextSize =
72 titre.TextColor3 =
Color3.new(1, 1,
1) titre.Position =
UDim2.new(0.5, 0,
0.3,
0) titre.AnchorPoint =
Vector2.new(0.5, 0.5)
titre.BackgroundTransparency = 1
titre.Parent = fond
-- Stats
local stats =
Instance.new("TextLabel") stats.Text =
"📅 Jours survécus : " ..
data.joursVecus stats.Font =
Enum.Font.Gotham stats.TextSize =
36 stats.Position =
UDim2.new(0.5, 0,
0.5,
0) stats.AnchorPoint =
Vector2.new(0.5, 0.5)
stats.BackgroundTransparency = 1
stats.Parent = fond
end
-- Écouter les événements
ReplicatedStorage:WaitForChild("Victoire").OnClientEvent:Connect(function(data)
afficherEcranFin(true, data) end)
ReplicatedStorage:WaitForChild("GameOver").OnClientEvent:Connect(function(data)
afficherEcranFin(false, data) end)
Tu maîtrises maintenant Lua, les systèmes de jeu, l'architecture client/serveur, et les bonnes pratiques de développement Roblox. Tu es prêt à créer n'importe quel jeu !