En este tutorial, aprenderás cómo lanzar un láser desde el bláster en Crear herramientas para jugadores y detectar si golpea a un jugador o no.
Raycasting para encontrar colisiones
Raycasting crea un rayo invisible desde una posición de inicio hacia una dirección dada con una longitud definida. Si el rayo colisiona con objetos o terreno en su camino, devolverá información sobre la colisión, como la posición y el objeto con el que colisionó.

Encontrar la ubicación del mouse
Antes de que se pueda disparar un láser, primero debes saber hacia dónde está apuntando el jugador. Esto se puede encontrar haciendo un raycasting desde la ubicación 2D del mouse del jugador en la pantalla directamente hacia adelante desde la cámara en el mundo 3D. El rayo colisionará con lo que el jugador esté apuntando con el mouse.
Abre el script ToolController dentro de la herramienta Blaster de Crear herramientas para jugadores. Si aún no has completado ese tutorial, puedes descargar el modelo Blaster e insertarlo en StarterPack.

En la parte superior del script, declara una constante llamada MAX_MOUSE_DISTANCE con un valor de 1000.
Crea una función llamada 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-- Conectar eventos a funciones apropiadastool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)Usa la función GetMouseLocation de UserInputService para obtener la ubicación 2D del mouse del jugador en la pantalla. Asigna esto a una variable llamada mouseLocation.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
Ahora que se conoce la ubicación 2D del mouse, sus propiedades X y Y se pueden usar como parámetros para la función Camera:ViewportPointToRay(), que crea un Ray desde la pantalla hacia el mundo 3D.
Usa las propiedades X y Y de mouseLocation como argumentos para la función ViewportPointToRay(). Asigna esto a una variable llamada screenToWorldRay.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Crear un rayo desde la ubicación 2D del mouselocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)end
Es hora de usar la función Raycast para verificar si el rayo golpea un objeto. Esto requiere una posición de inicio y un vector de dirección: en este ejemplo, usarás las propiedades origin y direction de screenToWorldRay.
La longitud del vector de dirección determina cuán lejos viajará el rayo. El rayo necesita ser tan largo como el MAX_MOUSE_DISTANCE, por lo que tendrás que multiplicar el vector de dirección por MAX_MOUSE_DISTANCE.
Declara una variable llamada directionVector y asígnale el valor de screenToWorldRay.Direction multiplicado por MAX_MOUSE_DISTANCE.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Crear un rayo desde la ubicación 2D del mouselocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- El vector de dirección unitario del rayo multiplicado por una distancia máximalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCELlama a la función Raycast de workspace, pasando la propiedad Origin de screenToWorldRay como el primer argumento y directionVector como el segundo. Asigna esto a una variable llamada raycastResult.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Crear un rayo desde la ubicación 2D del mouselocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- El vector de dirección unitario del rayo multiplicado por una distancia máximalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Raycast desde el origen del rayo hacia su direcciónlocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
Información de colisión
Si la operación de raycast encuentra un objeto golpeado por el rayo, devolverá un RaycastResult, que contiene información sobre la colisión entre el rayo y el objeto.
| Propiedad RaycastResult | Descripción |
|---|---|
| Instance | El BasePart o celda de Terrain con la que intersectó el rayo. |
| Position | Donde ocurrió la intersección; generalmente un punto directamente en la superficie de una parte o terreno. |
| Material | El material en el punto de colisión. |
| Normal | El vector normal de la cara intersectada. Esto se puede usar para determinar hacia dónde está apuntando la cara. |
La propiedad Position será la posición del objeto sobre el que el mouse está flotando. Si el mouse no está flotando sobre ningún objeto dentro de una distancia de MAX_MOUSE_DISTANCE, raycastResult será nil.
Crea una declaración if para verificar si raycastResult existe.
Si raycastResult tiene un valor, devuelve su propiedad Position.
Si raycastResult es nil, entonces encuentra el final del raycast. Calcula la posición 3D del mouse sumando screenToWorldRay.Origin y directionVector.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Crear un rayo desde la ubicación 2D del mouse
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- El vector de dirección unitario del rayo multiplicado por una distancia máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast desde el origen del rayo hacia su dirección
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Devolver el punto 3D de intersección
return raycastResult.Position
else
-- No se golpeó ningún objeto, así que calcula la posición al final del rayo
return screenToWorldRay.Origin + directionVector
end
endDisparar hacia el objetivo
Ahora que se conoce la posición 3D del mouse, se puede usar como una posición objetivo para disparar un láser. Se puede lanzar un segundo rayo entre la posición del arma del jugador y la posición objetivo usando la función Raycast.
Declara una constante llamada MAX_LASER_DISTANCE en la parte superior del script y asígnale 500, o tu rango elegido para el bláster láser.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Crea una función llamada fireWeapon debajo de la función getWorldMousePosition.
Llama a getWorldMousePosition y asigna el resultado a una variable llamada mousePosition. Esta será la posición objetivo para el raycast.
-- No se golpeó ningún objeto, así que calcula la posición al final del rayoreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
Esta vez, el vector de dirección para la función de raycast representará la dirección desde la posición de la herramienta del jugador hasta la ubicación objetivo.
Declara una variable llamada targetDirection y calcula el vector de dirección restando la posición de la herramienta de mouseLocation.
Normaliza el vector usando su propiedad Unit. Esto le da una magnitud de 1, lo que facilita multiplicarlo por una longitud más adelante.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- Calcular un vector de dirección normalizado y multiplicar por la distancia del láserlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendDeclara una variable llamada directionVector y asígnale el targetDirection multiplicado por el MAX_LASER_DISTANCE.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- La dirección para disparar el arma, multiplicada por una distancia máximalocal directionVector = targetDirection * MAX_LASER_DISTANCEend
Un objeto RaycastParams se puede usar para almacenar parámetros adicionales para la función de raycast. Se utilizará en tu bláster láser para asegurarse de que el raycast no colisione accidentalmente con el jugador que dispara el arma. Cualquier parte incluida en la propiedad FilterDescendantsInstances de un objeto RaycastParams será ignorada en el raycast.
Continúa la función fireWeapon y declara una variable llamada weaponRaycastParams. Asigna un nuevo objeto RaycastParams a ella.
Crea una tabla que contenga el personaje local del jugador y asígnala a la propiedad weaponRaycastParams.FilterDescendantsInstances.
Realiza un raycast desde la posición del mango de la herramienta del jugador, en una dirección hacia el directionVector. Recuerda agregar weaponRaycastParams como argumento esta vez. Asigna esto a una variable llamada 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()
-- Calcular un vector de dirección normalizado y multiplicar por la distancia del láser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- La dirección para disparar el arma multiplicada por una distancia máxima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorar el carácter del jugador para evitar que se dañen a sí mismos
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
endFinalmente, necesitarás verificar que la operación de raycast devolvió un valor. Si se devuelve un valor, un objeto fue golpeado por el rayo y se puede crear un láser entre el arma y la ubicación de impacto. Si no se devolvió nada, se necesita calcular la posición final para crear el láser.
Declara una variable vacía llamada hitPosition.
Usa una declaración if para verificar si weaponRaycastResult tiene un valor. Si se golpeó un objeto, asigna weaponRaycastResult.Position a hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Verificar si se golpearon objetos entre la posición de inicio y la de finlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendSi weaponRaycastResult no tiene valor, calcula la posición final del raycast sumando la posición del mango de la herramienta con el directionVector. Asigna esto a hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Verificar si se golpearon objetos entre la posición de inicio y la de finlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Calcular la posición final basada en la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorendendNavega a la función toolActivated y llama a la función fireWeapon para que el láser dispare cada vez que se active la herramienta.
local function toolActivated()tool.Handle.Activate:Play()fireWeapon()end
Verificar el objeto golpeado
Para averiguar si el objeto golpeado por el láser es parte del personaje de un jugador o simplemente un trozo de paisaje, necesitarás buscar un Humanoid, ya que cada personaje tiene uno.
Primero, necesitarás encontrar el modelo del personaje. Si se golpeó una parte del personaje, no puedes asumir que el padre del objeto golpeado sería el personaje. El láser podría haber golpeado una parte del cuerpo, un accesorio o una herramienta, todos los cuales se encuentran en diferentes partes de la jerarquía del personaje.

Puedes usar FindFirstAncestorOfClass para encontrar un ancestro del modelo del personaje del objeto golpeado por el láser, si existe. Si encuentras un modelo y contiene un humanoide, en la mayoría de los casos puedes asumir que es un personaje.
Agrega el código resaltado a continuación a la declaración if de weaponRaycastResult para verificar si se golpeó un personaje.
-- Verificar si se golpearon objetos entre la posición de inicio y la de finlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- La instancia golpeada será un hijo de un modelo de personaje-- Si se encuentra un humanoide en el modelo, entonces es probable que sea el personaje de un jugadorlocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Jugador golpeado")endendelse-- Calcular la posición final basada en la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorend
Ahora el bláster láser debería imprimir Jugador golpeado en la ventana de Salida cada vez que la operación de raycast golpea a otro jugador.
Probar con múltiples jugadores
Se necesitan dos jugadores para probar si el raycast del arma está encontrando a otros jugadores, por lo que necesitas iniciar un servidor local. Consulta simulación de múltiples clientes para obtener instrucciones.
En un cliente, prueba disparar al otro jugador con el arma haciendo clic en ellos. "Jugador golpeado" debería mostrarse en la salida cada vez que se dispara a un jugador.
Encontrar la posición del láser
El bláster debería disparar un rayo de luz roja hacia su objetivo. La función para esto estará dentro de un ModuleScript para que pueda reutilizarse en otros scripts más adelante. Primero, el script necesitará encontrar la posición donde se debe renderizar el rayo láser.
Crea un ModuleScript llamado LaserRenderer, parentado a StarterPlayerScripts bajo StarterPlayer.

Abre el script y renombra la tabla del módulo con el nombre del script LaserRenderer.
Declara una variable llamada SHOT_DURATION con un valor de 0.15. Este será el tiempo (en segundos) que el láser será visible.
Crea una función de LaserRenderer llamada createLaser con dos parámetros llamados toolHandle y endPosition.
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- Tiempo que el láser es visible-- Crear un rayo láser desde una posición de inicio hacia una posición finalfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererDeclara una variable llamada startPosition y establece la propiedad Position de toolHandle como su valor. Esta será la posición del bláster láser del jugador.
Declara una variable llamada laserDistance y resta endPosition de startPosition para encontrar la diferencia entre los dos vectores. Usa la propiedad Magnitude de esto para obtener la longitud del rayo láser.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendDeclara una variable laserCFrame para almacenar la posición y orientación del rayo láser. La posición necesita ser el punto medio del inicio y el final del rayo. Usa CFrame.lookAt para crear un nuevo CFrame ubicado en startPosition y mirando hacia endPosition. Multiplica esto por un nuevo CFrame con un valor de eje Z de la mitad de -laserDistance para obtener el punto medio.
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
Crear la parte del láser
Ahora que sabes dónde crear un rayo láser, necesitas agregar el rayo mismo. Esto se puede hacer fácilmente con una parte Neon.
Declara una variable laserPart y asígnale una nueva instancia de Part.
Establece las siguientes propiedades de laserPart:
- Size: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- Anchored: true
- CanCollide: false
- Color: Color3.fromRGB(225, 0, 0) (un color rojo fuerte)
- Material: Enum.Material.Neon
Parent laserPart a Workspace.
Agrega la parte al servicio Debris para que se elimine después de la cantidad de segundos en 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-- Agregar el rayo láser al servicio Debris para ser eliminado y limpiadoDebris:AddItem(laserPart, SHOT_DURATION)end
Ahora la función para renderizar el rayo láser está completa, se puede llamar desde el ToolController.
En la parte superior del script ToolController, declara una variable llamada LaserRenderer y requiere el módulo LaserRenderer ubicado en PlayerScripts.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.ParentEn la parte inferior de la función fireWeapon, llama a la función createLaser de LaserRenderer usando el mango de la herramienta y hitPosition como argumentos.
-- Calcular la posición final basada en la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endPrueba el arma haciendo clic en el botón Play. Un rayo láser debería ser visible entre el arma y el mouse cuando se activa la herramienta.
Controlar la tasa de disparo del arma
Las armas necesitan un retraso entre cada disparo para evitar que los jugadores inflijan demasiado daño en un corto período de tiempo. Esto se puede controlar verificando si ha pasado suficiente tiempo desde que un jugador disparó por última vez.
Declara una variable en la parte superior del ToolController llamada FIRE_RATE. Este será el tiempo mínimo entre cada disparo. Dale un valor de tu elección; este ejemplo usa 0.3 segundos.
Declara otra variable debajo llamada timeOfPreviousShot con un valor de 0. Esto almacena la última vez que el jugador disparó y se actualizará con cada disparo.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Crea una función llamada canShootWeapon sin parámetros. Esta función verificará cuánto tiempo ha pasado desde el disparo anterior y devolverá verdadero o falso.
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- Verificar si ha pasado suficiente tiempo desde que se disparó el disparo anteriorlocal function canShootWeapon()endlocal function getWorldMousePosition()Dentro de la función, declara una variable llamada currentTime; asígnale el resultado de llamar a la función tick(). Esto devuelve cuánto tiempo ha transcurrido, en segundos, desde el 1 de enero de 1970 (una fecha arbitraria ampliamente utilizada para calcular el tiempo).
Resta timeOfPreviousShot de currentTime y devuelve falso si el resultado es menor que FIRE_RATE; de lo contrario, devuelve verdadero.
-- Verificar si ha pasado suficiente tiempo desde que se disparó el disparo anteriorlocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendAl final de la función fireWeapon, actualiza timeOfPreviousShot cada vez que se dispare el arma usando tick.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endDentro de la función toolActivated, crea una declaración if y llama a canShootWeapon para verificar si el arma se puede disparar.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
Cuando pruebes el bláster, deberías encontrar que no importa cuán rápido hagas clic, siempre habrá un corto retraso de 0.3 segundos entre cada disparo.
Dañar al jugador
Los clientes no pueden dañar a otros clientes directamente; el servidor debe ser responsable de emitir daño cuando un jugador es golpeado.
Los clientes pueden usar un RemoteEvent para decirle al servidor que un personaje ha sido golpeado. Estos deben almacenarse en ReplicatedStorage, donde son visibles tanto para el cliente como para el servidor.
Crea una Carpeta en ReplicatedStorage llamada Events.

Inserta un RemoteEvent en la carpeta Events y nómbralo DamageCharacter.

En ToolController, crea variables al inicio del script para ReplicatedStorage y la carpeta 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 = 500Reemplaza la declaración de impresión "Jugador golpeado" en fireWeapon con una línea de Luau para disparar el evento remoto DamageCharacter con la variable characterModel como argumento.
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endendelse-- Calcular la posición final basada en la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorend
El servidor necesita causar daño al jugador que ha sido golpeado cuando se dispara el evento.
Inserta un Script en ServerScriptService y nómbralo ServerLaserManager.

Declara una variable llamada LASER_DAMAGE y configúralo en 10, o un valor de tu elección.
Crea una función llamada damageCharacter con dos parámetros llamados playerFired y characterToDamage.
Dentro de la función, encuentra el Humanoid del personaje y resta LASER_DAMAGE de su salud.
Conecta la función damageCharacter al evento remoto DamageCharacter en la carpeta Events.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Quitar salud del personajehumanoid.Health -= LASER_DAMAGEendend-- Conectar eventos a funciones apropiadaseventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)Prueba el bláster con 2 jugadores iniciando un servidor local. Cuando dispares al otro jugador, su salud disminuirá en la cantidad asignada a LASER_DAMAGE.
Renderizar los rayos láser de otros jugadores
Actualmente, el rayo láser es creado por el cliente que dispara el arma, por lo que solo ellos podrán ver el rayo láser.
Si el rayo láser se creara en el servidor, entonces todos podrían verlo. Sin embargo, habría un pequeño retraso entre el cliente disparando el arma y el servidor recibiendo la información sobre el disparo. Esto significaría que el cliente que dispara el arma vería un retraso entre cuando activa el arma y cuando ve el rayo láser; el arma se sentiría lenta como resultado.
Para resolver este problema, cada cliente creará sus propios rayos láser. Esto significa que el cliente que dispara el arma verá el rayo láser instantáneamente. Otros clientes experimentarán un pequeño retraso entre cuando otro jugador dispara y aparece un rayo. Este es el mejor escenario: no hay forma de comunicar el láser de un cliente a otros clientes más rápido.
Cliente tirador
Primero, el cliente necesita decirle al servidor que ha disparado un láser y proporcionar la posición final.
Inserta un RemoteEvent en la carpeta Events en ReplicatedStorage y nómbralo LaserFired.

Localiza la función fireWeapon en el script ToolController. Al final de la función, dispara el evento remoto LaserFired usando hitPosition como argumento.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
El servidor
El servidor ahora debe recibir el evento de que el cliente ha disparado y decirle a todos los clientes la posición de inicio y final del rayo láser para que también puedan renderizarlo.
En el script ServerLaserManager, crea una función llamada playerFiredLaser encima de damageCharacter con dos parámetros llamados playerFired y endPosition.
Conecta la función al evento remoto LaserFired.
-- Notificar a todos los clientes que se ha disparado un láser para que puedan mostrar el láserlocal function playerFiredLaser(playerFired, endPosition)end-- Conectar eventos a funciones apropiadaseventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
El servidor necesita la posición de inicio del láser. Esto podría ser enviado desde el cliente, pero es mejor evitar confiar en el cliente siempre que sea posible. La posición del mango del arma del personaje será la posición de inicio, por lo que el servidor puede encontrarla desde allí.
Crea una función getPlayerToolHandle encima de la función playerFiredLaser con un parámetro llamado player.
Usa el siguiente código para buscar el arma del jugador en su personaje y devolver el objeto del mango.
local LASER_DAMAGE = 10-- Encontrar el mango de la herramienta que el jugador está sosteniendolocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Notificar a todos los clientes que se ha disparado un láser para que puedan mostrar el láserlocal function playerFiredLaser(playerFired, endPosition)
El servidor ahora puede llamar a FireAllClients en el evento remoto LaserFired para enviar la información necesaria para renderizar el láser a los clientes. Esto incluye el jugador que disparó el láser (para que el cliente de ese jugador no renderice el láser dos veces), el mango del bláster (que actúa como una posición de inicio para el láser) y la posición final del láser.
En la función playerFiredLaser, llama a la función getPlayerToolHandle con playerFired como argumento y asigna el valor a una variable llamada toolHandle.
Si toolHandle existe, dispara el evento LaserFired para todos los clientes usando playerFired, toolHandle y endPosition como argumentos.
-- Notificar a todos los clientes que se ha disparado un láser para que puedan mostrar el láserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
Renderizar en los clientes
Ahora que se ha llamado a FireAllClients, cada cliente recibirá un evento del servidor para renderizar un rayo láser. Cada cliente puede reutilizar el módulo LaserRenderer de antes para renderizar el rayo láser usando la posición del mango de la herramienta y el valor de la posición final enviado por el servidor. El jugador que disparó el rayo láser en primer lugar debe ignorar este evento, de lo contrario, verá 2 láseres.
Crea un LocalScript en StarterPlayerScripts llamado ClientLaserManager.

Dentro del script, requiere el módulo LaserRenderer.
Crea una función llamada createPlayerLaser con los parámetros playerWhoShot, toolHandle y endPosition.
Conecta la función al evento remoto LaserFired en la carpeta Events.
En la función, usa una declaración if para verificar si playerWhoShot no es igual a LocalPlayer.
Dentro de la declaración if, llama a la función createLaser del módulo LaserRenderer usando toolHandle y endPosition como argumentos.
local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))local eventsFolder = ReplicatedStorage.Events-- Mostrar el láser de otro jugadorlocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)Prueba el bláster con 2 jugadores iniciando un servidor local. Coloca cada cliente en diferentes lados de tu monitor para que puedas ver ambas ventanas a la vez. Cuando dispares en un cliente, deberías ver el rayo láser en el otro cliente.
Efectos de sonido
El efecto de sonido de disparo actualmente solo se reproduce en el cliente que está disparando el proyectil. Necesitarás mover el código para reproducir el sonido para que otros jugadores también lo escuchen.
En el script ToolController, navega a la función toolActivated y elimina la línea que reproduce el sonido de Activar.
local function toolActivated()if canShootWeapon() thenfireWeapon()endendEn la parte inferior de la función createLaser en LaserRenderer, declara una variable llamada shootingSound y usa el método FindFirstChild() de toolHandle para verificar el sonido de Activar.
Usa una declaración if para verificar si shootingSound existe; si lo hace, llama a su función Play().
laserPart.Parent = workspace-- Agregar el rayo láser al servicio Debris para ser eliminado y limpiadoDebris:AddItem(laserPart, SHOT_DURATION)-- Reproducir el sonido de disparo del armalocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
Asegurar remotos usando validación
Si el servidor no está verificando los datos de las solicitudes entrantes, un hacker puede abusar de las funciones y eventos remotos y usarlos para enviar valores falsos al servidor. Es importante usar validación del lado del servidor para prevenir esto.
En su forma actual, el evento remoto DamageCharacter es muy vulnerable a ataques. Los hackers podrían usar este evento para dañar a cualquier jugador que deseen sin dispararles.
La validación es el proceso de verificar que los valores enviados al servidor sean realistas. En este caso, el servidor necesitará:
- Verificar si la distancia entre el jugador y la posición golpeada por el láser está dentro de un cierto límite.
- Hacer un raycast entre el arma que disparó el láser y la posición golpeada para asegurarse de que el disparo fue posible y no atravesó ninguna pared.
Cliente
El cliente necesita enviar al servidor la posición golpeada por el raycast para que pueda verificar que la distancia es realista.
En ToolController, navega a la línea donde se dispara el evento remoto DamageCharacter en la función fireWeapon.
Agrega hitPosition como argumento.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
Servidor
El cliente ahora está enviando un parámetro adicional a través del evento remoto DamageCharacter, por lo que el ServerLaserManager necesita ajustarse para aceptarlo.
En el script ServerLaserManager, agrega un parámetro hitPosition a la función damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Quitar salud del personajehumanoid.Health -= LASER_DAMAGEendendDebajo de la función getPlayerToolHandle, crea una función llamada isHitValid con tres parámetros: playerFired, characterToDamage y hitPosition.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
La primera verificación será la distancia entre la posición golpeada y el personaje golpeado.
Declara una variable llamada MAX_HIT_PROXIMITY en la parte superior del script y asígnale un valor de 10. Esta será la distancia máxima permitida entre el golpe y el personaje. Se necesita una tolerancia porque el personaje puede haberse movido ligeramente desde que el cliente disparó el evento.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10En la función isHitValid, calcula la distancia entre el personaje y la posición golpeada. Si la distancia es mayor que MAX_HIT_PROXIMITY, entonces devuelve falso.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Validar la distancia entre el personaje golpeado y la posición golpeadalocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
La segunda verificación involucrará un raycast entre el arma disparada y la posición golpeada. Si el raycast devuelve un objeto que no es el personaje, puedes asumir que el disparo no fue válido ya que algo estaba bloqueando el disparo.
Copia el código a continuación para realizar esta verificación. Devuelve true al final de la función: si llega al final, todas las verificaciones han pasado.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Validar la distancia entre el personaje golpeado y la posición golpeadalocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Verificar si se dispara a través de paredeslocal 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 se golpeó una instancia que no era el personaje, entonces ignora el disparoif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendDeclara una variable en la función damageCharacter llamada validShot. Asigna a ella el resultado de una llamada a la función isHitValid con tres argumentos: playerFired, characterToDamage y hitPosition.
En la declaración if a continuación, agrega un operador and para verificar si validShot es true.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")local validShot = isHitValid(playerFired, characterToDamage, hitPosition)if humanoid and validShot then-- Quitar salud del personajehumanoid.Health -= LASER_DAMAGEendend
Ahora el evento remoto damageCharacter es más seguro y evitará que la mayoría de los jugadores lo abusen. Ten en cuenta que algunos jugadores malintencionados a menudo encontrarán formas de eludir la validación; mantener seguros los eventos remotos es un esfuerzo continuo.
Tu bláster láser ahora está completo, con un sistema básico de detección de impactos utilizando raycasting. Prueba el tutorial Detectando entrada de usuario para averiguar cómo puedes agregar una acción de recarga a tu bláster láser, o crea un mapa divertido y prueba tu bláster láser con otros jugadores.
Código 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
-- Verificar si ha pasado suficiente tiempo desde que se disparó el disparo anterior
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()
-- Crear un rayo desde la ubicación 2D del mouse
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- El vector de dirección unitario del rayo multiplicado por una distancia máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast desde el origen del rayo hacia su dirección
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Devolver el punto 3D de intersección
return raycastResult.Position
else
-- No se golpeó ningún objeto, así que calcula la posición al final del rayo
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calcular un vector de dirección normalizado y multiplicar por la distancia del láser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- La dirección para disparar el arma, multiplicada por una distancia máxima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorar el carácter del jugador para evitar que se dañen a sí mismos
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Verificar si se golpearon objetos entre la posición de inicio y la de fin
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- La instancia golpeada será un hijo de un modelo de personaje
-- Si se encuentra un humanoide en el modelo, entonces es probable que sea el personaje de un jugador
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
-- Calcular la posición final basada en la distancia máxima del láser
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 -- Tiempo que el láser es visible
-- Crear un rayo láser desde una posición de inicio hacia una posición final
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
-- Agregar el rayo láser al servicio Debris para ser eliminado y limpiado
Debris:AddItem(laserPart, SHOT_DURATION)
-- Reproducir el sonido de disparo del arma
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
-- Encontrar el mango de la herramienta que el jugador está sosteniendo
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)
-- Validar la distancia entre el personaje golpeado y la posición golpeada
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Verificar si se dispara a través de paredes
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 se golpeó una instancia que no era el personaje, entonces ignora el disparo
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Notificar a todos los clientes que se ha disparado un láser para que puedan mostrar el láser
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
-- Quitar salud del personaje
humanoid.Health -= LASER_DAMAGE
end
end
-- Conectar eventos a funciones apropiadas
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
-- Mostrar el láser de otro jugador
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)