Dans ce tutoriel, tu vas apprendre à créer une arme que le joueur pourra récupérer dans le monde du jeu puis utiliser pour combattre des ennemis.
L’objectif est de réaliser un système complet comprenant :
une arme posée au sol ;
le ramassage automatique par le joueur ;
l’utilisation de l’arme depuis l’inventaire ;
une animation de frappe ;
des effets visuels lors de l’attaque ;
la détection des ennemis touchés ;
l’application de dégâts.
À la fin du projet, ton personnage pourra se défendre contre des adversaires comme dans de nombreux jeux d’aventure ou de rôle.
Crée une arme avec l’objet Tool puis un part renommer « Handle » de roblox :
Choisis la matière et la couleur de ton arme :
Positionne l’arme dans la main du joueur :
Saisie ce code dans le script :
-- récupération de l'arme
local arm = script.Parent
-- récupération des services pour animer
local Debris = game:GetService("Debris")
local TweenService = game:GetService("TweenService")
local Debris = game:GetService("Debris")
--
-- CONFIGURATION DES ANIMATIONS
--
local CONFIG = {
-- Particules
nbEtincelles = 50,
-- Onde de choc
ondeActive = true,
ondeCouleur = Color3.fromRGB(255, 150, 0),
-- Dégâts (rayon)
rayonDegats = 40,
-- Flash
flashActif = true,
sizeFlash = Vector3.new(5, 5, 5),
colorFlash = Color3.fromRGB(255, 0, 0),
}
--
-- EFFET FLASH ET ONDE DE CHOC
--
local function creerFlash(position)
local flash = Instance.new("Part")
flash.Shape = Enum.PartType.Ball
flash.Size = Vector3.new(0.1, 0.1, 0.1)
flash.Position = position
flash.Anchored = true
flash.CanCollide = false
flash.CastShadow = false
flash.Material = Enum.Material.Neon
flash.Color = CONFIG.colorFlash
flash.Parent = workspace
local tween = TweenService:Create(flash,
TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{ Size = CONFIG.sizeFlash, Transparency = 1 }
)
tween:Play()
Debris:AddItem(flash, 0.2)
end
local function creerOndeChoc(position)
local onde = Instance.new("Part")
onde.Shape = Enum.PartType.Ball
onde.Size = Vector3.new(1, 1, 1)
onde.Position = position
onde.Anchored = true
onde.CanCollide = false
onde.CastShadow = false
onde.Material = Enum.Material.Neon
onde.Color = CONFIG.ondeCouleur
onde.Transparency = 0.8
onde.Parent = workspace
local tween = TweenService:Create(onde,
TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{ Size = Vector3.new(CONFIG.rayonDegats * 2, CONFIG.rayonDegats * 2, CONFIG.rayonDegats * 2), Transparency = 1 }
)
tween:Play()
Debris:AddItem(onde, 0.5)
end
--
-- EFFET ETINCELLES
--
local function creerEtincelles(position)
for i = 1, CONFIG.nbEtincelles do
local etincelle = Instance.new("Part")
etincelle.Shape = Enum.PartType.Ball
-- taille des etincelles
etincelle.Size = Vector3.new(0.2, 0.2, 0.2)
etincelle.Position = position
etincelle.CanCollide = false
etincelle.CastShadow = false
etincelle.Material = Enum.Material.Neon
-- Couleur aléatoire entre orange et jaune
etincelle.Color = Color3.fromRGB(255, math.random(100, 255), 0)
etincelle.Parent = workspace
-- Direction aléatoire
local direction = Vector3.new(
math.random(-100, 100) / 100,
math.random(20, 100) / 100,
math.random(-100, 100) / 100
).Unit
-- vitesse aléatoire de propagation des étincelles
local vitesse = math.random(20, 200)
local vb = Instance.new("BodyVelocity")
vb.Velocity = direction * vitesse
vb.MaxForce = Vector3.new(1e4, 1e4, 1e4)
vb.Parent = etincelle
-- Disparition progressive
local duree = math.random(5, 12) / 10
local tween = TweenService:Create(etincelle,
TweenInfo.new(duree, Enum.EasingStyle.Quad, Enum.EasingDirection.In),
{ Transparency = 1, Size = Vector3.new(0.05, 0.05, 0.05) }
)
tween:Play()
Debris:AddItem(etincelle, duree)
end
end
--
-- limite le nombre de dégâts pendant un certain temps
--
local function withCooldown(hit)
local tag = Instance.new("Folder")
tag.Name = "ArmTag"
tag.Parent = hit.Parent
Debris:AddItem(tag, 0.4)
task.wait(0.4)
end
-- si le joueur est en cooldown, ne pas appliquer les dégâts
local function isCooldown(hit)
return hit.Parent:FindFirstChild("ArmTag")
end
-- mouvement coup d'épée
local function swordStroke(arm)
local tween = TweenService:Create(arm.Parent,
TweenInfo.new(1, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out),
{ Grip = arm.Parent.Grip * CFrame.Angles(0, 0, math.rad(90)) }
)
tween:Play()
end
-- l'arme touche un humanoid
arm.Touched:Connect(function(hit)
local humanoid = hit and hit.Parent:FindFirstChild("Humanoid")
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if player then return end
if not humanoid then return end
if isCooldown(hit) then return end
-- coup d'épée
swordStroke(arm)
-- dégâts
humanoid:TakeDamage(10)
-- effets
if CONFIG.flashActif then creerFlash(hit.Position) end
if CONFIG.ondeActive then creerOndeChoc(hit.Position) end
creerEtincelles(hit.Position)
-- cooldown pendant un certain temps
withCooldown(hit)
end)
Rajoute un son laser
Insère un son dans l’arborescence du tool :
Rajoute dans ton script la référence du son :
local soundLaser = arm.Laser
Puis ajoute dans ton script la fonction pour jouer le son :
local function playSound(sound)
if not sound then return end
pcall(function() sound:Play() end)
end
Puis modifie la fonction arm.Touched pour jouer le son :
-- l'arme touche un humanoid
arm.Touched:Connect(function(hit)
local humanoid = hit and hit.Parent:FindFirstChild("Humanoid")
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if player then return end
if not humanoid then return end
if isCooldown(hit) then return end
-- coup d'épée
playSound(soundLaser)
swordStroke(arm)
-- dégâts
humanoid:TakeDamage(10)
-- effets
if CONFIG.flashActif then creerFlash(hit.Position) end
if CONFIG.ondeActive then creerOndeChoc(hit.Position) end
creerEtincelles(hit.Position)
-- cooldown pendant un certain temps
withCooldown(hit)
end)
Tu es le maître d‘un 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.
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).
Quand un joueur quitte le jeu (PlayerRemoving) :
On supprime ses données pour libérer de la mémoire.
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 :
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 :
Le script cherche le nom du joueur dans son carnet de notes.
Il trouve la position 3D (X, Y, Z) du dernier checkpoint validé.
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 :
Le jeu met à jour ta fiche dans le carnet.
Le checkpoint change de couleur (il devient vert pour te montrer visuellement que c’est bon).
Un message s’affiche dans le chat grâce à un module de dialogue (ex: » Joueur1 a atteint le checkpoint 3/5″).
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) :
Les Variables (local ...) : Des boîtes pour ranger des informations (la couleur des blocs, les coordonnées…).
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).
Les Événements (.Touched, .PlayerAdded) : Des déclencheurs qui disent au script : « Hé ! Quelque chose s’est passé, exécute le code maintenant ! »
Voici une explication simple et étape par étape pour créer un module Lua sur Roblox qui affiche une boîte de dialogue au-dessus de la tête des joueurs. C’est comme une bulle de BD qui apparaît quand un personnage parle !
Ce module permet d’afficher un texte (comme « Bonjour ! ») au-dessus de la tête d’un personnage dans Roblox, avec un design personnalisable (couleurs, taille, durée d’affichage, etc.). Idéal pour :
Des quêtes ou dialogues dans ton jeu.
Des messages temporaires (ex : « Bravo ! »).
Des indications pour les joueurs.
Créer un module pour générer les boîtes de dialogue
Un module, c’est comme un générateur unique pour créer tes boîtes de dialogue :
Tu gagnes du temps (pas besoin de tout réécrire).
Tu évites les erreurs (tout est centralisé).
Tu peux améliorer ton jeu facilement (ajouter des animations, des sons, etc.).
« Un bon développeur est un développeur fainéant : il écrit le code une fois et le réutilise 1000 fois ! »
Où placer le ModuleScript pour la génération des boîtes de dialogue
Emplacement
Usage
ReplicatedStorage
Accessible côté client et serveur recommandé
ServerScriptService
Serveur uniquement
StarterPlayerScripts
Client uniquement
ReplicatedStorage est le meilleur choix pour une utilisation coté client et serveur. Placer un module dans ReplicatedStorage dans Roblox peut être pratique dans ce cas, mais cela comporte aussi des risques importants à connaître, surtout si ton module contient des fonctions sensibles ou des données critiques. Tout ce qui est dans ReplicatedStorage est téléchargé et exécutable côté client (par les joueurs). Un joueur malveillant peut :
Exploiter des failles si ton module contient des appels non sécurisés
Lire tout le code de ton module (même si tu le caches, il peut être extrait avec des outils comme Roblox Studio ou des exploits).
Modifier localement le comportement du module (en utilisant des exploits ou des scripts clients).
Crée un ModuleScript :
local DialogueModule = {}
local TweenService = game:GetService("TweenService")
-- CONFIGURATION
DialogueModule.CONFIG = {
largeur = 250,
hauteur = 80,
offsetY = 3,
couleurFond = Color3.fromRGB(0, 0, 0),
transparenceFond = 0.4,
couleurTexte = Color3.fromRGB(255, 255, 255),
couleurBordure = Color3.fromRGB(255, 255, 255),
taillePolicee = 16,
dureAffichage = 4,
}
function DialogueModule.creerDialogue(personnage, texte)
local CONFIG = DialogueModule.CONFIG
local head = personnage:WaitForChild("Head", 5)
if not head then return end
-- Supprimer l'ancienne boîte
local ancien = head:FindFirstChild("DialogueGui")
if ancien then ancien:Destroy() end
-- Créer le BillboardGui
local billboard = Instance.new("BillboardGui")
billboard.Name = "DialogueGui"
billboard.Adornee = head
billboard.Size = UDim2.new(0, CONFIG.largeur, 0, CONFIG.hauteur)
-- Pour le rebond
billboard.StudsOffset = Vector3.new(0, CONFIG.offsetY - 2, 0)
billboard.AlwaysOnTop = true
billboard.ResetOnSpawn = false
billboard.Parent = head
-- Fond
local fond = Instance.new("Frame")
fond.Size = UDim2.new(1, 0, 1, 0)
fond.BackgroundColor3 = CONFIG.couleurFond
fond.BackgroundTransparency = 1 -- Commence invisible
fond.BorderSizePixel = 0
fond.Parent = billboard
-- Coins arrondis
local coin = Instance.new("UICorner")
coin.CornerRadius = UDim.new(0, 10)
coin.Parent = fond
-- Bordure
local bordure = Instance.new("UIStroke")
bordure.Color = CONFIG.couleurBordure
bordure.Thickness = 1.5
bordure.Transparency = 0.5
bordure.Parent = fond
-- Texte
local label = Instance.new("TextLabel")
label.Size = UDim2.new(1, -16, 1, -10)
label.Position = UDim2.new(0, 8, 0, 5)
label.BackgroundTransparency = 1
label.TextColor3 = CONFIG.couleurTexte
label.TextSize = CONFIG.taillePolicee
label.Font = Enum.Font.GothamMedium
label.Text = texte
label.TextWrapped = true
label.TextXAlignment = Enum.TextXAlignment.Center
label.Parent = fond
-- Flèche
local fleche = Instance.new("Frame")
fleche.Size = UDim2.new(0, 14, 0, 14)
fleche.Position = UDim2.new(0.5, -7, 1, -7)
fleche.BackgroundColor3 = CONFIG.couleurFond
fleche.BackgroundTransparency = CONFIG.transparenceFond
fleche.BorderSizePixel = 0
fleche.Rotation = 45
fleche.Parent = fond
-- Suppression après délai
if CONFIG.dureAffichage > 0 then
task.delay(CONFIG.dureAffichage, function()
if billboard and billboard.Parent then
billboard:Destroy()
end
end)
end
return billboard
end
return DialogueModule
Génère la boîte de dialogue soit dans un localScript :
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local DialogueModule = require(game.ReplicatedStorage.DialogueModule)
-- Afficher
local character = game.Players.LocalPlayer.Character
DialogueModule.creerDialogue(character, "Bonjour !")
Ou dans un script sur le serveur par exemple lors d’une collision avec un part :
local part = script.Parent
local Players = game:GetService("Players")
local DialogueModule = require(game.ReplicatedStorage.DialogueModule)
part.Touched:Connect(function(hit)
local player = Players:GetPlayerFromCharacter(hit.Parent)
if player then
-- Afficher
local character = player.Character
DialogueModule.creerDialogue(character, "Bonjour !")
end
end)
Comment modifier les paramètres de la boîte de dialogue :
-- Modifier la config si besoin
DialogueModule.CONFIG.dureAffichage = 6
DialogueModule.CONFIG.couleurFond = Color3.fromRGB(30, 30, 80)
Rajoute dans ton code une animation de rebonds avant la suppression :
-- 1. Rebond de la boîte
local tweenRebond = TweenService:Create(
billboard,
TweenInfo.new(0.3, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out),
{StudsOffset = Vector3.new(0, CONFIG.offsetY, 0)}
)
tweenRebond:Play()
Animation transparence du fond :
-- 2. Fondu du fond
local tweenFondu = TweenService:Create(
fond,
TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{BackgroundTransparency = CONFIG.transparenceFond}
)
tweenFondu:Play()
Dans ce tutoriel, tu vas réaliser une animation où une boule descend une pente, disparaît lorsqu’elle arrive en bas, puis réapparaît automatiquement en haut pour recommencer son parcours.
Cette technique est très utilisée dans les jeux pour créer des décors animés ou des parcours d’obstacles.
Tu vas apprendre à :
construire une boule avec un Part sphérique ;
créer une pente avec un Part Coin ;
détecter l’arrivée de la boule grâce à une boîte de collision ;
utiliser un script serveur pour replacer la boule ;
organiser correctement les objets dans l’Explorateur ;
utiliser ReplicatedStorage pour conserver un modèle de référence.
Elle servira uniquement à détecter l’arrivée de la boule.
On l’appelle souvent une HitBox ou boîte de collision.
Pourquoi utiliser une boîte de collision ?
Au lieu de vérifier en permanence la position de la boule, Roblox peut détecter automatiquement lorsqu’un objet touche une zone.
Les avantages :
moins de calculs ;
code plus simple ;
meilleure précision.
Faire disparaître et réapparaître la boule
Où placer le script ?
Deux solutions sont possibles.
Solution 1 : ServerScriptService
ServerScriptService └─ GestionBoule
C’est la méthode recommandée.
Les avantages :
tous les scripts sont regroupés ;
plus facile à maintenir ;
meilleure sécurité.
Solution 2 : Sous le Folder
Workspace └─ Boules └─ Script
Cela fonctionne également.
Cette solution est parfois utilisée lorsque le script ne gère qu’un seul système.
Pour un petit projet comme celui-ci, cela reste acceptable, crée un script sous folder :
Saisie le code suivant :
-- récupération des ressources graphiques
local folder = script.Parent
local hitBox = folder.HitBox
local ball = folder.Ball
-- récupération du folder replicatedStorage
local replicatedStorage = game:GetService("ReplicatedStorage")
-- récupération de la position initiale de la balle
local positionInitial = ball.Position
-- modification des propriété de la ball
ball.Anchored = false
ball.CanCollide = true
ball.Name = "Ball"
-- clonage de la balle
local newBall = ball:Clone()
newBall.Parent = folder
-- rangement de la balle en modèle replicatedStorage
ball.Parent = replicatedStorage
-- détection de la collision de la balle
hitBox.Touched:Connect(function(hit)
if hit.Name ~= "Ball" then return end
newBall:Destroy()
task.wait(1)
newBall = ball:Clone()
newBall.Parent = folder
newBall.Anchored = false
end)
Pourquoi utiliser un Script serveur ?
Nous allons utiliser un Script et non un LocalScript.
Le serveur est responsable :
des objets du Workspace ;
de la physique ;
des collisions ;
des déplacements des Parts.
Comme la boule existe pour tous les joueurs, c’est le serveur qui doit la gérer.
Mémoriser la position de départ
Le script va enregistrer la position initiale de la boule :
local positionInitial = ball.Position
Cette variable contient les coordonnées exactes du point de départ.
Plus tard, le script pourra replacer la boule exactement au même endroit.
Sauvegarder un modèle dans ReplicatedStorage
Crée une copie parfaite de la boule.
Place cette copie dans :
-- récupération du folder replicatedStorage
local replicatedStorage = game:GetService("ReplicatedStorage")
ball.Parent = replicatedStorage
Cette boule ne sera jamais utilisée directement dans le jeu.
Elle sert de modèle de référence.
Pourquoi utiliser ReplicatedStorage ?
ReplicatedStorage permet de stocker des objets qui pourront être utilisés plus tard.
Les avantages :
conserver un modèle intact ;
recréer facilement un objet détruit ;
éviter de reconstruire l’objet à la main ;
faciliter les systèmes complexes.
Pourquoi cloner la boule ?
Lorsque la boule disparaît, il est parfois plus simple de :
détruire l’ancienne ;
recréer une nouvelle boule.
Pour cela :
local newBall = ball:Clone()
Le clonage produit une copie identique :
même taille ;
même couleur ;
mêmes propriétés ;
mêmes scripts éventuels.
Cette méthode est très utilisée dans les jeux Roblox.
Créer un flash lumineux
Ce script sert à créer un effet de flash lumineux à un endroit précis dans le jeu. Par exemple, il peut être utilisé lorsqu’un joueur clique, lorsqu’une explosion se produit ou lorsqu’un objet apparaît ou disparaît.
Le flash est créé sous la forme d’une petite sphère lumineuse qui grandit rapidement avant de disparaître.
Quand la fonction est appelée :
une petite boule lumineuse est créée ;
elle apparaît à la position demandée ;
elle grossit rapidement ;
elle devient transparente ;
elle disparaît ;
Roblox la supprime automatiquement.
Le résultat ressemble à une petite explosion lumineuse ou à un effet magique très utilisé dans les jeux Roblox.
Rajoute ce code dans ton script :
local Debris = game:GetService("Debris")
local TweenService = game:GetService("TweenService")
--
-- CONFIGURATION
--
local CONFIG = {
-- Flash
flashActif = true,
sizeFlash = Vector3.new(20, 20, 20),
colorFlash = Color3.fromRGB(255, 0, 0),
}
--
-- EFFETS Flash
--
local function creerFlash(position)
local flash = Instance.new("Part")
flash.Shape = Enum.PartType.Ball
flash.Size = Vector3.new(0.1, 0.1, 0.1)
flash.Position = position
flash.Anchored = true
flash.CanCollide = false
flash.CastShadow = false
flash.Material = Enum.Material.Neon
flash.Color = CONFIG.colorFlash
flash.Parent = workspace
local tween = TweenService:Create(flash,
TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{ Size = CONFIG.sizeFlash, Transparency = 1 }
)
tween:Play()
Debris:AddItem(flash, 0.2)
end
--
-- EXPLOSION PRINCIPALE
--
local function exploser(ball)
local position = ball.Position
ball.CanCollide = false
ball.Transparency = 1
if CONFIG.flashActif then creerFlash(position) end
ball:Destroy()
end
Puis modifie ton script pour appeler le flash lorsque la balle touche la HitBox :
-- détection de la collision de la balle
hitBox.Touched:Connect(function(hit)
if hit.Name ~= "Ball" then return end
exploser(newBall)
task.wait(1)
newBall = ball:Clone()
newBall.Parent = folder
newBall.Anchored = false
end)
Crée une onde de choc
Ce script crée une onde de choc visuelle qui se propage autour d’un point précis.
Tu peux imaginer ce qui se passe lorsqu’une pierre tombe dans l’eau : une vague se forme puis s’agrandit progressivement. Ici, c’est le même principe, mais sous la forme d’une sphère lumineuse qui grandit avant de disparaître.
Ce type d’effet est souvent utilisé pour :
une explosion ;
un sort magique ;
un impact puissant ;
l’arrivée d’un boss ;
une compétence spéciale.
-- Script dans la boule (Part) ou un Script serveur
local Debris = game:GetService("Debris")
local TweenService = game:GetService("TweenService")
--
-- CONFIGURATION
--
local CONFIG = {
-- Onde de choc
ondeActive = true,
ondeCouleur = Color3.fromRGB(255, 150, 0),
-- Dégâts (rayon)
rayonDegats = 40,
forceDegats = 80,
-- Flash
flashActif = true,
sizeFlash = Vector3.new(20, 20, 20),
colorFlash = Color3.fromRGB(255, 0, 0),
}
--
-- EFFETS ONDE DE CHOC
--
local function creerOndeChoc(position)
local onde = Instance.new("Part")
onde.Shape = Enum.PartType.Ball
onde.Size = Vector3.new(1, 1, 1)
onde.Position = position
onde.Anchored = true
onde.CanCollide = false
onde.CastShadow = false
onde.Material = Enum.Material.Neon
onde.Color = CONFIG.ondeCouleur
onde.Transparency = 0.3
onde.Parent = workspace
local tween = TweenService:Create(onde,
TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{ Size = Vector3.new(CONFIG.rayonDegats * 2, CONFIG.rayonDegats * 2, CONFIG.rayonDegats * 2), Transparency = 1 }
)
tween:Play()
Debris:AddItem(onde, 0.5)
end
local function appliquerForce(position)
for _, part in ipairs(workspace:GetPartBoundsInRadius(position, CONFIG.rayonDegats)) do
if part.Anchored then continue end
local direction = (part.Position - position)
local distance = direction.Magnitude
if distance < 0.1 then continue end
local force = (1 - distance / CONFIG.rayonDegats) * CONFIG.forceDegats
part:ApplyImpulse(direction.Unit * force * part.AssemblyMass)
end
end
--
-- EXPLOSION PRINCIPALE
--
local function exploser(ball)
local position = ball.Position
ball.CanCollide = false
ball.Transparency = 1
if CONFIG.flashActif then creerFlash(position) end
if CONFIG.ondeActive then creerOndeChoc(position) endappliquerForce(position)
ball:Destroy()
end
Lorsque la fonction creerOndeChoc() est appelée :
une petite sphère lumineuse apparaît ;
elle est placée au centre de l’explosion ;
elle s’agrandit jusqu’au rayon défini dans la configuration ;
elle devient progressivement transparente ;
elle disparaît ;
Roblox la supprime automatiquement.
Cette technique permet de créer facilement des effets spectaculaires pour des explosions, des sorts magiques ou des impacts puissants dans un jeu Roblox.
Créer de la fumée
Ce script permet de créer un effet de fumée à un endroit précis dans le jeu.
Cet effet peut être utilisé pour représenter :
une explosion ;
un moteur ;
un incendie ;
une cheminée ;
l’impact d’un projectile.
La fumée apparaît pendant un court instant, puis disparaît automatiquement.
-- ═══════════════════════════════════════
-- CONFIGURATION
-- ═══════════════════════════════════════
local CONFIG = {
-- Fumée
fumeeActive = true,
-- Onde de choc
ondeActive = true,
ondeCouleur = Color3.fromRGB(255, 150, 0),
-- Dégâts (rayon)
rayonDegats = 40,
forceDegats = 80,
-- Flash
flashActif = true,
sizeFlash = Vector3.new(20, 20, 20),
colorFlash = Color3.fromRGB(255, 0, 0),
}
-- ═══════════════════════════════════════
-- EFFETS FUMEE
-- ═══════════════════════════════════════
l
local function creerFumee(position)
local anchor = Instance.new("Part")
anchor.Size = Vector3.new(1, 1, 1)
anchor.Position = position
anchor.Anchored = true
anchor.CanCollide = false
anchor.Transparency = 1
anchor.Parent = workspace
local fumee = Instance.new("Smoke")
fumee.Color = Color3.fromRGB(80, 80, 80)
fumee.Opacity = 0.5
fumee.RiseVelocity = 10
fumee.Size = 12
fumee.Parent = anchor
-- Couper la fumée puis supprimer
task.delay(0.3, function()
fumee.Enabled = false
end)
Debris:AddItem(anchor, 3)
end
-- ═══════════════════════════════════════
-- EXPLOSION PRINCIPALE
-- ═══════════════════════════════════════
local function exploser(ball)
local position = ball.Position
ball.CanCollide = false
ball.Transparency = 1
if CONFIG.flashActif then creerFlash(position) end
if CONFIG.ondeActive then creerOndeChoc(position) end
if CONFIG.fumeeActive then creerFumee(position) end
appliquerForce(position)
ball:Destroy()
end
L’objet Smoke ne peut pas exister seul.
Il doit être attaché à un objet Roblox.
La Part invisible sert donc de :
support ;
point d’émission ;
repère dans l’espace.
Le joueur ne la voit jamais, mais elle permet à la fumée de fonctionner correctement.
Lorsque la fonction creerFumee() est appelée :
un support invisible est créé ;
un effet Smoke est ajouté dessus ;
la fumée grise est émise ;
elle monte rapidement ;
l’émission s’arrête après 0,3 seconde ;
Roblox supprime automatiquement l’ensemble après 3 secondes.
Cette technique permet d’ajouter facilement des effets visuels réalistes à tes explosions et animations dans Roblox.
Création d’étincelles
Ce script permet de créer un effet d’étincelles semblable à celui d’une explosion, d’un feu d’artifice ou d’un impact métallique.
Lorsqu’il est exécuté, plusieurs petites sphères lumineuses sont créées au même endroit. Elles partent ensuite dans différentes directions à des vitesses variées avant de disparaître progressivement.
Création de plusieurs étincelles
La fonction utilise une boucle qui se répète autant de fois que la valeur définie dans :
CONFIG.nbEtincelles
Si cette valeur est égale à 20, alors 20 étincelles seront créées.
Plus cette valeur est grande, plus l’effet sera spectaculaire, mais aussi plus il demandera de calculs à Roblox.
-- Script dans la boule (Part) ou un Script serveur
local Debris = game:GetService("Debris")
local TweenService = game:GetService("TweenService")
-- ═══════════════════════════════════════
-- CONFIGURATION
-- ═══════════════════════════════════════
local CONFIG = {
-- Particules
nbEtincelles = 50,
-- Fumée
fumeeActive = true,
-- Onde de choc
ondeActive = true,
ondeCouleur = Color3.fromRGB(255, 150, 0),
-- Dégâts (rayon)
rayonDegats = 40,
forceDegats = 80,
-- Flash
flashActif = true,
sizeFlash = Vector3.new(20, 20, 20),
colorFlash = Color3.fromRGB(255, 0, 0),
}
-- ═══════════════════════════════════════
-- EFFETS ETINCELLES
-- ═══════════════════════════════════════
local function creerEtincelles(position)
for i = 1, CONFIG.nbEtincelles do
local etincelle = Instance.new("Part")
etincelle.Shape = Enum.PartType.Ball
-- taille des etincelles
etincelle.Size = Vector3.new(0.2, 0.2, 0.2)
etincelle.Position = position
etincelle.CanCollide = false
etincelle.CastShadow = false
etincelle.Material = Enum.Material.Neon
-- Couleur aléatoire entre orange et jaune
etincelle.Color = Color3.fromRGB(255, math.random(100, 255), 0)
etincelle.Parent = workspace
-- Direction aléatoire
local direction = Vector3.new(
math.random(-100, 100) / 100,
math.random(20, 100) / 100,
math.random(-100, 100) / 100
).Unit
-- vitesse aléatoire de propagation des étincelles
local vitesse = math.random(20, 200)
local vb = Instance.new("BodyVelocity")
vb.Velocity = direction * vitesse
-- Autorise une poussée très forte sur les axes X, Y et Z pour propulser l'étincelle 1e4 = 10000
vb.MaxForce = Vector3.new(1e4, 1e4, 1e4)
vb.Parent = etincelle
-- Disparition progressive
local duree = math.random(5, 12) / 10
local tween = TweenService:Create(etincelle,
TweenInfo.new(duree, Enum.EasingStyle.Quad, Enum.EasingDirection.In),
{ Transparency = 1, Size = Vector3.new(0.05, 0.05, 0.05) }
)
tween:Play()
Debris:AddItem(etincelle, duree)
end
end
-- ═══════════════════════════════════════
-- EXPLOSION PRINCIPALE
-- ═══════════════════════════════════════
local function exploser(ball)
local position = ball.Position
ball.CanCollide = false
ball.Transparency = 1
if CONFIG.flashActif then creerFlash(position) end
if CONFIG.ondeActive then creerOndeChoc(position) end
if CONFIG.fumeeActive then creerFumee(position) end
creerEtincelles(position)
appliquerForce(position)
ball:Destroy()
end
Apparence des étincelles
Chaque étincelle est représentée par une très petite sphère lumineuse.
Le matériau Neon lui donne un aspect brillant qui ressemble à une particule incandescente.
Pour rendre l’effet plus réaliste, la couleur n’est pas toujours la même. Le rouge reste au maximum tandis que le vert varie aléatoirement. Cela produit différentes nuances allant :
du jaune vif ;
à l’orange ;
jusqu’à l’orange foncé.
Comme dans une véritable explosion, toutes les étincelles n’ont donc pas exactement la même couleur.
Une direction différente pour chaque étincelle
Après leur création, les étincelles doivent se disperser.
Le script génère donc une direction aléatoire pour chacune d’elles.
Certaines partent :
vers la gauche ;
vers la droite ;
vers l’avant ;
vers l’arrière ;
vers le haut.
Cette dispersion donne l’impression d’une véritable projection de particules.
Si toutes les étincelles allaient dans la même direction, l’effet serait beaucoup moins naturel.
Une vitesse différente pour chaque étincelle
Le script choisit également une vitesse aléatoire.
Certaines étincelles sont éjectées très rapidement tandis que d’autres se déplacent plus lentement.
C’est ce mélange de vitesses qui rend l’explosion crédible.
Dans la réalité, les fragments d’une explosion ne se déplacent jamais tous à la même vitesse.
Utilisation de BodyVelocity
Pour propulser les étincelles, le script utilise un objet appelé BodyVelocity.
On peut le comparer à une petite poussée invisible qui agit sur chaque étincelle. Cette poussée envoie immédiatement la particule dans la direction choisie avec la vitesse calculée. Le résultat est un mouvement rapide qui donne l’impression que les étincelles jaillissent du centre de l’explosion.
Disparition progressive
Une étincelle ne reste pas visible indéfiniment. Le script lui attribue une durée de vie aléatoire. Certaines disparaissent rapidement alors que d’autres restent visibles un peu plus longtemps.
Pendant cette durée :
elles deviennent progressivement transparentes ;
leur taille diminue petit à petit.
L’effet obtenu ressemble à une braise qui refroidit et s’éteint.
Animation avec TweenService
La disparition est réalisée grâce à TweenService.
Au lieu de rendre l’étincelle invisible d’un seul coup, Roblox anime progressivement :
la transparence ;
la taille.
L’étincelle semble donc se consumer naturellement avant de disparaître.
Nettoyage automatique
Une fois son animation terminée, chaque étincelle est supprimée grâce au service Debris. Cela évite d’accumuler des centaines de petits objets invisibles dans le jeu. C’est une étape importante pour conserver de bonnes performances.
Ce que voit le joueur
Lorsqu’une explosion se produit :
plusieurs petites boules lumineuses apparaissent ;
elles partent dans toutes les directions ;
certaines vont plus vite que d’autres ;
elles deviennent progressivement transparentes ;
elles rétrécissent ;
elles disparaissent complètement.
L’ensemble crée un effet très proche des étincelles d’un feu d’artifice, d’une explosion ou d’un choc métallique.
Créer un effet de projection de débris
Ce script sert à simuler une explosion de petits morceaux de matière, comme des cailloux, des fragments de mur ou des débris projetés au sol.
L’idée est simple : au lieu d’avoir un seul effet, on génère plusieurs petits objets qui partent dans toutes les directions avec des tailles et des vitesses différentes.
-- ═══════════════════════════════════════
-- CONFIGURATION
-- ═══════════════════════════════════════
local CONFIG = {
-- Particules
nbEtincelles = 50,
nbDebris = 10,
-- Fumée
fumeeActive = true,
-- Onde de choc
ondeActive = true,
ondeCouleur = Color3.fromRGB(255, 150, 0),
-- Dégâts (rayon)
rayonDegats = 40,
forceDegats = 80,
-- Flash
flashActif = true,
sizeFlash = Vector3.new(20, 20, 20),
colorFlash = Color3.fromRGB(255, 0, 0),
}
-- ═══════════════════════════════════════
-- EFFETS DEBRIS
-- ═══════════════════════════════════════
local function creerDebris(position)
for i = 1, CONFIG.nbDebris do
local morceau = Instance.new("Part")
morceau.Shape = Enum.PartType.CornerWedge
morceau.Size = Vector3.new(
math.random(1, 4) / 10,
math.random(1, 4) / 10,
math.random(1, 4) / 10
)
morceau.Position = position + Vector3.new(
math.random(-2, 2),
math.random(0, 2),
math.random(-2, 2)
)
morceau.Material = Enum.Material.SmoothPlastic
morceau.Color = Color3.fromRGB(80, 80, 80)
morceau.Parent = workspace
local direction = Vector3.new(
math.random(-100, 100) / 100,
math.random(30, 100) / 100,
math.random(-100, 100) / 100
).Unit
local vb = Instance.new("BodyVelocity")
vb.Velocity = direction * math.random(10, 30)
vb.MaxForce = Vector3.new(1e4, 1e4, 1e4)
vb.Parent = morceau
Debris:AddItem(morceau, math.random(2, 4))
end
end
-- ═══════════════════════════════════════
-- EXPLOSION PRINCIPALE
-- ═══════════════════════════════════════
local function exploser(ball)
local position = ball.Position
ball.CanCollide = false
ball.Transparency = 1
if CONFIG.flashActif then creerFlash(position) end
if CONFIG.ondeActive then creerOndeChoc(position) end
if CONFIG.fumeeActive then creerFumee(position) end
creerEtincelles(position)
creerDebris(position)
appliquerForce(position)
ball:Destroy()
end
Création de plusieurs morceaux
La fonction utilise une boucle qui se répète un nombre de fois défini par :
CONFIG.nbDebris
Plus cette valeur est grande, plus l’explosion est impressionnante. Chaque boucle crée donc un nouveau morceau de débris.
Apparence des débris
Chaque fragment est une petite Part avec :
une taille différente à chaque fois (petits ou un peu plus gros morceaux) ;
une couleur gris foncé pour simuler de la pierre ou du béton ;
un matériau simple pour rester léger et réaliste.
L’objectif est d’éviter un rendu trop uniforme : dans une vraie explosion, tous les morceaux ne sont jamais identiques.
Position initiale aléatoire
Les débris ne sont pas tous créés exactement au même point.
Ils apparaissent légèrement autour de la position d’origine, avec un petit décalage :
certains un peu à gauche ;
d’autres un peu à droite ;
certains légèrement plus haut.
Cela donne l’impression que l’explosion a un vrai volume, et pas juste un point fixe.
Direction de projection
Chaque débris reçoit une direction aléatoire dans l’espace. Cette direction est calculée pour permettre :
une dispersion horizontale (gauche / droite / avant / arrière) ;
une poussée vers le haut pour simuler l’explosion.
Ensuite, cette direction est normalisée, ce qui signifie qu’on garde uniquement le sens, sans influencer la vitesse.
Mise en mouvement
Pour propulser les morceaux, le script utilise un BodyVelocity.
Ce système agit comme une poussée instantanée appliquée à chaque débris. La vitesse est également aléatoire :
certains morceaux partent doucement ;
d’autres sont projetés rapidement.
Cela rend l’effet beaucoup plus naturel et dynamique.
Durée de vie limitée
Chaque débris n’est pas permanent. Le service Debris s’occupe de supprimer automatiquement chaque morceau après un temps aléatoire.
Cela évite :
d’encombrer le jeu avec trop d’objets ;
de ralentir les performances ;
de laisser des débris inutiles dans le monde.
Résultat visuel
Quand la fonction est appelée, on observe :
plusieurs petits morceaux apparaissent au point d’impact ;
ils sont légèrement dispersés autour du centre ;
ils sont projetés dans toutes les directions ;
certains montent haut, d’autres restent proches du sol ;
ils disparaissent progressivement.
L’ensemble donne un effet très proche :
d’une explosion de mur ;
d’un impact de balle lourde ;
ou d’un objet qui se casse violemment.
Rajouter un son d’explosion
Trouve et insère le dans l’arborescence :
Puis ajoute à ton script la gestion du son le son :
local soundExplosion = folder.Explosion
local function playSound(sound)
if not sound then return end
pcall(function() sound:Play() end)
end
Puis modifie la fonction Exploser de ton script pour ajouter le son de l’explosion :
--
-- EXPLOSION PRINCIPALE
--
local function exploser(ball)
local position = ball.Position
ball.CanCollide = false
ball.Transparency = 1
playSound(soundExplosion)
if CONFIG.flashActif then creerFlash(position) end
if CONFIG.ondeActive then creerOndeChoc(position) end
if CONFIG.fumeeActive then creerFumee(position) end
creerEtincelles(position)
creerDebris(position)
appliquerForce(position)
ball:Destroy()
end
Dans ce tutoriel, tu vas apprendre à déplacer une image vers le bas sur une interface du joueur en utilisant TweenService dans Roblox Studio.
Les animations sont très utiles pour rendre un jeu plus agréable et plus vivant. Par exemple, tu peux faire apparaître un panneau d’information, faire glisser un bouton ou afficher une récompense avec une animation fluide.
Pour réaliser cela, nous allons utiliser un LocalScript et le service TweenService
Dans Roblox, les éléments affichés à l’écran du joueur sont appelés des interfaces graphiques ou GUI.
Une interface est généralement composée de plusieurs objets :
ScreenGui : le conteneur principal affiché à l’écran.
Frame : une fenêtre ou une zone de l’interface.
ImageLabel : un objet qui affiche une image.
TextLabel : un objet qui affiche du texte.
Button : un bouton sur lequel le joueur peut cliquer.
Exemple :
Pour positionner ta fenêtre (Frame) au milieu de l’écran :
Crée un LocalScript
Les interfaces appartiennent à chaque joueur.
Si tu modifies l’interface avec un script serveur classique (Script), cela peut entraîner des problèmes car le serveur gère tous les joueurs.
Un LocalScript s’exécute uniquement sur l’ordinateur du joueur :
il est plus rapide ;
il permet de gérer l’interface personnelle du joueur ;
il évite d’envoyer des informations inutiles au serveur.
C’est donc le meilleur choix pour animer une interface graphique.
Tu pourrais déplacer une image en modifiant sa position directement :
image.Position = UDim2.new(0,0,0.5,0)
Mais l’image apparaîtrait immédiatement à sa nouvelle position.
Avec TweenService, Roblox crée automatiquement une animation fluide entre la position de départ et la position d’arrivée.
Les avantages sont nombreux :
animation plus agréable ;
code plus simple ;
contrôle de la durée ;
possibilité d’ajouter différents effets de mouvement.
-- récupérer le service TweenService
local TweenService = game:GetService("TweenService")
-- récupérer l' du joueur
local screen = script.Parent
-- récupérer l'image
local image = screen.Frame.ImageLabel
-- créer l'animation
local tweenInfo = TweenInfo.new(3, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true)
-- définir l'objectif de l'animation
local objectif = {Position = UDim2.new(image.Position.X.Scale,0 , 0.8, 0)}
-- lancer l'animation
local tween = TweenService:Create(image, tweenInfo, objectif)
tween:Play()
Créer un jeu sur Roblox, inventer une expérience interactive pour les joueurs. tu vas construire un véritable espace de salles de cinéma interactives dans Roblox Studio.
Le joueur peut se déplacer librement d’une salle à l’autre, découvrir différents écrans et interagir directement avec eux grâce à la souris. Chaque salle pourra proposer une ambiance différente, des boutons interactifs ou des animations qui réagissent aux actions du joueur.
Par script, tu iras encore plus loin en découvrant comment modifier la caméra du joueur pour rendre l’expérience plus immersive. Selon les situations, la caméra permettra :
de passer automatiquement en vue First Person lorsque le joueur entre dans une salle ;
ou de rapprocher la caméra du personnage pour donner un effet de cinéma ou de jeu narratif ;
et même de changer la caméra avec une touche du clavier.
À travers ce projet, tu utilisera plusieurs outils importants de Roblox Studio :
la création d’interfaces et d’écrans interactifs sur des parts;
les scripts en langage Lua ;
la gestion des déplacements du joueur ;
les événements déclenchés par le clavier ou les collisions ;
et le contrôle de la caméra pour améliorer l’immersion du jeu.
A toi de réfléchir à partir de ces scripts pour programmer un jeu comme de vrais créateurs de jeux vidéo : comment guider le joueur, rendre une salle intéressante et créer une ambiance unique grâce à la caméra et aux interactions.
Comment créer une interactive room
Crée la structure de la salle de cinéma avec une boite de collision hit box et un écran sur le lequel tu positionnes un surfaceGui. Sur le surfaceGui crée un textButton que l’on fera bouger :
Le joueur interagit sur le bouton.
Crée et modifie le script sous surfaceGui pour déplacer le bouton et interagir avec la souris :
-- récupération du frame
local frame = script.Parent.Frame
-- récupération du service de tween
local tweenService = game:GetService("TweenService")
-- récupération du bouton
local button = frame.TextButton
-- variable de bascule entre pause et reprendre le déplacement
local pause = false
-- couleur de base du bouton
local colorBase = button.BackgroundColor3
-- déplacement horizontal du bouton avec un tweenService
local tween = tweenService:Create(button,
TweenInfo.new(10,
Enum.EasingStyle.Linear,
Enum.EasingDirection.InOut, -1, false),
{Position = button.Position + UDim2.new(1, 0, 0, 0)})
tween:Play()
-- action sur le click de souris
button.MouseButton1Click:Connect(function()
--click de la souris pour mettre en pause ou reprendre le tween
pause = not pause
if pause then
tween:Pause()
button.BackgroundColor3 = Color3.fromRGB(0, 255, 0)
else
tween:Play()
button.BackgroundColor3 = colorBase
end
end)
Sous ReplicatedStorage crée un remoteEvent, ce remoteEvent est utilisé pour prévenir un scriptLocal du joueur afin de changer son comportement :
La hitbox est une Partqui occupe tout l’intérieur de la salle. Elle est transparente et sans CanCollide. Elle sert à gérer la collision lorsque le joueur pénètre dans la salle.
Crée et modifie ce script pour gérer la collision avec le joueur quand celui ci rentre ou sort de la salle pour envoyer un signal au script local du joueur :
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- récupération du remoteEvent
local kioskEvent = ReplicatedStorage:WaitForChild("KioskEvent")
-- récupération des services Debris
local Debris = game:GetService("Debris")
-- récupération de la hitbox
local hitbox = script.Parent
-- nom de l'action
local actionName = "KioskLocked"
--- durée du cooldown en secondes
local COOLDOWN_DURATION = 2
-- locker du kiosk
local function KioskLocked(character)
-- si le joueur est déjà dans la salle, ne rien faire
if character:FindFirstChild(actionName) then return false end
-- création d'un tag temporaire pour indiquer que le joueur est dans la salle
local tag = Instance.new("Folder")
tag.Name = actionName
tag.Parent = character
Debris:AddItem(tag, COOLDOWN_DURATION)
return true
end
-- détection du joueur qui entre dans la salle
hitbox.Touched:Connect(function(hit)
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if player then
local character = player.Character
if not character then return end
-- Envoyer au client concerné uniquement
if KioskLocked(character) then
kioskEvent:FireClient(player, true)
end
end
end)
-- détection du joueur qui sort de la salle
hitbox.TouchEnded:Connect(function(hit)
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if player then
local character = player.Character
if not character then return end
-- Envoyer au client concerné uniquement
if KioskLocked(character) then
kioskEvent:FireClient(player, false)
end
end
end)
Crée un localScript sous StarterPlayer pour gérer une touche pour le changement de caméra sur le joueur ou pour gérer le remoteEvent venant du serveur :
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local kioskEvent = ReplicatedStorage:WaitForChild("KioskEvent")
local CinemaEvent = ReplicatedStorage:WaitForChild("CinemaEvent")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local player = Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local humanoid = char:FindFirstChildOfClass("Humanoid")
local head = char:WaitForChild("Head")
local hrp = char:WaitForChild("HumanoidRootPart")
local cam = workspace.CurrentCamera
local screen = player.PlayerGui.ScreenGui
local cinemaButton = screen.Frame.CinemaButton
local angleY = 0
local angleX = 0
local DISTANCE = -0.5
local HEIGHT = 0
local KEY_ROTATION_SPEED = 2
local isLocked = false
local connection = nil -- stocker la connexion pour pouvoir la couper
local function enableLockedCam(firstPerson, autorotate, viewAngle, distance, height)
-- Paramètres par défaut
if firstPerson == nil then
firstPerson = true
end
if autorotate == nil then
autorotate = true
end
if not autorotate then
humanoid.WalkSpeed = 0
humanoid.JumpPower = 0
end
viewAngle = viewAngle or {0,0}
distance = distance or DISTANCE
height = height or HEIGHT
cam.CameraType = Enum.CameraType.Custom
if firstPerson then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
-- Initialiser l'angle sur la rotation actuelle du personnage
-- Correction : ToEulerAnglesYXZ retourne (X, Y, Z), l'angle horizontal est le 2ème
local currentAngleX, currentAngleY, _ = hrp.CFrame:ToEulerAnglesYXZ()
angleY = math.deg(currentAngleY)
angleX = math.deg(currentAngleX)
local initangleY = angleY
local initangleX = angleX
connection = RunService.RenderStepped:Connect(function()
local delta = UserInputService:GetMouseDelta()
-- Rotation clavier (flèches ou Q/D)
local keyDeltaX = 0
if UserInputService:IsKeyDown(Enum.KeyCode.Left) or UserInputService:IsKeyDown(Enum.KeyCode.Q) then
keyDeltaX = -KEY_ROTATION_SPEED
elseif UserInputService:IsKeyDown(Enum.KeyCode.Right) or UserInputService:IsKeyDown(Enum.KeyCode.D) then
keyDeltaX = KEY_ROTATION_SPEED
end
local keyDeltaY = 0
if UserInputService:IsKeyDown(Enum.KeyCode.Up) then
keyDeltaY = KEY_ROTATION_SPEED
elseif UserInputService:IsKeyDown(Enum.KeyCode.Down) then
keyDeltaY = -KEY_ROTATION_SPEED
end
-- Fusionner souris + clavier
local totalDeltaX = delta.X + keyDeltaX
local totalDeltaY = delta.Y
if viewAngle[1] > 0 then
angleY = math.clamp(angleY - totalDeltaX, -viewAngle[1]+initangleY, viewAngle[1]+initangleY)
elseif viewAngle[1] < 0 then
angleY = 0
else
angleY = angleY - totalDeltaX * 0.3
end
if viewAngle[2] > 0 then
angleX = math.clamp(angleX - totalDeltaY, -viewAngle[2]+initangleX, viewAngle[2]+initangleX)
elseif viewAngle[2] < 0 then
angleX = 0
else
angleX = angleX - totalDeltaY* 0.3
end
-- Caméra placée dans la tête, regardant vers l'avant
-- local rotation = CFrame.Angles(math.rad(angleX), math.rad(angleY), 0)
-- Reculer la caméra derrière la tête (ajuste la valeur selon le rendu)
local camCFrame = CFrame.new(head.Position) * CFrame.Angles(0, math.rad(angleY), 0) * CFrame.Angles(math.rad(angleX), 0, 0) * CFrame.new(0, height, distance)
cam.CFrame = camCFrame
-- Tourner le personnage avec la caméra
hrp.CFrame = CFrame.new(hrp.Position) * CFrame.Angles(0, math.rad(angleY), 0)
end)
end
local function disableLockedCam()
-- Couper le RenderStepped
if connection then
task.spawn(function()
RunService.RenderStepped:Wait()
connection:Disconnect()
connection = nil
end)
end
-- Restaurer le comportement normal de Roblox
local cam = workspace.CurrentCamera
cam.CameraType = Enum.CameraType.Custom
-- 2. Reassigner le sujet (crucial !)
if humanoid then
cam.CameraSubject = humanoid
humanoid.WalkSpeed = 16
humanoid.JumpPower = 50
end
-- 3. Laisser Roblox reprendre le contrôle au prochain frame
RunService.RenderStepped:Wait()
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
-- Toggle avec F
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if input.KeyCode == Enum.KeyCode.F then
print("F",isLocked)
isLocked = not isLocked
if isLocked then
enableLockedCam(false, true, {180,60},-2, 1)
else
disableLockedCam()
end
end
end)
screen.Enabled = false
-- Écouter l'événement envoyé par le serveur
kioskEvent.OnClientEvent:Connect(function(entered)
print("KioskEvent reçu :", entered)
if entered then
screen.Enabled = true
if not isLocked then
enableLockedCam(false, true, {180,0},-2, 1)
end
else
screen.Enabled = false
isLocked = false
disableLockedCam()
end
end)
local cinemaetat = false
cinemaButton.MouseButton1Click:Connect(function()
cinemaetat = not cinemaetat
CinemaEvent:FireServer(cinemaetat)
end)
Vue derrière le joueur :
enableLockedCam(false, false, {120,10},5, 2)
firstperson ou pas, player peut bouger ou pas, {angle de vision horizontal, angle de vision vertical, 5 studs en arrière, 2 au dessus de la tête
Vue devant le joueur :
enableLockedCam(false, {120,0},-1, 0)
firstperson ou pas, player peut bouger ou pas, {angle de vision horizontal, angle de vision vertical, 1 stud en avant de la tête, 0 au dessus de la tête
Dans ce tutoriel sur Roblox, tu vas pouvoir apprendre à créer un parcours de plateformes dynamique et dangereux. Le principe est simple : plusieurs chemins sont proposés au joueur pour atteindre la sortie du niveau. Mais attention, les différents chemins ne restent pas visibles éternellement ! Grâce à un script en Lua, certains chemins disparaissent régulièrement au fil du temps. Le joueur doit donc observer, réfléchir rapidement et changer de direction avant de tomber dans le vide.
Création de la structure des différents chemins
Dans un premier temps, créer une structure de différents chemins composés de plateformes.
Créer un Folder principal nommé PathsFinding puis des sous Folder qui représenteront tes différents chemins possibles Path01, Path02 … puis sous les différents chemins créer tes plateformes, puis un Script :
Programmation de l’animation des différents chemins
Saisie ce code dans ton script, ce script en Lua pour Roblox permet de créer plusieurs chemins de plateformes qui apparaissent et disparaissent automatiquement.
Au début, le programme récupère le service TweenService, utilisé pour créer des animations fluides comme les effets de disparition et d’apparition.
Les variables de configuration définissent combien de temps un chemin reste visible, le temps d’attente entre les transitions et la durée de l’animation :
local DISPLAY_DURATION = 3 — secondes d’affichage du chemin
local WAIT_TIME = 3 — secondes entre chaque transition
local FADE_DURATION = 0.8 — secondes pour le fade in/out
Le script récupère ensuite tous les dossiers contenant les chemins et les trie dans l’ordre (path1, path2, etc.).
Chaque plateforme est préparée : elle devient fixe (Anchored = true), invisible (Transparency = 1) et sans collision (CanCollide = false).
Les informations importantes des chemins sont stockées dans des tableaux pour rendre le script plus rapide et éviter des calculs inutiles. La fonction fadeParts() sert à rendre les plateformes visibles ou invisibles grâce à une animation progressive appelée “fade”. Au démarrage, seul le premier chemin apparaît et devient solide pour que le joueur puisse marcher dessus.
Ensuite, une boucle infinie fait apparaître le chemin suivant pendant que l’ancien disparaît progressivement. Grâce à ce système, le joueur doit constamment changer de chemin pour éviter de tomber et réussir à trouver la sortie du niveau dans Roblox Studio.
local TweenService = game:GetService("TweenService")
-- Configuration
local DISPLAY_DURATION = 3 -- secondes d'affichage du chemin
local WAIT_TIME = 3 -- secondes entre chaque transition
local FADE_DURATION = 0.8 -- secondes pour le fade in/out
-- Récupérer tous les chemins du dossier
local pathsFinding = script.Parent
-- trie des chemins par leur nom : path1, path2, path3, ...
local children = pathsFinding:GetChildren()
table.sort(children, function(a, b) return a.Name < b.Name end)
local maxPaths = 0
-- Cache des parts par niveau (évite GetChildren() en boucle)
local pathFinding = {}
-- Cache des données par niveau (évite GetAttribute à chaque frame)
local pathData = {}
-- Prépare les données des différents chemins
for i, path in ipairs(children) do
if not path:IsA("Folder") then continue end
local parts = {}
for _, part in ipairs(path:GetChildren()) do
if not part:IsA("BasePart") then continue end
part.Anchored = true
part.CanCollide = false
part.Transparency = 1
table.insert(parts, part)
end
maxPaths += 1
pathFinding[maxPaths] = parts
-- Stocke les attibuts en mémoire
pathData[maxPaths] = {
displayDuration = path:GetAttribute("DisplayDuration") or DISPLAY_DURATION,
waitTime = path:GetAttribute("WaitTime") or WAIT_TIME,
}
end
-- TweenInfo créé une seule fois
local tweenInfo = TweenInfo.new(FADE_DURATION, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut)
-- Rend visible ou invisible tous les les parts d'un chemin
local function fadeParts(parts, targetTransparency, canCollide)
for _, part in ipairs(parts) do
TweenService:Create(part, tweenInfo, {
Transparency = targetTransparency
}):Play()
if canCollide ~= nil then
part.CanCollide = canCollide
end
end
end
-- Affiche le premier chemin au démarrage
fadeParts(pathFinding[1], 0, true)
local currentIndex = 1
while true do
-- Attendre avant de rendre in le chemin actuel
task.wait(pathData[currentIndex].displayDuration)
-- Calcule l'index du prochain chemin (boucle)
local nextIndex = (currentIndex % maxPaths) + 1
-- Transition simultanée : fade out ancien + fade in nouveau
fadeParts(pathFinding[nextIndex], 0, true)
-- Attendre avant de rendre invisible le chemin actuel
task.wait(pathData[currentIndex].waitTime)
fadeParts(pathFinding[currentIndex], 1, false)
-- Attendre le temps du fadout avant de continuer
task.wait(FADE_DURATION)
-- Mise à jour l'index pour la prochaine itération
currentIndex = nextIndex
end
Dans ce tutoriel, tu vas apprendre à programmer des ennemis très simples dans Roblox. Les ennemis sont représentés par des blocs. Les ennemies détectent un joueur proche, avancent vers lui, et lui infligent des dégâts lorsqu’ils le touchent. Tu vas construire ton script pour gérer plusieurs ennemis qui pourront avoir des comportements différents : distance proche différence, vitesse de déplacement différente, niveau de dommage différent.
Les ennemis s’approchent
Dans un premier temps construis l’arborescence suivante avec un Folder qui va regrouper tous tes ennemis, un script, des Part qui vont représenter tes ennemis :
Renomme les éléments de ton arborescence :
Folder : Enemies
Script : EnemiesScript
Part : Enemy
Puis saisie le code suivant dans ton script pour que tous les blocs ennemis s’approchent du joueur à partir d’une certaine distance entre le joueur et le bloc :
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Configuration
local DETECTION_DISTANCE = 30 -- distance de détection du joueur (studs)
local SPEED = 10 -- vitesse de déplacement (studs/s)
local STOP_DISTANCE = 3 -- distance à laquelle la part s'arrête
local enemies = script.Parent
-- Cache des données par platform (évite GetAttribute à chaque frame)
local enemiesData = {}
-- Initialisation
for _, enemy in enemies:GetChildren() do
if not enemy:IsA("BasePart") then continue end
enemy.Anchored = true
-- Stocke les données en mémoire
enemiesData[enemy] = {
detectionDistance = enemy:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
speed = enemy:GetAttribute("Speed") or SPEED,
stopDistance = enemy:GetAttribute("StopDistance") or STOP_DISTANCE,
}
end
RunService.Heartbeat:Connect(function(deltaTime)
-- Récupère le joueur local (ou le premier joueur connecté côté serveur)
local player = Players:GetPlayers()[1]
if not player then return end
local character = player.Character
if not character then return end
-- Récupère la position du joueur
local root = character:FindFirstChild("HumanoidRootPart")
if not root then return end
for enemy, data in pairs(enemiesData) do
-- Vérifie que la plateforme existe encore
if not enemy or not enemy.Parent then
enemyData[enemy] = nil
continue
end
-- Vérifie que le joueur est à portée
local distance = (root.Position - enemy.Position).Magnitude
-- Trop loin : la part attend
if distance > data.detectionDistance then continue end
-- Assez proche : arrêt
if data.stopDistance > 0 and distance <= data.stopDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Calcul de la direction vers le joueur
local direction = (root.Position - enemy.Position).Unit
local newPosition = enemy.Position + direction * SPEED * deltaTime
-- Mise à jour de la force à appliquer en fonction de la vitesse
enemy.AssemblyLinearVelocity = direction * data.speed
-- Mise à jour de la position de la part et son orientation
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) + direction * data.speed * deltaTime
end
end)
Lance le jeu : les blocs s’approchent du joueur mais restent à une certaine distance du joueur.
Les ennemis infligent des dommages au joueur
Dans le script suivant les blocs s’approchent à toucher le joueur, et lui infligent des dommages :
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Configuration
local DETECTION_DISTANCE = 30 -- distance de détection du joueur (studs)
local SPEED = 10 -- vitesse de déplacement (studs/s)
local STOP_DISTANCE = 0 -- distance à laquelle la part s'arrête
local DAMAGE = 5 -- dégâts infligés aux joueurs
local RESET_DELAY = 0.5 -- délai avant de pouvoir toucher à nouveau (secondes)
local enemies = script.Parent
-- Cache des données par platform (évite GetAttribute à chaque frame)
local enemiesData = {}
-- Initialisation
for _, enemy in enemies:GetChildren() do
if not enemy:IsA("BasePart") then continue end
enemy.Anchored = true
enemy.CanCollide = true
-- Stocke les données en mémoire
enemiesData[enemy] = {
detectionDistance = enemy:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
speed = enemy:GetAttribute("Speed") or SPEED,
stopDistance = enemy:GetAttribute("StopDistance") or STOP_DISTANCE,
damage = enemy:GetAttribute("Damage") or DAMAGE,
resetDelay = enemy:GetAttribute("ResetDelay") or RESET_DELAY,
}
enemy.Touched:Connect(function(otherPart)
-- Vérifie que l'objet touché est un personnage
local character = otherPart.Parent
if not Players:GetPlayerFromCharacter(character) then return end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
-- Vérifie que la part n'a pas déjà été touchée
if enemy:FindFirstChild("Touched") then return end
-- Applique les dégâts et empêche la répétition
humanoid:TakeDamage((enemiesData[enemy].damage))
enemy.AssemblyLinearVelocity = Vector3.zero
-- Marque la part comme touchée
local tag = Instance.new("Folder")
tag.Name = "Touched"
tag.Parent = enemy
Debris:AddItem(tag, enemiesData[enemy].resetDelay)
end)
end
RunService.Heartbeat:Connect(function(deltaTime)
-- Récupère le joueur local (ou le premier joueur connecté côté serveur)
local player = Players:GetPlayers()[1]
if not player then return end
local character = player.Character
if not character then return end
-- Récupère la position du joueur
local root = character:FindFirstChild("HumanoidRootPart")
if not root then return end
for enemy, data in pairs(enemiesData) do
-- Vérifie que la plateforme existe encore
if not enemy or not enemy.Parent then
enemyData[enemy] = nil
continue
end
-- Si la part est déja touchée, on ne fait rien
if enemy:FindFirstChild("Touched") then return end
-- Vérifie que le joueur est à portée
local distance = (root.Position - enemy.Position).Magnitude
-- Trop loin : la part attend
if distance > data.detectionDistance then return end
-- Assez proche : arrêt
if data.stopDistance > 0 and distance <= data.stopDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Calcul de la direction vers le joueur
local direction = (root.Position - enemy.Position).Unit
-- Mise à jour de la force à appliquer en fonction de la vitesse
enemy.AssemblyLinearVelocity = direction * data.speed
-- Mise à jour de la position de la part et son orientation
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) + direction * data.speed * deltaTime
end
end)
Paramétrer chaque ennemis
Ajoute aux propriétés de chaque ennemi les attributs suivants avec des valeurs différentes :
DetectionDistance : distance de détection du joueur (studs)
Speed : vitesse de déplacement (studs/s)
StopDistance : distance à laquelle la part s’arrête si 0 l’ennemi vient toucher le joueur
Damage : dégâts infligés aux joueurs
ResetDelay : délai avant de pouvoir toucher à nouveau (secondes)
Puis saisie le Nom de l’attribut ainsi que son type number :
Puis donne une valeur pour chaque attribut :
L’ennemi est une boule
Ajoute dans le code pour un nouveau comportement pour faire rouler le boule :
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Configuration
local DETECTION_DISTANCE = 30 -- distance de détection du joueur (studs)
local SPEED = 10 -- vitesse de déplacement (studs/s)
local STOP_DISTANCE = 0 -- distance à laquelle la part s'arrête
local DAMAGE = 5 -- dégâts infligés aux joueurs
local RESET_DELAY = 0.5 -- délai avant de pouvoir toucher à nouveau (secondes)
local enemies = script.Parent
-- Cache des données par platform (évite GetAttribute à chaque frame)
local enemiesData = {}
-- Initialisation
for _, enemy in enemies:GetChildren() do
if not enemy:IsA("BasePart") then continue end
enemy.Anchored = true
enemy.CanCollide = true
-- Stocke les données en mémoire
enemiesData[enemy] = {
detectionDistance = enemy:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
speed = enemy:GetAttribute("Speed") or SPEED,
stopDistance = enemy:GetAttribute("StopDistance") or STOP_DISTANCE,
damage = enemy:GetAttribute("Damage") or DAMAGE,
resetDelay = enemy:GetAttribute("ResetDelay") or RESET_DELAY,
}
enemy.Touched:Connect(function(otherPart)
-- Vérifie que l'objet touché est un personnage
local character = otherPart.Parent
if not Players:GetPlayerFromCharacter(character) then return end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
-- Vérifie que la part n'a pas déjà été touchée
if enemy:FindFirstChild("Touched") then return end
-- Applique les dégâts et empêche la répétition
enemy.AssemblyLinearVelocity = Vector3.zero
enemy.AssemblyAngularVelocity = Vector3.zero
humanoid:TakeDamage((enemiesData[enemy].damage))
-- Marque la part comme touchée
local tag = Instance.new("Folder")
tag.Name = "Touched"
tag.Parent = enemy
Debris:AddItem(tag, enemiesData[enemy].resetDelay)
end)
end
RunService.Heartbeat:Connect(function(deltaTime)
-- Récupère le joueur local (ou le premier joueur connecté côté serveur)
local player = Players:GetPlayers()[1]
if not player then return end
local character = player.Character
if not character then return end
-- Récupère la position du joueur
local root = character:FindFirstChild("HumanoidRootPart")
if not root then return end
for enemy, data in pairs(enemiesData) do
-- Vérifie que la plateforme existe encore
if not enemy or not enemy.Parent then
--enemyData[enemy] = nil
continue
end
-- Si la part est déja touchée, on ne fait rien
if enemy:FindFirstChild("Touched") then continue end
-- Vérifie que le joueur est à portée
local distance = (root.Position - enemy.Position).Magnitude
-- Trop loin : la part attend
if distance > data.detectionDistance then continue end
-- Assez proche : arrêt
if data.stopDistance > 0 and distance <= data.stopDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Calcul de la direction vers le joueur
if enemy.Shape == Enum.PartType.Ball then
enemy.Anchored = false
-- Direction vers le joueur (ignorant Y pour rester au sol)
local direction = Vector3.new(
root.Position.X - enemy.Position.X,
0,
root.Position.Z - enemy.Position.Z
).Unit
-- Force appliquée vers le joueur
local mass = enemy.AssemblyMass
local targetVelocity = direction * data.speed
local correction = targetVelocity - Vector3.new(
enemy.AssemblyLinearVelocity.X,
0,
enemy.AssemblyLinearVelocity.Z
)
enemy:ApplyImpulse(correction * mass)
-- Rotation de roulement
local rollAxis = Vector3.new(-direction.Y, 0, -direction.X)
local rollSpeed = data.speed / (enemy.Size.Y / 2)
if enemy.Size.X > enemy.Size.Y then
rollSpeed = data.speed / (enemy.Size.X / 2)
end
enemy.AssemblyAngularVelocity = rollAxis * rollSpeed
else
local direction = (root.Position - enemy.Position).Unit
-- Mise à jour de la force à appliquer en fonction de la vitesse
enemy.AssemblyLinearVelocity = direction * data.speed
-- Mise à jour de la position de la part et son orientation
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) + direction * data.speed * deltaTime
end
end
end)
Les ennemies sont des RIG
Ajoute des rig sous une structure suivante :
Pour ajouter un RIG choisis :
Ajoute sous chaque Rig un script que tu renommes Animate, ce script anime le Rig pour qu’il marche ou qu’il attende en fonction de la situation :
local humanoid = script.Parent:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
-- IDs des animations R6 par défaut de Roblox
local ANIMATIONS = {
idle = "rbxassetid://180435571",
walk = "rbxassetid://180426354",
run = "rbxassetid://180426354",
jump = "rbxassetid://125750702",
fall = "rbxassetid://180436148",
}
-- Charge les animations
local tracks = {}
for name, id in pairs(ANIMATIONS) do
local anim = Instance.new("Animation")
anim.AnimationId = id
tracks[name] = animator:LoadAnimation(anim)
end
-- Priorités
tracks.idle.Priority = Enum.AnimationPriority.Idle
tracks.walk.Priority = Enum.AnimationPriority.Movement
-- Joue l'idle au démarrage
tracks.idle:Play()
local currentAnim = "idle"
-- Vérification de vélocité pour gérer l'animation
local VELOCITY_THRESHOLD = 0.5 -- en dessous = considéré à l'arrêt
game:GetService("RunService").Heartbeat:Connect(function()
local velocity = humanoid.RootPart and
humanoid.RootPart.AssemblyLinearVelocity or Vector3.zero
local isMoving = Vector3.new(velocity.X, 0, velocity.Z).Magnitude > VELOCITY_THRESHOLD
if isMoving then
if currentAnim ~= "walk" then
tracks.idle:Stop()
tracks.walk:Play()
currentAnim = "walk"
end
else
if currentAnim == "walk" then
tracks.walk:Stop()
tracks.idle:Play()
currentAnim = "idle"
end
end
end)
Sous ton folder principal, créer un script et ajoute ce code :
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
-- Configuration
local DETECTION_DISTANCE = 60
local STOP_DISTANCE = 3
local SPEED = 10
local UPDATE_INTERVAL = 0.5 -- réduit pour un mouvement plus fluide
local DAMAGE = 5
local RESET_DELAY = 0.5
local rigsFolder = script.Parent
local enemieRigData = {}
-- Crée une hitbox soudée sur le HumanoidRootPart
local function createHitbox(rig)
local root = rig:FindFirstChild("HumanoidRootPart")
if not root then return nil end
local hitbox = Instance.new("Part")
hitbox.Name = "Hitbox_" .. rig.Name
hitbox.Shape = Enum.PartType.Ball
hitbox.Size = Vector3.new(5, 5, 5)
hitbox.Transparency = 0.8
hitbox.CanCollide = false
hitbox.CanTouch = true
hitbox.Anchored = false
hitbox.CastShadow = false
hitbox.Massless = true
hitbox.Color = Color3.fromRGB(255, 0, 0)
hitbox.Material = Enum.Material.Neon
hitbox.CFrame = root.CFrame * CFrame.new(0, 1, -0.5)
hitbox.Parent = workspace
local weld = Instance.new("WeldConstraint")
weld.Part0 = root
weld.Part1 = hitbox
weld.Parent = hitbox
-- Nettoyage automatique si le rig est détruit
rig.AncestryChanged:Connect(function()
if not rig.Parent then
hitbox:Destroy()
enemieRigData[rig] = nil
end
end)
return hitbox
end
-- Arrête le rig
local function stopRig(data)
if not data.isMoving then return end
data.humanoid.WalkSpeed = 0
data.isMoving = false
end
-- Déplace le rig vers une cible
local function chaseTarget(data, targetPosition)
local now = time()
if now - data.lastUpdate < data.updateInterval then return end
data.lastUpdate = now
data.humanoid.WalkSpeed = data.speed
data.humanoid:MoveTo(targetPosition)
data.isMoving = true
end
-- Initialisation des rigs
for _, rig in ipairs(rigsFolder:GetChildren()) do
if not rig:IsA("Model") then continue end
local humanoid = rig:FindFirstChildOfClass("Humanoid")
local rootPart = rig:FindFirstChild("HumanoidRootPart")
if not humanoid or not rootPart then continue end
local hitbox = createHitbox(rig)
if not hitbox then continue end
enemieRigData[rig] = {
detectionDistance = rig:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
speed = rig:GetAttribute("Speed") or SPEED,
stopDistance = rig:GetAttribute("StopDistance") or STOP_DISTANCE,
updateInterval = rig:GetAttribute("UpdateInterval") or UPDATE_INTERVAL,
damage = rig:GetAttribute("Damage") or DAMAGE,
hitbox = hitbox,
humanoid = humanoid,
rootPart = rootPart,
lastUpdate = 0,
isMoving = false,
lastHit = 0,
}
-- Détection de collision avec le joueur
hitbox.Touched:Connect(function(hit)
local data = enemieRigData[rig]
if not data then return end
-- Vérifie que c'est bien le joueur (pas une autre hitbox)
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
local humanoidTarget = character:FindFirstChildOfClass("Humanoid")
local player = Players:GetPlayerFromCharacter(character)
if not humanoidTarget or not player then return end
-- Cooldown pour éviter les dégâts multiples
local now = time()
if now - data.lastHit < RESET_DELAY then return end
data.lastHit = now
humanoidTarget:TakeDamage(data.damage)
print(rig.Name, "inflige", data.damage, "dégâts à", player.Name)
end)
end
-- Heartbeat : déplacement de tous les rigs
RunService.Heartbeat:Connect(function()
local player = Players:GetPlayers()[1]
if not player then return end
local character = player.Character
if not character then return end
local root = character:FindFirstChild("HumanoidRootPart")
if not root then return end
for rig, data in pairs(enemieRigData) do
if not rig.Parent then continue end
local distance = (root.Position - data.rootPart.Position).Magnitude
if distance > data.detectionDistance or distance <= data.stopDistance then
stopRig(data)
continue
end
chaseTarget(data, root.Position)
end
end)
Les Rig disposent d’une Hitbox pour gérer la collision avec le joueur, pour rendre complétement transparente cette Hitbox, modifie sa Tranparency :
-- Crée une hitbox soudée sur le HumanoidRootPart
local function createHitbox(rig)
local root = rig:FindFirstChild("HumanoidRootPart")
if not root then return nil end
local hitbox = Instance.new("Part")
hitbox.Name = "Hitbox_" .. rig.Name
hitbox.Shape = Enum.PartType.Ball
hitbox.Size = Vector3.new(5, 5, 5)
hitbox.Transparency = 1
Dans Roblox, les textures servent à donner un aspect plus réaliste aux objets du jeu et donne du relief au jeu. Au lieu d’avoir seulement une couleur simple, on peut ajouter des images pour représenter du bois, de la pierre, du métal ou encore de l’herbe. La texture principale correspond à l’image visible sur l’objet, appelée souvent “Color” ou “Albedo”, qui donne les couleurs et les détails. Il existe aussi des images spéciales comme la Normal Map : elle ne change pas la forme réelle de l’objet, mais elle crée une illusion de relief avec la lumière, par exemple des bosses, des fissures ou des briques. D’autres textures peuvent contrôler la brillance, les reflets ou les zones transparentes, ce qui permet de créer des matériaux beaucoup plus réalistes dans un jeu Roblox.
Comment affecter une texture existante
Crée un part :
Associe au part une texture existante :
Puis choisis une couleur :
Essaye les autres textures.
Comment créer une nouvelle texture
Tu peux utiliser ce site pour créer une nouvelle texture ou ce site pour trouver des textures libre de droit :
Choisir une texture sur un site :
Modifie la taille de l’image dans Paint pour une taille de 512×512 pixels :
Puis générer une image « normalMap » pour le relief avec ce site :
Clic avec le bouton gauche de ta souris pour charger ton image :
Modifie les différents paramètres pour obtenir l’image que tu souhaites :
Puis saisis le nom de ton fichier puis clic sur Download :
Maintenant que tes deux images sont prêtes, dans ROBLOX demande l’ouverture du gestionnaire des matériaux :
Crée un nouveau matériau en cliquant sur le + en haut à gauche :