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)
Des ennemis qui ne traversent pas les murs (Raycast) :
-- recherche des services
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Configuration
local DETECTION_DISTANCE = 60 -- distance de détection du joueur (studs)
local SPEED = 12 -- vitesse de déplacement (studs/s)
local STOP_DISTANCE = 10 -- distance à laquelle la part s'arrête
-- recherche des ennemis dans le workspace sous le folder "Monstre"
local enemies = workspace.GAME:WaitForChild("Monstre")
-- Paramètres dynamiques par ennemi
local enemiesData = {}
-- Initialisation de tous les ennemis
for _, enemy in enemies:GetChildren() do
-- contrôle si c'est une part qui représente un ennemi
if not enemy:IsA("BasePart") then continue end
enemy.Anchored = true
-- Stocke les données en mémoire pour chaque ennemi
enemiesData[enemy] = {
detectionDistance = enemy:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
speed = enemy:GetAttribute("Speed") or SPEED,
stopDistance = enemy:GetAttribute("StopDistance") or STOP_DISTANCE,
cooldown = false,
}
-- dégats sur le joueur si il touche l'ennemi'
enemy.Touched:Connect(function(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
if enemiesData[enemy].cooldown then return end
enemiesData[enemy].cooldown = true
humanoid:TakeDamage(10)
task.delay(0.5, function() enemiesData[enemy].cooldown = false end)
end)
end
--[[ Configuration du Raycast (exclure les ennemis eux-mêmes et le personnage)
Pour éviter que les parts traversent les murs, il faut faire un Raycast entre la part et le joueur.
Si le rayon touche un mur avant le joueur on stoppe la part.
]]
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
RunService.Heartbeat:Connect(function(deltaTime)
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
-- Met à jour les exclusions à chaque frame (Player + tous les ennemis exclus)
local excluded = { character }
for enemy in pairs(enemiesData) do
if enemy and enemy.Parent then
table.insert(excluded, enemy)
end
end
raycastParams.FilterDescendantsInstances = excluded
for enemy, data in pairs(enemiesData) do
if not enemy or not enemy.Parent then
enemiesData[enemy] = nil
continue
end
-- Calcul de la direction et de la distance entre l'ennemi et le joueur
local toPlayer = root.Position - enemy.Position
local distance = toPlayer.Magnitude
local direction = toPlayer.Unit
-- Trop loin : la part attend
if distance > data.detectionDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Assez proche : arrêt
if data.stopDistance > 0 and distance <= data.stopDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Raycast entre l'ennemi et le joueur
local rayResult = workspace:Raycast(enemy.Position, direction * distance, raycastParams)
if rayResult then
-- Un mur est détecté avant le joueur : on stoppe
enemy.AssemblyLinearVelocity = Vector3.zero
-- Optionnel : orienter quand même vers le joueur
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
else
-- Chemin libre : déplacement normal
enemy.AssemblyLinearVelocity = direction * data.speed
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 :

-- recherche des services
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Configuration
local DETECTION_DISTANCE = 60 -- distance de détection du joueur (studs)
local SPEED = 12 -- vitesse de déplacement (studs/s)
local STOP_DISTANCE = 10 -- distance à laquelle la part s'arrête
-- recherche des ennemis dans le workspace sous le folder "Monstre"
local enemies = workspace.GAME:WaitForChild("Monstre")
-- Paramètres dynamiques par ennemi
local enemiesData = {}
-- Initialisation de tous les ennemis
for _, enemy in enemies:GetChildren() do
-- contrôle si c'est une part qui représente un ennemi
if not enemy:IsA("BasePart") then continue end
enemy.Anchored = true
-- Stocke les données en mémoire pour chaque ennemi
enemiesData[enemy] = {
detectionDistance = enemy:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
speed = enemy:GetAttribute("Speed") or SPEED,
stopDistance = enemy:GetAttribute("StopDistance") or STOP_DISTANCE,
cooldown = false,
}
-- dégats sur le joueur si il touche l'ennemi'
enemy.Touched:Connect(function(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
if enemiesData[enemy].cooldown then return end
enemiesData[enemy].cooldown = true
character:PivotTo(CFrame.new(-10, 1.5, -69))
task.delay(0.5, function() enemiesData[enemy].cooldown = false end)
end)
end
--[[ Configuration du Raycast (exclure les ennemis eux-mêmes et le personnage)
Pour éviter que les parts traversent les murs, il faut faire un Raycast entre la part et le joueur.
Si le rayon touche un mur avant le joueur on stoppe la part.
]]
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
RunService.Heartbeat:Connect(function(deltaTime)
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
-- Met à jour les exclusions à chaque frame (Player + tous les ennemis exclus)
local excluded = { character }
for enemy in pairs(enemiesData) do
if enemy and enemy.Parent then
table.insert(excluded, enemy)
end
end
raycastParams.FilterDescendantsInstances = excluded
for enemy, data in pairs(enemiesData) do
if not enemy or not enemy.Parent then
enemiesData[enemy] = nil
continue
end
-- Calcul de la direction et de la distance entre l'ennemi et le joueur
local toPlayer = root.Position - enemy.Position
local distance = toPlayer.Magnitude
local direction = toPlayer.Unit
-- Trop loin : la part attend
if distance > data.detectionDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Assez proche : arrêt
if data.stopDistance > 0 and distance <= data.stopDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Raycast entre l'ennemi et le joueur
local rayResult = workspace:Raycast(enemy.Position, direction * distance, raycastParams)
if rayResult then
-- Un mur est détecté avant le joueur : on stoppe
enemy.AssemblyLinearVelocity = Vector3.zero
-- Optionnel : orienter quand même vers le joueur
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
else
-- Chemin libre : déplacement normal
enemy.AssemblyLinearVelocity = direction * data.speed
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
+ direction * data.speed * deltaTime
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
Exemple d’un code avec des ennemis qui ne peuvent pas traverser un mur, utilisation de Raycast :
-- recherche des services
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Configuration
local DETECTION_DISTANCE = 60 -- distance de détection du joueur (studs)
local SPEED = 3 -- vitesse de déplacement (studs/s)
local STOP_DISTANCE = 2 -- distance à laquelle la part s'arrête
-- recherche des ennemis dans le workspace sous le folder "Monstre"
local enemies = workspace.GAME:WaitForChild("Monstre")
-- Paramètres dynamiques par ennemi
local enemiesData = {}
-- Initialisation de tous les ennemis
for _, enemy in enemies:GetChildren() do
-- contrôle si c'est une part qui représente un ennemi
if not enemy:IsA("BasePart") then continue end
enemy.Anchored = true
-- Stocke les données en mémoire pour chaque ennemi
enemiesData[enemy] = {
detectionDistance = enemy:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
speed = enemy:GetAttribute("Speed") or SPEED,
stopDistance = enemy:GetAttribute("StopDistance") or STOP_DISTANCE,
cooldown = false,
}
-- dégats sur le joueur si il touche l'ennemi'
enemy.Touched:Connect(function(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
if enemiesData[enemy].cooldown then return end
enemiesData[enemy].cooldown = true
humanoid:TakeDamage(10)
task.delay(0.5, function() enemiesData[enemy].cooldown = false end)
end)
end
--[[ Configuration du Raycast (exclure les ennemis eux-mêmes et le personnage)
Pour éviter que les parts traversent les murs, il faut faire un Raycast entre la part et le joueur.
Si le rayon touche un mur avant le joueur on stoppe la part.
]]
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
RunService.Heartbeat:Connect(function(deltaTime)
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
-- Met à jour les exclusions à chaque frame (Player + tous les ennemis exclus)
local excluded = { character }
for enemy in pairs(enemiesData) do
if enemy and enemy.Parent then
table.insert(excluded, enemy)
end
end
raycastParams.FilterDescendantsInstances = excluded
for enemy, data in pairs(enemiesData) do
if not enemy or not enemy.Parent then
enemiesData[enemy] = nil
continue
end
-- Calcul de la direction et de la distance entre l'ennemi et le joueur
local toPlayer = root.Position - enemy.Position
local distance = toPlayer.Magnitude
local direction = toPlayer.Unit
-- Trop loin : la part attend
if distance > data.detectionDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Assez proche : arrêt
if data.stopDistance > 0 and distance <= data.stopDistance then
enemy.AssemblyLinearVelocity = Vector3.zero
continue
end
-- Raycast entre l'ennemi et le joueur
local rayResult = workspace:Raycast(enemy.Position, direction * distance, raycastParams)
if rayResult then
-- Un mur est détecté avant le joueur : on stoppe
enemy.AssemblyLinearVelocity = Vector3.zero
-- Optionnel : orienter quand même vers le joueur
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
else
-- Chemin libre : déplacement normal
enemy.AssemblyLinearVelocity = direction * data.speed
enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
+ direction * data.speed * deltaTime
end
end
end)
