En este tutorial, aprenderás a lanzar un láser desde el bláster en Crear herramientas de jugador y detectar si golpea a un jugador.
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 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 de ToolController dentro de la herramienta Blaster desde Crear herramientas de jugador. Si aún no has completado ese tutorial, puedes descargar el modelo de 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 pueden usarse 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 a 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 qué tan lejos viajará el rayo. El rayo necesita ser tan largo como el MAX_MOUSE_DISTANCE, por lo que tendrás que multiplicar el vector 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 de mouseLocationlocal 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 del 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 de mouseLocationlocal 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 | La BasePart o celda Terrain que el rayo intersectó. |
| Position | Donde ocurrió la intersección; usualmente 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 puede usarse para determinar hacia dónde está apuntando la cara. |
La propiedad Position será la posición del objeto sobre el que el mouse está apuntando. Si el mouse no está apuntando 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 juntos.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Crear un rayo desde la ubicación 2D de mouseLocation
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
-- Ningún objeto fue golpeado, así que calcula la posición al final del rayo
return screenToWorldRay.Origin + directionVector
end
end
Dispara 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 utilizando 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.
-- Ningún objeto fue golpeado, 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 al restar 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 tarde.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- Calcular un vector de dirección normalizado y multiplicar por la distancia 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 puede usarse para almacenar parámetros adicionales para la función de raycast. Se usará 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 al personaje local del jugador y asígnala a la propiedad weaponRaycastParams.FilterDescendantsInstances.
Raycast desde la posición del asa de la herramienta del jugador, en una dirección hacia el directionVector. Recuerda añadir 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 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 personaje del jugador para prevenir 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)
end
Finalmente, 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ó a 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 finlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendSi weaponRaycastResult no tiene un valor, calcula la posición final del raycast al sumar la posición del asa 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 finlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Calcular la posición final basándose 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 solo una pieza del paisaje, necesitarás buscar un Humanoid, ya que cada personaje tiene uno.
Primero, necesitarás encontrar el modelo de personaje. Si se golpeó a 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 de modelo de 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 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 probablemente se trata de un personaje de jugadorlocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Jugador golpeado")endendelse-- Calcular la posición final basándose 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 Output cada vez que la operación de raycast golpea a otro jugador.
Prueba con varios jugadores
Se necesitan dos jugadores para probar si el raycast del arma está encontrando a otros jugadores, así que necesitas iniciar un servidor local. Consulta simulación multi-cliente para ver 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 un jugador es disparado.
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 debería renderizarse el rayo láser.
Crea un ModuleScript llamado LaserRenderer, parentado a StarterPlayerScripts bajo StarterPlayer.

Abre el script y cambia el nombre de la tabla del módulo al nombre del script LaserRenderer.
Declara una variable llamada SHOT_DURATION con un valor de 0.15. Esta será la cantidad de tiempo (en segundos) que el láser es 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 será visible-- Crear un rayo láser desde una posición de inicio hacia una posición de finfunction 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 que esté mirando hacia endPosition. Multiplica esto por un nuevo CFrame con un valor del 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 pieza del láser
Ahora que sabes dónde crear un rayo láser, necesitas añadir el rayo en sí. 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.
Añade la parte al servicio de Debris para que sea eliminada 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-- Añadir el rayo láser al servicio de Debris para que sea eliminado y limpiadoDebris:AddItem(laserPart, SHOT_DURATION)end
Ahora la función para renderizar el rayo láser está completa, puede ser llamada por 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 asa de la herramienta y hitPosition como argumentos.
-- Calcular la posición final basándose 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 de reproducir. 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 el último disparo de un jugador.
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 utiliza 0.3 segundos.
Declara otra variable debajo llamada timeOfPreviousShot con un valor de 0. Esto almacena el último tiempo en 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 el último disparolocal 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 pasado, en segundos, desde el 1 de enero de 1970 (una fecha arbitraria ampliamente utilizada para calcular tiempo).
Resta timeOfPreviousShot de currentTime y devuelve false si el resultado es menor que FIRE_RATE; de lo contrario, devuelve true.
-- Verificar si ha pasado suficiente tiempo desde el último disparolocal 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 puede ser disparada.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
Cuando pruebes el bláster, deberías encontrar que no importa qué tan rápido hagas clic, siempre habrá un breve retraso de 0.3 segundos entre cada disparo.
Daño al jugador
Los clientes no pueden dañar a otros clientes directamente; el servidor necesita ser responsable de emitir daño cuando un jugador es golpeado.
Los clientes pueden usar un RemoteEvent para informar 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 de 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)endendelse-- Calcular la posición final basándose en la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorend
El servidor necesita hacer 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 asígnale el valor de 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-- Remover 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 crea en el servidor, entonces todos podrán 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 significa que el cliente disparando 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 dispare el arma verá el rayo láser instantáneamente. Otros clientes experimentarán un pequeño retraso entre cuando otro jugador dispare y aparezca un rayo. Este es el mejor escenario: no hay forma de comunicar el rayo de un cliente a otros clientes más rápido.
Cliente que dispara
Primero, el cliente necesita informar 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 decir a todos los clientes la posición inicial 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 inicial del láser. Esto podría ser enviado desde el cliente, pero es mejor evitar confiar en el cliente cuando sea posible. La posición del asa del arma del personaje será la posición inicial, así 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 carácter y devolver el objeto del asa.
local LASER_DAMAGE = 10-- Encontrar el asa 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 al jugador que disparó el láser (para que el cliente de ese jugador no renderice el láser dos veces), el asa 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 asa de la herramienta y el valor de posición final enviado por el servidor. El jugador que disparó el rayo láser en primer lugar debería ignorar este evento, de lo contrario, verán 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 dispara 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 Activate.
Usa una declaración if para verificar si shootingSound existe; si es así, llama a su función Play().
laserPart.Parent = workspace-- Añadir el rayo láser al servicio de 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 utilizarlos para enviar valores falsos al servidor. Es importante utilizar 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 son realistas. En este caso, el servidor necesitará:
- Verificar si la distancia entre el jugador y la posición golpeada por el rayo láser está dentro de un cierto límite.
- Hacer 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.
Añade 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, así que el ServerLaserManager necesita ser ajustado para aceptarlo.
En el script ServerLaserManager, añade un parámetro hitPosition a la función damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Remover 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 el valor de 10. Esta será la distancia máxima permitida entre el golpe y el carácter. Se necesita una tolerancia porque el carácter 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 carácter y la posición golpeada. Si la distancia es mayor que MAX_HIT_PROXIMITY, entonces devuelve false.
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 implicará 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 disparando 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 fue el personaje, ignorar el disparoif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendDeclara una variable en la función damageCharacter llamada validShot. Asígnale 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, añade 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-- Remover salud del personajehumanoid.Health -= LASER_DAMAGEendend
Ahora el evento remoto damageCharacter es más seguro y impedirá que la mayoría de los jugadores lo abuse. Ten en cuenta que algunos jugadores malintencionados a menudo encontrarán maneras de eludir la validación; mantener seguros los eventos remotos es un esfuerzo continuo.
Tu bláster láser está ahora completo, con un sistema básico de detección de impactos usando raycasting. Prueba el tutorial de Detectar Entrada del Usuario para averiguar cómo puedes añadir 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 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
-- Ningún objeto fue golpeado, 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 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 personaje del jugador para prevenir 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 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 probablemente se trata de un personaje de 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 basándose 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 será visible
-- Crear un rayo láser desde una posición de inicio hacia una posición 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
-- Añadir el rayo láser al servicio de Debris para que sea 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 LaserRenderer
ServerLaserManager
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Encontrar el asa 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 disparando 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 fue el personaje, ignorar 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
-- Remover 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)