Tutoriales intermedios

Detección de impactos con láseres

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

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ó.

Raycast de A hacia B colisionando con una pared

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.

  1. 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.

  2. En la parte superior del script, declara una constante llamada MAX_MOUSE_DISTANCE con un valor de 1000.

  3. Crea una función llamada getWorldMousePosition.


    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local function getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end
    local function toolActivated()
    tool.Handle.Activate:Play()
    end
    -- Conectar eventos a funciones apropiadas
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. 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.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local 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.

  1. 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 mouse
    local 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.

  1. 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 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
  2. Llama 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 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)

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 RaycastResultDescripción
InstanceLa BasePart o celda Terrain que el rayo intersectó.
PositionDonde ocurrió la intersección; usualmente un punto directamente en la superficie de una parte o terreno.
MaterialEl material en el punto de colisión.
NormalEl 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.

  1. Crea una declaración if para verificar si raycastResult existe.

  2. Si raycastResult tiene un valor, devuelve su propiedad Position.

  3. 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.

  1. 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.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  2. Crea una función llamada fireWeapon debajo de la función getWorldMousePosition.

  3. 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 rayo
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local 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.

  1. Declara una variable llamada targetDirection y calcula el vector de dirección al restar la posición de la herramienta de mouseLocation.

  2. 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áser
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. Declara 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áxima
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

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.

  1. Continúa la función fireWeapon y declara una variable llamada weaponRaycastParams. Asigna un nuevo objeto RaycastParams a ella.

  2. Crea una tabla que contenga al personaje local del jugador y asígnala a la propiedad weaponRaycastParams.FilterDescendantsInstances.

  3. 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.

  1. Declara una variable vacía llamada hitPosition.

  2. 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 fin
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Si 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 fin
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Calcular la posición final basándose en la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. Navega 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.

  1. 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 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
    print("Jugador golpeado")
    end
    end
    else
    -- Calcular la posición final basándose en la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end

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.

  1. Crea un ModuleScript llamado LaserRenderer, parentado a StarterPlayerScripts bajo StarterPlayer.

  2. Abre el script y cambia el nombre de la tabla del módulo al nombre del script LaserRenderer.

  3. 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.

  4. 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 fin
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Declara 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.

  6. 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.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. Declara 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.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    local 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.

  1. Declara una variable laserPart y asígnale una nueva instancia de Part.

  2. Establece las siguientes propiedades de laserPart:

    1. Size: Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame: laserCFrame
    3. Anchored: true
    4. CanCollide: false
    5. Color: Color3.fromRGB(225, 0, 0) (un color rojo fuerte)
    6. Material: Enum.Material.Neon
  3. Parent laserPart a Workspace.

  4. 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.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(225, 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)
    end

Ahora la función para renderizar el rayo láser está completa, puede ser llamada por el ToolController.

  1. 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.Parent
  2. En 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áser
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. Prueba 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.

  1. 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.

  2. 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 = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. Crea 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.3
    local timeOfPreviousShot = 0
    -- Verificar si ha pasado suficiente tiempo desde el último disparo
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. 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).

  5. 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 disparo
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. Al final de la función fireWeapon, actualiza timeOfPreviousShot cada vez que se dispare el arma usando tick.


    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. Dentro 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() then
    tool.Handle.Activate:Play()
    fireWeapon()
    end
    end

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.

  1. Crea una Carpeta en ReplicatedStorage llamada Events.

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

  3. 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.Parent
    local eventsFolder = ReplicatedStorage.Events
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  4. Reemplaza 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 then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    eventsFolder.DamageCharacter:FireServer(characterModel)
    end
    end
    else
    -- Calcular la posición final basándose en la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end

El servidor necesita hacer daño al jugador que ha sido golpeado cuando se dispara el evento.

  1. Inserta un Script en ServerScriptService y nómbralo ServerLaserManager.

  2. Declara una variable llamada LASER_DAMAGE y asígnale el valor de 10, o un valor de tu elección.

  3. Crea una función llamada damageCharacter con dos parámetros llamados playerFired y characterToDamage.

  4. Dentro de la función, encuentra el Humanoid del personaje y resta LASER_DAMAGE de su salud.

  5. Conecta la función damageCharacter al evento remoto DamageCharacter en la carpeta Events.


    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    function damageCharacter(playerFired, characterToDamage)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Remover salud del personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Conectar eventos a funciones apropiadas
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. 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.

  1. Inserta un RemoteEvent en la carpeta Events en ReplicatedStorage y nómbralo LaserFired.

  2. 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 + directionVector
    end
    timeOfPreviousShot = 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.

  1. En el script ServerLaserManager, crea una función llamada playerFiredLaser encima de damageCharacter con dos parámetros llamados playerFired y endPosition.

  2. 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áser
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- Conectar eventos a funciones apropiadas
    eventsFolder.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í.

  1. Crea una función getPlayerToolHandle encima de la función playerFiredLaser con un parámetro llamado player.

  2. 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á sosteniendo
    local function getPlayerToolHandle(player)
    local weapon = player.Character:FindFirstChildOfClass("Tool")
    if weapon then
    return weapon:FindFirstChild("Handle")
    end
    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)

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.

  1. En la función playerFiredLaser, llama a la función getPlayerToolHandle con playerFired como argumento y asigna el valor a una variable llamada toolHandle.

  2. 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áser
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

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.

  1. Crea un LocalScript en StarterPlayerScripts llamado ClientLaserManager.

  2. Dentro del script, requiere el módulo LaserRenderer.

  3. Crea una función llamada createPlayerLaser con los parámetros playerWhoShot, toolHandle y endPosition.

  4. Conecta la función al evento remoto LaserFired en la carpeta Events.

  5. En la función, usa una declaración if para verificar si playerWhoShot no es igual a LocalPlayer.

  6. 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 jugador
    local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
    if playerWhoShot ~= Players.LocalPlayer then
    LaserRenderer.createLaser(toolHandle, endPosition)
    end
    end
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. 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.

  1. 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() then
    fireWeapon()
    end
    end
  2. En 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.

  3. 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 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

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.

  1. En ToolController, navega a la línea donde se dispara el evento remoto DamageCharacter en la función fireWeapon.

  2. Añade hitPosition como argumento.


    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
    end
    end

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.

  1. 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 personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Debajo de la función getPlayerToolHandle, crea una función llamada isHitValid con tres parámetros: playerFired, characterToDamage y hitPosition.


    end
    local function isHitValid(playerFired, characterToDamage, hitPosition)
    end

La primera verificación será la distancia entre la posición golpeada y el personaje golpeado.

  1. 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.Events
    local LASER_DAMAGE = 10
    local MAX_HIT_PROXIMITY = 10
  2. En 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 golpeada
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

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.

  1. 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 golpeada
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 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
  2. Declara 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.

  3. 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 personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end

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)
©2026 Roblox Corporation. Roblox, el logotipo de Roblox y "Powering Imagination" son algunas de nuestras marcas registradas y no registradas en los Estados Unidos y otros países.