Dans ce tutoriel, vous apprendrez à lancer un laser depuis le blaster dans Créer des outils pour les joueurs et à détecter s'il touche un joueur.
Raycasting pour trouver des collisions
Raycasting crée un rayon invisible à partir d'une position de départ vers une direction donnée avec une longueur définie. Si le rayon entre en collision avec des objets ou le terrain sur son chemin, il renverra des informations sur la collision telles que la position et l'objet avec lequel il est entré en collision.

Trouver la position de la souris
Avant qu'un laser puisse être tiré, vous devez d'abord savoir où le joueur vise. Cela peut être trouvé en raycastant à partir de la position 2D de la souris du joueur sur l'écran directement vers l'avant depuis la caméra dans le monde 3D. Le rayon entrera en collision avec tout ce que le joueur vise avec la souris.
Ouvrez le script ToolController à l'intérieur de l'outil Blaster depuis Créer des outils pour les joueurs. Si vous n'avez pas encore terminé ce tutoriel, vous pouvez télécharger le modèle Blaster et l'insérer dans StarterPack.

En haut du script, déclarez une constante nommée MAX_MOUSE_DISTANCE avec une valeur de 1000.
Créez une fonction appelée getWorldMousePosition.
local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()endlocal function toolActivated()tool.Handle.Activate:Play()end-- Connecter les événements aux fonctions appropriéestool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)Utilisez la fonction GetMouseLocation de UserInputService pour obtenir la position 2D de la souris du joueur sur l'écran. Assignez cela à une variable nommée mouseLocation.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
Maintenant que la position 2D de la souris est connue, ses propriétés X et Y peuvent être utilisées comme paramètres pour la fonction Camera:ViewportPointToRay(), qui crée un Ray de l'écran vers le monde 3D.
Utilisez les propriétés X et Y de mouseLocation comme arguments pour la fonction ViewportPointToRay(). Assignez cela à une variable nommée screenToWorldRay.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Créer un rayon à partir de la position 2D de la sourislocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)end
Il est temps d'utiliser la fonction Raycast pour vérifier si le rayon touche un objet. Cela nécessite une position de départ et un vecteur de direction : dans cet exemple, vous utiliserez les propriétés d'origine et de direction de screenToWorldRay.
La longueur du vecteur de direction détermine jusqu'où le rayon voyagera. Le rayon doit être aussi long que le MAX_MOUSE_DISTANCE, donc vous devrez multiplier le vecteur de direction par MAX_MOUSE_DISTANCE.
Déclarez une variable nommée directionVector et assignez-lui la valeur de screenToWorldRay.Direction multipliée par MAX_MOUSE_DISTANCE.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Créer un rayon à partir de la position 2D de la sourislocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Le vecteur de direction unitaire du rayon multiplié par une distance maximalelocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCEAppelez la fonction Raycast de workspace, en passant la propriété Origin de screenToWorldRay comme premier argument et directionVector comme second. Assignez cela à une variable nommée raycastResult.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Créer un rayon à partir de la position 2D de la sourislocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Le vecteur de direction unitaire du rayon multiplié par une distance maximalelocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Raycast à partir de l'origine du rayon vers sa directionlocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
Informations sur la collision
Si l'opération de raycast trouve un objet touché par le rayon, elle renverra un RaycastResult, qui contient des informations sur la collision entre le rayon et l'objet.
| Propriété RaycastResult | Description |
|---|---|
| Instance | Le BasePart ou la cellule Terrain que le rayon a intersecté. |
| Position | Où l'intersection a eu lieu ; généralement un point directement sur la surface d'une pièce ou du terrain. |
| Matériau | Le matériau au point de collision. |
| Normal | Le vecteur normal de la face intersectée. Cela peut être utilisé pour déterminer dans quelle direction la face pointe. |
La propriété Position sera la position de l'objet sur lequel la souris survole. Si la souris ne survole aucun objet dans un rayon de MAX_MOUSE_DISTANCE, raycastResult sera nil.
Créez une instruction if pour vérifier si raycastResult existe.
Si raycastResult a une valeur, renvoyez sa propriété Position.
Si raycastResult est nil, trouvez la fin du raycast. Calculez la position 3D de la souris en ajoutant screenToWorldRay.Origin et directionVector ensemble.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Créer un rayon à partir de la position 2D de la souris
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Le vecteur de direction unitaire du rayon multiplié par une distance maximale
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast à partir de l'origine du rayon vers sa direction
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Retourner le point 3D d'intersection
return raycastResult.Position
else
-- Aucun objet n'a été touché, donc calculez la position à la fin du rayon
return screenToWorldRay.Origin + directionVector
end
endTirer vers la cible
Maintenant que la position 3D de la souris est connue, elle peut être utilisée comme une position cible pour tirer un laser. Un second rayon peut être tiré entre la position de l'arme du joueur et la position cible en utilisant la fonction Raycast.
Déclarez une constante appelée MAX_LASER_DISTANCE en haut du script et assignez-lui la valeur de 500, ou votre portée choisie pour le blaster laser.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Créez une fonction appelée fireWeapon sous la fonction getWorldMousePosition.
Appelez getWorldMousePosition et assignez le résultat à une variable nommée mousePosition. Ce sera la position cible pour le raycast.
-- Aucun objet n'a été touché, donc calculez la position à la fin du rayonreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
Cette fois, le vecteur de direction pour la fonction raycast représentera la direction de la position de l'outil du joueur vers la position cible.
Déclarez une variable nommée targetDirection et calculez le vecteur de direction en soustrayant la position de l'outil de mouseLocation.
Normalisez le vecteur en utilisant sa propriété Unit. Cela lui donne une magnitude de 1, ce qui le rend facile à multiplier par une longueur plus tard.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- Calculer un vecteur de direction normalisé et multiplier par la distance du laserlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendDéclarez une variable nommée directionVector et assignez-lui le targetDirection multiplié par le MAX_LASER_DISTANCE.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- La direction pour tirer l'arme, multipliée par une distance maximalelocal directionVector = targetDirection * MAX_LASER_DISTANCEend
Un objet RaycastParams peut être utilisé pour stocker des paramètres supplémentaires pour la fonction raycast. Il sera utilisé dans votre blaster laser pour s'assurer que le raycast ne touche pas accidentellement le joueur tirant l'arme. Toute pièce incluse dans la propriété FilterDescendantsInstances d'un objet RaycastParams sera ignorée dans le raycast.
Continuez la fonction fireWeapon et déclarez une variable appelée weaponRaycastParams. Assignez-lui un nouvel objet RaycastParams.
Créez une table contenant le personnage local du joueur et assignez-la à la propriété weaponRaycastParams.FilterDescendantsInstances.
Raycast à partir de la position de la poignée de l'outil du joueur, dans une direction vers le directionVector. N'oubliez pas d'ajouter weaponRaycastParams comme argument cette fois. Assignez cela à une variable nommée weaponRaycastResult.
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local tool = script.Parent
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local function getWorldMousePosition()local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calculer un vecteur de direction normalisé et multiplier par la distance du laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- La direction pour tirer l'arme multipliée par une distance maximale
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorer le personnage du joueur pour éviter qu'il ne se blesse lui-même
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
endEnfin, vous devrez vérifier que l'opération de raycast a renvoyé une valeur. Si une valeur est renvoyée, un objet a été touché par le rayon et un laser peut être créé entre l'arme et l'emplacement touché. Si rien n'a été renvoyé, la position finale doit être calculée afin de créer le laser.
Déclarez une variable vide nommée hitPosition.
Utilisez une instruction if pour vérifier si weaponRaycastResult a une valeur. Si un objet a été touché, assignez weaponRaycastResult.Position à hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Vérifiez si des objets ont été touchés entre la position de départ et d'arrivéelocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendSi weaponRaycastResult n'a pas de valeur, calculez la position de fin du raycast en ajoutant ensemble la position de la poignée de l'outil avec le directionVector. Assignez cela à hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Vérifiez si des objets ont été touchés entre la position de départ et d'arrivéelocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Calculez la position de fin en fonction de la distance maximale du laserhitPosition = tool.Handle.Position + directionVectorendendNaviguez vers la fonction toolActivated et appelez la fonction fireWeapon afin que le laser tire chaque fois que l'outil est activé.
local function toolActivated()tool.Handle.Activate:Play()fireWeapon()end
Vérifier l'objet touché
Pour savoir si l'objet touché par le laser fait partie du personnage d'un joueur ou simplement d'un élément de décor, vous devrez rechercher un Humanoid, car chaque personnage en a un.
Tout d'abord, vous devrez trouver le modèle de personnage. Si une partie du personnage a été touchée, vous ne pouvez pas supposer que le parent de l'objet touché serait le personnage. Le laser pourrait avoir touché une partie du corps, un accessoire ou un outil, tous situés dans différentes parties de la hiérarchie du personnage.

Vous pouvez utiliser FindFirstAncestorOfClass pour trouver un ancêtre modèle de personnage de l'objet touché par le laser, s'il en existe un. Si vous trouvez un modèle et qu'il contient un humanoïde, dans la plupart des cas, vous pouvez supposer qu'il s'agit d'un personnage.
Ajoutez le code mis en surbrillance ci-dessous à l'instruction if de weaponRaycastResult pour vérifier si un personnage a été touché.
-- Vérifiez si des objets ont été touchés entre la position de départ et d'arrivéelocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- L'instance touchée sera un enfant d'un modèle de personnage-- Si un humanoïde est trouvé dans le modèle, alors c'est probablement le personnage d'un joueurlocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Joueur touché")endendelse-- Calculez la position de fin en fonction de la distance maximale du laserhitPosition = tool.Handle.Position + directionVectorend
Maintenant, le blaster laser devrait imprimer Joueur touché dans la fenêtre Sortie chaque fois que l'opération de raycast touche un autre joueur.
Tester avec plusieurs joueurs
Deux joueurs sont nécessaires pour tester si le raycast de l'arme trouve d'autres joueurs, donc vous devez démarrer un serveur local. Voir simulation multi-client pour les instructions.
Sur un client, testez le tir sur l'autre joueur avec l'arme en cliquant sur eux. "Joueur touché" devrait s'afficher dans la sortie chaque fois qu'un joueur est touché.
Trouver la position du laser
Le blaster doit tirer un faisceau de lumière rouge vers sa cible. La fonction pour cela sera à l'intérieur d'un ModuleScript afin qu'elle puisse être réutilisée dans d'autres scripts plus tard. Tout d'abord, le script devra trouver la position où le faisceau laser doit être rendu.
Créez un ModuleScript nommé LaserRenderer, parenté à StarterPlayerScripts sous StarterPlayer.

Ouvrez le script et renommez la table du module au nom du script LaserRenderer.
Déclarez une variable nommée SHOT_DURATION avec une valeur de 0.15. Ce sera la durée (en secondes) pendant laquelle le laser est visible.
Créez une fonction de LaserRenderer nommée createLaser avec deux paramètres appelés toolHandle et endPosition.
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- Temps pendant lequel le laser est visible-- Créer un faisceau laser d'une position de départ vers une position de finfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererDéclarez une variable nommée startPosition et définissez la propriété Position de toolHandle comme sa valeur. Ce sera la position du blaster laser du joueur.
Déclarez une variable nommée laserDistance et soustrayez endPosition de startPosition pour trouver la différence entre les deux vecteurs. Utilisez la propriété Magnitude de cela pour obtenir la longueur du faisceau laser.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendDéclarez une variable laserCFrame pour stocker la position et l'orientation du faisceau laser. La position doit être le point médian du début et de la fin du faisceau. Utilisez CFrame.lookAt pour créer un nouveau CFrame situé à startPosition et faisant face à endPosition. Multipliez cela par un nouveau CFrame avec une valeur d'axe Z de la moitié de -laserDistance pour obtenir le point médian.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)end
Créer la pièce laser
Maintenant que vous savez où créer un faisceau laser, vous devez ajouter le faisceau lui-même. Cela peut être fait facilement avec une pièce Neon.
Déclarez une variable laserPart et assignez-lui une nouvelle instance de Part.
Définissez les propriétés suivantes de laserPart :
- Size: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- Anchored: true
- CanCollide: false
- Color: Color3.fromRGB(225, 0, 0) (une couleur rouge vif)
- Material: Enum.Material.Neon
Parent laserPart à Workspace.
Ajoutez la pièce au service Debris afin qu'elle soit supprimée après le nombre de secondes dans la variable SHOT_DURATION.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)local laserPart = Instance.new("Part")laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)laserPart.CFrame = laserCFramelaserPart.Anchored = truelaserPart.CanCollide = falselaserPart.Color = Color3.fromRGB(225, 0, 0)laserPart.Material = Enum.Material.NeonlaserPart.Parent = workspace-- Ajouter le faisceau laser au service Debris pour être supprimé et nettoyéDebris:AddItem(laserPart, SHOT_DURATION)end
Maintenant, la fonction pour rendre le faisceau laser est complète, elle peut être appelée par le ToolController.
En haut du script ToolController, déclarez une variable nommée LaserRenderer et exigez le ModuleScript LaserRenderer situé dans PlayerScripts.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.ParentEn bas de la fonction fireWeapon, appelez la fonction createLaser de LaserRenderer en utilisant la poignée de l'outil et hitPosition comme arguments.
-- Calculez la position de fin en fonction de la distance maximale du laserhitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endTestez l'arme en cliquant sur le bouton Play. Un faisceau laser devrait être visible entre l'arme et la souris lorsque l'outil est activé.
Contrôler le taux de tir de l'arme
Les armes ont besoin d'un délai entre chaque tir pour empêcher les joueurs de causer trop de dégâts en peu de temps. Cela peut être contrôlé en vérifiant si suffisamment de temps s'est écoulé depuis le dernier tir d'un joueur.
Déclarez une variable en haut du ToolController appelée FIRE_RATE. Ce sera le temps minimum entre chaque tir. Donnez-lui une valeur de votre choix ; cet exemple utilise 0.3 secondes.
Déclarez une autre variable en dessous appelée timeOfPreviousShot avec une valeur de 0. Cela stocke le dernier moment où le joueur a tiré et sera mis à jour à chaque tir.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Créez une fonction appelée canShootWeapon sans paramètres. Cette fonction vérifiera combien de temps s'est écoulé depuis le dernier tir et renverra vrai ou faux.
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- Vérifiez si suffisamment de temps s'est écoulé depuis le dernier tirlocal function canShootWeapon()endlocal function getWorldMousePosition()À l'intérieur de la fonction, déclarez une variable nommée currentTime ; assignez-lui le résultat de l'appel à la fonction tick(). Cela renvoie combien de temps s'est écoulé, en secondes, depuis le 1er janvier 1970 (une date arbitraire largement utilisée pour calculer le temps).
Soustrayez timeOfPreviousShot de currentTime et renvoyez faux si le résultat est inférieur à FIRE_RATE ; sinon, renvoyez vrai.
-- Vérifiez si suffisamment de temps s'est écoulé depuis le dernier tirlocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendÀ la fin de la fonction fireWeapon, mettez à jour timeOfPreviousShot chaque fois que l'arme est tirée en utilisant tick.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endÀ l'intérieur de la fonction toolActivated, créez une instruction if et appelez canShootWeapon pour vérifier si l'arme peut être tirée.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
Lorsque vous testez le blaster, vous devriez constater que peu importe la rapidité avec laquelle vous cliquez, il y aura toujours un court délai de 0.3 secondes entre chaque tir.
Infliger des dégâts au joueur
Les clients ne peuvent pas infliger de dégâts à d'autres clients directement ; le serveur doit être responsable de l'infligement de dégâts lorsqu'un joueur est touché.
Les clients peuvent utiliser un RemoteEvent pour informer le serveur qu'un personnage a été touché. Ceux-ci doivent être stockés dans ReplicatedStorage, où ils sont visibles à la fois pour le client et le serveur.
Créez un Dossier dans ReplicatedStorage nommé Events.

Insérez un RemoteEvent dans le dossier Events et nommez-le DamageCharacter.

Dans ToolController, créez des variables au début du script pour ReplicatedStorage et le dossier Events.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Remplacez l'instruction d'impression "Joueur touché" dans fireWeapon par une ligne de Luau pour tirer l'événement DamageCharacter avec la variable characterModel comme argument.
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endendelse-- Calculez la position de fin en fonction de la distance maximale du laserhitPosition = tool.Handle.Position + directionVectorend
Le serveur doit infliger des dégâts au joueur qui a été touché lorsque l'événement est tiré.
Insérez un Script dans ServerScriptService et nommez-le ServerLaserManager.

Déclarez une variable nommée LASER_DAMAGE et définissez-la à 10, ou une valeur de votre choix.
Créez une fonction nommée damageCharacter avec deux paramètres appelés playerFired et characterToDamage.
À l'intérieur de la fonction, trouvez l'Humanoid du personnage et soustrayez LASER_DAMAGE de sa santé.
Connectez la fonction damageCharacter à l'événement DamageCharacter dans le dossier Events.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Retirer de la santé du personnagehumanoid.Health -= LASER_DAMAGEendend-- Connecter les événements aux fonctions appropriéeseventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)Testez le blaster avec 2 joueurs en démarrant un serveur local. Lorsque vous tirez sur l'autre joueur, sa santé diminuera de la valeur assignée à LASER_DAMAGE.
Rendre les faisceaux laser des autres joueurs
Actuellement, le faisceau laser est créé par le client tirant l'arme, donc seul ce client pourra voir le faisceau laser.
Si le faisceau laser était créé sur le serveur, alors tout le monde pourrait le voir. Cependant, il y aurait un petit délai entre le client tirant l'arme et le serveur recevant l'information sur le tir. Cela signifierait que le client tirant l'arme verrait un délai entre le moment où il active l'arme et le moment où il voit le faisceau laser ; l'arme semblerait donc laguer en conséquence.
Pour résoudre ce problème, chaque client créera ses propres faisceaux laser. Cela signifie que le client tirant l'arme verra le faisceau laser instantanément. D'autres clients éprouveront un petit délai entre le moment où un autre joueur tire et l'apparition d'un faisceau. C'est le meilleur scénario : il n'y a aucun moyen de communiquer le laser d'un client à d'autres clients plus rapidement.
Client du tireur
Tout d'abord, le client doit informer le serveur qu'il a tiré un laser et fournir la position de fin.
Insérez un RemoteEvent dans le dossier Events de ReplicatedStorage et nommez-le LaserFired.

Localisez la fonction fireWeapon dans le script ToolController. À la fin de la fonction, tirez l'événement LaserFired en utilisant hitPosition comme argument.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
Le serveur
Le serveur doit maintenant recevoir l'événement que le client a tiré et informer tous les clients de la position de départ et de fin du faisceau laser afin qu'ils puissent également le rendre.
Dans le script ServerLaserManager, créez une fonction nommée playerFiredLaser au-dessus de damageCharacter avec deux paramètres appelés playerFired et endPosition.
Connectez la fonction à l'événement LaserFired.
-- Informer tous les clients qu'un laser a été tiré afin qu'ils puissent afficher le laserlocal function playerFiredLaser(playerFired, endPosition)end-- Connecter les événements aux fonctions appropriéeseventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
Le serveur a besoin de la position de départ du laser. Cela pourrait être envoyé par le client, mais il est préférable d'éviter de faire confiance au client autant que possible. La position de la poignée de l'arme du personnage sera la position de départ, donc le serveur peut la trouver à partir de là.
Créez une fonction getPlayerToolHandle au-dessus de la fonction playerFiredLaser avec un paramètre appelé player.
Utilisez le code suivant pour rechercher l'arme du joueur et renvoyer l'objet de la poignée.
local LASER_DAMAGE = 10-- Trouver la poignée de l'outil que le joueur tientlocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Informer tous les clients qu'un laser a été tiré afin qu'ils puissent afficher le laserlocal function playerFiredLaser(playerFired, endPosition)
Le serveur peut maintenant appeler FireAllClients sur l'événement LaserFired pour envoyer les informations nécessaires pour rendre le laser aux clients. Cela inclut le joueur qui a tiré le laser (afin que le client de ce joueur ne rende pas le laser deux fois), la poignée du blaster (qui sert de position de départ pour le laser) et la position de fin du laser.
Dans la fonction playerFiredLaser, appelez la fonction getPlayerToolHandle avec playerFired comme argument et assignez la valeur à une variable nommée toolHandle.
Si toolHandle existe, tirez l'événement LaserFired pour tous les clients en utilisant playerFired, toolHandle et endPosition comme arguments.
-- Informer tous les clients qu'un laser a été tiré afin qu'ils puissent afficher le laserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
Rendre sur les clients
Maintenant que FireAllClients a été appelé, chaque client recevra un événement du serveur pour rendre un faisceau laser. Chaque client peut réutiliser le module LaserRenderer de plus tôt pour rendre le faisceau laser en utilisant la position de la poignée de l'outil et la valeur de position de fin envoyée par le serveur. Le joueur qui a tiré le faisceau laser à l'origine doit ignorer cet événement, sinon il verra 2 lasers.
Créez un LocalScript dans StarterPlayerScripts appelé ClientLaserManager.

À l'intérieur du script, exigez le module LaserRenderer.
Créez une fonction nommée createPlayerLaser avec les paramètres playerWhoShot, toolHandle et endPosition.
Connectez la fonction à l'événement LaserFired dans le dossier Events.
Dans la fonction, utilisez une instruction if pour vérifier si playerWhoShot n'est pas égal à LocalPlayer.
À l'intérieur de l'instruction if, appelez la fonction createLaser du module LaserRenderer en utilisant toolHandle et endPosition comme arguments.
local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))local eventsFolder = ReplicatedStorage.Events-- Afficher le laser d'un autre joueurlocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)Testez le blaster avec 2 joueurs en démarrant un serveur local. Positionnez chaque client de chaque côté de votre moniteur afin que vous puissiez voir les deux fenêtres en même temps. Lorsque vous tirez sur un client, vous devriez voir le laser sur l'autre client.
Effets sonores
L'effet sonore de tir ne se joue actuellement que sur le client qui tire le projectile. Vous devrez déplacer le code pour jouer le son afin que d'autres joueurs puissent également l'entendre.
Dans le script ToolController, naviguez vers la fonction toolActivated et supprimez la ligne qui joue le son Activate.
local function toolActivated()if canShootWeapon() thenfireWeapon()endendEn bas de la fonction createLaser dans LaserRenderer, déclarez une variable nommée shootingSound et utilisez la méthode FindFirstChild() de toolHandle pour vérifier le son Activate.
Utilisez une instruction if pour vérifier si shootingSound existe ; si c'est le cas, appelez sa fonction Play().
laserPart.Parent = workspace-- Ajouter le faisceau laser au service Debris pour être supprimé et nettoyéDebris:AddItem(laserPart, SHOT_DURATION)-- Jouer le son de tir de l'armelocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
Sécuriser les remotes en utilisant la validation
Si le serveur ne vérifie pas les données des requêtes entrantes, un hacker peut abuser des fonctions et événements distants et les utiliser pour envoyer de fausses valeurs au serveur. Il est important d'utiliser une validation côté serveur pour éviter cela.
Dans sa forme actuelle, l'événement distant DamageCharacter est très vulnérable aux attaques. Les hackers pourraient utiliser cet événement pour endommager n'importe quel joueur qu'ils souhaitent sans les tirer.
La validation est le processus de vérification que les valeurs envoyées au serveur sont réalistes. Dans ce cas, le serveur devra :
- Vérifier si la distance entre le joueur et la position touchée par le laser est dans une certaine limite.
- Raycaster entre l'arme qui a tiré le laser et la position touchée pour s'assurer que le tir était possible et n'a pas traversé de murs.
Client
Le client doit envoyer au serveur la position touchée par le raycast afin qu'il puisse vérifier que la distance est réaliste.
Dans ToolController, naviguez jusqu'à la ligne où l'événement DamageCharacter est tiré dans la fonction fireWeapon.
Ajoutez hitPosition comme argument.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
Serveur
Le client envoie maintenant un paramètre supplémentaire via l'événement DamageCharacter, donc le ServerLaserManager doit être ajusté pour l'accepter.
Dans le script ServerLaserManager, ajoutez un paramètre hitPosition à la fonction damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Retirer de la santé du personnagehumanoid.Health -= LASER_DAMAGEendendEn dessous de la fonction getPlayerToolHandle, créez une fonction nommée isHitValid avec trois paramètres : playerFired, characterToDamage et hitPosition.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
La première vérification sera la distance entre la position touchée et le personnage touché.
Déclarez une variable nommée MAX_HIT_PROXIMITY en haut du script et assignez-lui une valeur de 10. Ce sera la distance maximale autorisée entre le coup et le personnage. Une tolérance est nécessaire car le personnage peut avoir légèrement bougé depuis que le client a tiré l'événement.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10Dans la fonction isHitValid, calculez la distance entre le personnage et la position touchée. Si la distance est supérieure à MAX_HIT_PROXIMITY, renvoyez faux.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Valider la distance entre le personnage touché et la position touchéelocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
La deuxième vérification impliquera un raycast entre l'arme tirée et la position touchée. Si le raycast renvoie un objet qui n'est pas le personnage, vous pouvez supposer que le tir n'était pas valide car quelque chose bloquait le tir.
Copiez le code ci-dessous pour effectuer cette vérification. Renvoie vrai à la fin de la fonction : si elle atteint la fin, toutes les vérifications ont réussi.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Valider la distance entre le personnage touché et la position touchéelocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Vérifier si le tir passe à travers des murslocal toolHandle = getPlayerToolHandle(playerFired)if toolHandle thenlocal rayLength = (hitPosition - toolHandle.Position).Magnitudelocal rayDirection = (hitPosition - toolHandle.Position).Unitlocal raycastParams = RaycastParams.new()raycastParams.FilterDescendantsInstances = {playerFired.Character}local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)-- Si une instance a été touchée qui n'était pas le personnage, alors ignorez le tirif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendDéclarez une variable dans la fonction damageCharacter appelée validShot. Assignez-lui le résultat d'un appel à la fonction isHitValid avec trois arguments : playerFired, characterToDamage et hitPosition.
Dans l'instruction if ci-dessous, ajoutez un opérateur et pour vérifier si validShot est vrai.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")local validShot = isHitValid(playerFired, characterToDamage, hitPosition)if humanoid and validShot then-- Retirer de la santé du personnagehumanoid.Health -= LASER_DAMAGEendend
Maintenant, l'événement distant damageCharacter est plus sécurisé et empêchera la plupart des joueurs de l'abuser. Notez que certains joueurs malveillants trouveront souvent des moyens de contourner la validation ; garder les événements distants sécurisés est un effort continu.
Votre blaster laser est maintenant complet, avec un système de détection des impacts de base utilisant le raycasting. Essayez le tutoriel Détection de l'entrée utilisateur pour découvrir comment vous pouvez ajouter une action de rechargement à votre blaster laser, ou créez une carte amusante et essayez votre blaster laser avec d'autres joueurs !
Code final
ToolController
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
local tool = script.Parent
local eventsFolder = ReplicatedStorage.Events
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local FIRE_RATE = 0.3
local timeOfPreviousShot = 0
-- Vérifiez si suffisamment de temps s'est écoulé depuis le dernier tir
local function canShootWeapon()
local currentTime = tick()
if currentTime - timeOfPreviousShot < FIRE_RATE then
return false
end
return true
end
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Créer un rayon à partir de la position 2D de la souris
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Le vecteur de direction unitaire du rayon multiplié par une distance maximale
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast à partir de l'origine du rayon vers sa direction
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Retourner le point 3D d'intersection
return raycastResult.Position
else
-- Aucun objet n'a été touché, donc calculez la position à la fin du rayon
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calculer un vecteur de direction normalisé et multiplier par la distance du laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- La direction pour tirer l'arme, multipliée par une distance maximale
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorer le personnage du joueur pour éviter qu'il ne se blesse lui-même
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Vérifiez si des objets ont été touchés entre la position de départ et d'arrivée
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- L'instance touchée sera un enfant d'un modèle de personnage
-- Si un humanoïde est trouvé dans le modèle, alors c'est probablement le personnage d'un joueur
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
if characterModel then
local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
if humanoid then
eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
end
end
else
-- Calculez la position de fin en fonction de la distance maximale du laser
hitPosition = tool.Handle.Position + directionVector
end
timeOfPreviousShot = tick()
eventsFolder.LaserFired:FireServer(hitPosition)
LaserRenderer.createLaser(tool.Handle, hitPosition)
end
local function toolEquipped()
tool.Handle.Equip:Play()
end
local function toolActivated()
if canShootWeapon() then
fireWeapon()
end
end
tool.Equipped:Connect(toolEquipped)
tool.Activated:Connect(toolActivated)LaserRenderer
local LaserRenderer = {}
local Debris = game:GetService("Debris")
local SHOT_DURATION = 0.15 -- Temps pendant lequel le laser est visible
-- Créer un faisceau laser d'une position de départ vers une position de fin
function LaserRenderer.createLaser(toolHandle, endPosition)
local startPosition = toolHandle.Position
local laserDistance = (startPosition - endPosition).Magnitude
local laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)
local laserPart = Instance.new("Part")
laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)
laserPart.CFrame = laserCFrame
laserPart.Anchored = true
laserPart.CanCollide = false
laserPart.Color = Color3.fromRGB(255, 0, 0)
laserPart.Material = Enum.Material.Neon
laserPart.Parent = workspace
-- Ajouter le faisceau laser au service Debris pour être supprimé et nettoyé
Debris:AddItem(laserPart, SHOT_DURATION)
-- Jouer le son de tir de l'arme
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRendererServerLaserManager
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Trouver la poignée de l'outil que le joueur est en train de tenir
local function getPlayerToolHandle(player)
local weapon = player.Character:FindFirstChildOfClass("Tool")
if weapon then
return weapon:FindFirstChild("Handle")
end
end
local function isHitValid(playerFired, characterToDamage, hitPosition)
-- Valider la distance entre le personnage touché et la position touchée
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Vérifier si le tir passe à travers des murs
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
local rayLength = (hitPosition - toolHandle.Position).Magnitude
local rayDirection = (hitPosition - toolHandle.Position).Unit
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {playerFired.Character}
local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)
-- Si une instance a été touchée qui n'était pas le personnage, alors ignorez le tir
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Informer tous les clients qu'un laser a été tiré afin qu'ils puissent afficher le laser
local function playerFiredLaser(playerFired, endPosition)
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
end
end
function damageCharacter(playerFired, characterToDamage, hitPosition)
local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
if humanoid and validShot then
-- Retirer de la santé du personnage
humanoid.Health -= LASER_DAMAGE
end
end
-- Connecter les événements aux fonctions appropriées
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)ClientLaserManager
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts:WaitForChild("LaserRenderer"))
local eventsFolder = ReplicatedStorage.Events
-- Afficher le laser d'un autre joueur
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)