Detecta golpes con láser

*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 blaster en Crear herramientas de jugador 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 especificada con una longitud definida. Si el rayo se choca con objetos o terreno en su camino,返回信息 sobre la colisión, como la posición y el objeto con el que colisionó.

Raycast desde A hacia B colisionando con una pared

Encontrando la ubicación del mouse

Antes de que se pueda disparar un láser, debes saber primero dónde apunta el jugador. Esto se puede encontrar al raycasting desde la ubicación 2D del jugador en la pantalla directamente hacia delante de la cámara en el mundo del juego. El rayo chocará con lo que el jugador apunta con el ratón.

  1. Abre el script ToolController dentro de la herramienta Blaster desde Crear herramientas de jugador. Si aún no has completado ese tutorial, puedes descargar el modelo Blaster yInsertarlo en StarterPack.

  2. En la parte superior del script, declare 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
    -- Conecta 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 se conoce la ubicación del mouse 2D, sus propiedades X y Y se pueden usar como parámetros para la función Camera:ViewportPointToRay(), que crea un 1> Datatype.Ray1> desde la pantalla en el mundo del juego 3D.

  1. Usa las propiedades X y Y de mouseLocation como argumentos para la función 1> Class.Camera:ViewportPointToRay()|ViewportPointToRay() . Asigna esto a una variable llamada4> screenToWorldRay4> .


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crea un rayo desde la ubicación de la ratón 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    end

Es hora de usar la función Raycast para comprobar si el rayo golpea un objeto. Esto requiere una posición de inicio y un fuerza vectorialde dirección: en este ejemplo, usarás las propiedades de inicio y dirección de screenToWorldRay .

La longitud del vector de dirección determina la distancia a la que viajará el rayo. El rayo debe ser tan largo como el MAX_MOUSE_DISTANCE, por lo que tendrás que multiplicar el vector de dirección por MAX_MOUSE_DISTANCE.

  1. Declara una variable llamada direcciónVector y asigna el valor de screenToWorldRay.Direction multiplicado por MAX_MOUSE_DISTANCE .


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crea un rayo desde la ubicación de mouse 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- La dirección de la unidad del rayo se multiplica por una distancia máxima
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
  2. Llamar la función de espacio de trabajo Raycast que pasa la propiedad Origen de screenToWorldRay como primer argumento y 1> numberDirection1> como segundo. Asignar esto a una variable llamada 4> raycastResult4> .


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crea un rayo desde la ubicación de mouse 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- La dirección de la unidad del rayo se multiplica por una distancia máxima
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
    -- Raycast desde la dirección de la 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,返回一个 RaycastResult , que contiene información sobre la colisión entre el rayo y el objeto.

Propiedad de RaycastResultDescripción
InstanciaLa BasePart o Terrain celda que el rayo intersección.
PosiciónDonde ocurrió la intersección; generalmente un punto directamente en la superficie de una parte o terreno.
MaterialEl material en el punto de colisión.
NormalEl vértice normal de la cara intersección. Esto se puede usar para determinar en qué dirección apunta la cara.

La propiedad Position será la posición del objeto que el mouse está pasando por encima. Si el mouse no está pasando por encima de ningún objeto dentro de un rango de MAX_MOUSE_DISTANCE , raycastResult será nulo.

  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 nulo, entonces encuentra el final del raycast. Calcula la posición 3D del mouse al agregar screenToWorldRay.Origin y directionVector juntos.


local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Crea un rayo desde la ubicación de mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- La dirección de la unidad del rayo se multiplica por una distancia máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast desde la dirección de la rayo hacia su dirección
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Restablecer el punto de intersección 3D
return raycastResult.Position
else
-- No se golpeó ningún objeto, así que cálculo la posición al final del rayo
return screenToWorldRay.Origin + directionVector
end
end

Dispara hacia el objetivo

Ahora que la posición del mouse 3D es conocida, se puede usar como posición de objetivo para disparar un láser hacia. Un segundo rayo se puede cast entre el arma del jugador y la posición del objetivo usando la función Raycast .

  1. Declara una constante llamada MAX_LASER_DISTANCE en la parte superior del script y asigna a 500 o el rango que elijas para el 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. Llamar getWorldMousePosition y asignar el resultado a una variable llamada mousePosition . Esto será la posición de destino para el raycast.


    -- No se golpeó ningún objeto, así que cálculo 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 vértice de dirección para la función de raycast representará la dirección desde la posición de la herramienta del jugador a la ubicación del objetivo.

  1. Declara una variable llamada dirección de destino y calcula el vértice de dirección restante al restar la posición de la herramienta de mouseLocation .

  2. Normalice el vértice usando su propiedad Unidad . Esto le da una magnitud de 1, lo que permite multiplicarlo más tarde por una longitud.


    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 direcciónVector y asigna a ella 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 se puede usar para almacenar parámetros adicionales para la función de raycast. Se usará en su láser para asegurarse de que el raycast no se colisione accidentalmente con el jugador que dispara el arma. Cualquier parte incluida en la propiedad Datatype.RaycastParams.FilterDescendantsInstances|FilterDescendants

  1. Continúe con la función fireWeapon y declare una variable llamada weaponRaycastParams . Asigne un nuevo objeto de RaycastParams a ella.

  2. Crear una tabla que contenga el personaje local del jugador y asignarlo a la propiedad weaponRaycastParams.FilterDescendantsInstances

  3. Raycast desde la posición de la mano de herramientas del jugador, en una dirección hacia el directionVector . Recuerda agregar weaponRaycastParams como argumento esta vez. Asigna esto a una variable nombrada 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 se multiplica por una distancia máxima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignora el personaje 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)
end

Finalmente, necesitará 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 golpe. Si no se devolvió nada, la posición final debe ser calculada para crear el láser.

  1. Declara una variable vacía llamada hitPosition .

  2. Usa la declaración si para chequear si weaponRaycastResult tiene un valor. Si se golpeó un objeto, asigna weaponRaycastResult.Position a 1> hitPosition1> .


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Compruebe si se golpearon algunos objetos entre la posición de inicio y final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Si weaponRaycastResult no tiene valor, calcula la posición final del raycast agregando juntos la posición de la mano de herramienta con el 向量Vector . Asigna esto a 1> hitPosition 1> .


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Compruebe si se golpearon algunos objetos entre la posición de inicio y final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Calcular la posición de finalización según la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. Navegue a la función toolActivated y llame a la función fireWeapon para que el láser dispare cada vez que la herramienta se activa.


    local function toolActivated()
    tool.Handle.Activate:Play()
    fireWeapon()
    end

Revisando el objeto golpeado

Para encontrar si el objeto golpeado por el láser es parte del personaje de un jugador o solo una parte del paisaje, necesitarás buscar un Humanoid, como cada personaje tiene uno.

Primero, necesitarás encontrar el modelo de personaje . Si se golpeó una parte del personaje, no puede 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 antepasado de modelo de personaje del objeto golpeado por el láser, si existe uno. Si encuentras un modelo y contiene un humanoid, en la mayoría de los casos puedes asumir que es un personaje.

  1. Agregue el código resaltado a continuación a la weaponRaycastResult si declaración para ver si un personaje fue golpeado.


    -- Compruebe si se golpearon algunos objetos entre la posición de inicio y final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- La instancia golpeada será un hijo de un modelo de aplicación de modelado
    -- Si se encuentra un humanoid en el modelo, 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
    print("Player hit")
    end
    end
    else
    -- Calcular la posición de finalización según la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end

Ahora el láser debería imprimir Player hit a la ventana de salida cada vez que la operación de raycast golpee a otro jugador.

Probar con múltiples jugadores

Se necesitan dos jugadores para probar si el raycast de armas está encontrando a otros jugadores, por lo que debe comenzar un servidor local.

  1. Seleccione la pestaña Prueba en Studio.

  2. Asegúrese de que el menú desplegable de jugadores esté configurado como '2 Jugadores' y haga clic en el botón Iniciar para comenzar un servidor local con 2 clientes. Tres ventanas aparecerán. La primera ventana será el servidor local, las otras ventanas serán los clientes para Player1 y Player2.

  3. En un cliente, pruebe disparando al otro jugador con el arma haciendo clic en él. "Player golpeado" debería mostrarse en la salida cada vez que un jugador es disparado.

Puede obtener más información sobre la pestaña de prueba aquí .

Encuentra la posición del láser

El láser debería disparar un rayo de luz rojo a su objetivo. La función para esto estará dentro de un ModuleScript para que se pueda reutilizar en otros scripts más tarde. Primero, el script necesitará encontrar la posición en la que se deberá renderizar el rayo de luz.

  1. Crea un ModuleScript llamado LaserRender , padre de StarterPlayerScripts debajo de StarterPlayer.

  2. Abre el script y renombra la tabla de módulos a LaserRender .

  3. Declara una variable llamada DURACIÓN DE LÁSER con un valor de 0.15 . Esto será la duración (en segundos) del láser visible.

  4. Crea una función de LaserRender llamada createLaser con dos parámetros llamados toolHandle y endPosition .


    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- Tiempo que el láser es visible para
    -- Crea un rayo láser desde una posición de inicio hacia una posición de final
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Declara una variable llamada startPosition y establece la propiedad Position de toolHandle como su valor. Esto será la posición del láser del jugador.

  6. Declara una variable llamada láserDistancia y sótala endPosition de startPosition para encontrar la diferencia entre los dos vectores. Usa la propiedad 1> Magnitud1> de esta para obtener la longitud del rayo láser.


    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. Declare una variable laserCFrame para almacenar la posición y orientación del rayo láser. La posición debe ser el punto medio del inicio y fin del rayo. Usa CFrame.lookAt para crear un nuevo CFrame que se encuentra


    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 de láser

Ahora que sabes dónde crear un rayo láser, tienes que añadir el propio rayo. Esto se puede hacer fácilmente con una parte de neón.

  1. Declara una variable laserPart y asigna a ella una nueva instancia Part .

  2. Establece las siguientes propiedades de laserPart :

    1. Tamaño : Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame : laserCFrame
    3. Anclado : true
    4. Puede colisionar : false
    5. Color : Color3.fromRGB(225, 0, 0) (un color rojo fuerte)
    6. Material : Enum.Material.Neon
  3. Padre laserPart a Espacio de Trabajo .

  4. Agregue la parte al servicio de 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.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 láser al servicio de Debris para ser eliminado y limpiado
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Ahora la función para renderizar el rayo láser está completa, se puede llamar por el Controlador de herramientas .

  1. En la parte superior del script ToolController , declara una variable llamada LaserRender y requiere el módulo de script de LaserRender que se encuentra 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 LaserRender createLaser usando el mango de herramientas y hitPosition como argumentos.


    -- Calcular la posición de finalización según 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 Tocar. Un rayo láser debería ser visible entre el arma y el mouse cuando la herramienta está activada.

Control de 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 comprobando si ha pasado suficiente tiempo desde que un jugador disparó por última vez.

  1. Declara una variable en la parte superior de la ToolController llamada FIRE_RATE . Esto será el tiempo mínimo entre cada disparo. Dale un valor de tu elección; este ejemplo usa 0.3 segundos.

  2. 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 = 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 mirará cuánto tiempo ha pasado desde el último disparo y devolverá true o false.


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- Compruebe si se ha acumulado suficiente tiempo desde el disparo anterior
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. Dentro de la función declare una variable nombrada currentTime ; asignar a ella el resultado de llamar la función tick(). Esto devuelve cuánto tiempo ha pasado, en segundos, desde el 1 de enero de 1970 (una fecha de cualquier tipo ampliamente utilizada para calcular el tiempo).

  5. Sustrae el timeOfPreviousShot de currentTime y devuelve falso si el resultado es menor que 2> FIRE_RATE2>; de lo contrario, devuelve 5> verdadero5> .


    -- Compruebe si se ha acumulado suficiente tiempo desde el disparo anterior
    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 disparan las armas usando tick .


    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. En la función toolActivated, crea una declaración if y llama a canShootWeapon para comprobar si la arma se puede disparar.


    local function toolActivated()
    if canShootWeapon() then
    tool.Handle.Activate:Play()
    fireWeapon()
    end
    end

Cuando pruebes el blaster deberás encontrar que sin importar la rapidez con la que hagas hcer clic, siempre habrá un breve 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.

  1. Crear un directorio en ReplicatedStorage llamado Eventos .

  2. Inserta un evento remoto en la carpeta de eventos y llévalo por nombre DamageCharacter .

  3. En ToolController , crea variables al comienzo del script para ReplicatedStorage y la carpeta de Eventos.


    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. Reemplace la declaración de impresión de "Player hit" con una línea de Lua para disparar el evento remoto de fireWeapon con la variable de argumento characterModel .


    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 de finalización según la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end

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

  1. Inserta un Script en ServerScriptService y llévalo por nombre ServerLaserManager .

  2. Declara una variable llamada LASER_DAMAGE y establece que 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 carácter de Humanoid y sótalo LASER_DAMAGE de su salud.

  5. Conecta la función damageCharacter a el evento remoto DamageCharacter en la carpeta de Eventos.


    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
    -- Eliminar salud del personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Conecta eventos a funciones apropiadas
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Prueba el blaster con 2 jugadores iniciando un servidor local. Cuando dispare al otro jugador, su salud se reducirá por el número asignado a LASER_DAMAGE .

Renderizar los rayos láser de otros jugadores

Actualmente, el rayo láser se crea 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 creó en el servidor, todos podrían verlo. Sin embargo, habría un pequeño retraso entre el cliente que dispara el arma y el servidor que recibe la información sobre el tiro. 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 con lag.

Para solucionar 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 un rayo aparece. Este es el mejor escenario de caso: no hay forma de comunicar el láser de un cliente a otros clientes más rápido.

Cliente del Tirador

Primero, el cliente necesita decirle al servidor que ha disparado un láser y proporcionar la posición de finalización.

  1. Inserta un Evento remoto en la carpeta de Eventos en ReplicatedStorage y llévalo por nombre LaserFired .

  2. Localice la función fireWeapon en el script ToolController . Al final de la función, dispara el evento remoto LaserFired usando 1> 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 que el cliente ha disparado y comunicar a todos los clientes la posición de inicio y fin del láser para que también puedan renderizarlo.

  1. En el script ServerLaserManager , crea una función llamada playerFiredLaser por encima de damageCharacter con dos parámetros llamados 1> playerFired1> y 4> endPosition4> .

  2. Conecta la función a la LaserFired evento remoto.


    -- Informar a todos los clientes que se ha disparado un láser para que puedan mostrar el láser
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- Conecta eventos a funciones apropiadas
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

El servidor necesita la posición de inicio del láser. Esto se puede enviar desde el cliente, pero es mejor evitar confiar en el cliente en la medida de lo posible. La posición de la mano de la arma del personaje será la posición de inicio, por lo que el servidor puede encontrarla desde allí.

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

  2. Usa el siguiente código para buscar el personaje del jugador para la arma y devolver el objeto de manejo.


    local LASER_DAMAGE = 10
    -- Encuentra la maneta 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
    -- Informar 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 FireAllClients en el evento remoto LaserFired para enviar la información requerida para renderizar el láser a los clientes. Esto incluye al jugador que disparó el láser (para que el cliente para ese jugador no renderice el láser dos veces) 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 1> toolHandle1> .

  2. Si toolHandle existe, fire el evento LaserFired para todos los clientes que usan playerFired, toolHandle y 1> endPosition1> como argumentos.


    -- Informar 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

Renderizado en los clientes

Ahora FireAllClients ha sido llamado, cada cliente recibirá un evento del servidor para renderizar un rayo láser. Cada cliente puede reutilizar el módulo LaserRenderer desde el anterior para renderizar el rayo láser usando la posición y el valor de la posición de manejo del herramientaenviados por el servidor. El jugador que disparó el rayo láser en el primer lugar debería ignorar este evento de lo contrario

  1. Crea un LocalScript en StarterPlayerScripts llamado ClientLaserManager .

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

  3. Crea una función llamada crearPlayerLaser con los parámetros playerWhoShot , toolHandle y 1> endPosition1> .

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

  5. En la función, use una declaración si para ver si playerWhoShot no es igual a playerLocalPlayer .

  6. Dentro de la declaración if, llama la función createLaser desde el módulo LaserRender 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 blaster con 2 jugadores iniciando un servidor local. Posiciona cada cliente en diferentes lados de tu monitor para que puedas ver ambas ventanas al mismo tiempo. Cuando disparas en un cliente, deberás ver el láser en el otro cliente.

Efectos de sonido

El efecto de sonido de disparo solo se reproducirá 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.

  1. En el script ToolController , navega a la función toolActivated y elimina la línea que reproduce el sonido de Activate.


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. En la parte inferior de la función createLaser en LaserRender , declara una variable llamada shootingSound y usa el método 1> Class.Instance:FindFirstChild()|FindFirstChild()1> de 4> toolHandle4> para verificar el sonido de activación.

  3. Usa una declaración si para comprobar si shootingSound existe; si lo hace, llama su función Reproducir .


    laserPart.Parent = workspace
    -- Añadir láser al servicio de Debris para ser eliminado y limpiado
    Debris:AddItem(laserPart, SHOT_DURATION)
    -- Reproduce el sonido de disparo del arma
    local shootingSound = toolHandle:FindFirstChild("Activate")
    if shootingSound then
    shootingSound:Play()
    end
    end

Asegurando controles remotos usando validación

Si el servidor no está verificando los datos de las solicitudes entrantes, un hacker puede abusar de funciones y eventos remotos y usarlos para enviar valores falsos al servidor. Es importante usar 验证 del lado del servidor para evitar esto.

En su forma actual, el evento remoto DamageCharacter es muy vulnerable a los ataques. Los hackers podrían usar este evento para dañar a cualquier jugador que quieran en el juego sin dispararles.

La validación es el proceso de verificación de que los valores que se envían al servidor son realistas. En este caso, el servidor necesitará:

  • Compruebe si la distancia entre el jugador y la posición golpeada por el láser está dentro de un cierto límite.
  • Raycast entre el arma que disparó el láser y la posición de golpe para asegurar que el disparo era posible y no pasó a través de ninguna pared.

Cliente

El cliente necesita enviar al servidor la posición golpeada por el raycast para que pueda verificar la distancia que es realista.

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

  2. Agregue 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, por lo que el ServerLaserManager necesita ser ajustado para aceptarlo.

  1. En el script ServerLaserManager , agregue un parámetro de hitPosition a la función damageCharacter.


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Eliminar salud del personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Debajo de la función getPlayerToolHandle , crea una función llamada playerFired con tres parámetros: 1> playerFired1>, 4> characterToDamage4> y 7> hitPosition7>.


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

La primera prueba será la distancia entre la posición de golpe y la posición de personaje.

  1. Declara una variable llamada MAX_HIT_PROXIMITY en la parte superior del script y asigna un valor de 10 . Esto será la distancia máxima permitida entre el hit y el personaje. Se necesita una tolerancia, ya que el personaje 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 personaje y la posición de golpe. Si la distancia es mayor que MAX_HIT_PROXIMITY entonces devuelve falso .


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Validar la distancia entre el carácter golpeado y la posición de golpe
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

La segunda verificación involucrará un raycast entre el arma disparada y la posición de golpe. 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 este verificar, comprobar. Return true al final de la función: si llega al finalizar, todos los cheques han pasado.


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Validar la distancia entre el carácter golpeado y la posición de golpe
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 then
    return false
    end
    -- Compruebe si está disparando a través de las 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, ignore el tiro
    if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
    return false
    end
    end
    return true
    end
  2. Declare una variable en la función damageCharacter llamada validoShot . Asigna el resultado de una llamada a la función isHitValid con tres argumentos: 1> playerFired1> , 4> characterToDamage4> y 7> hitPosition7> .

  3. En la declaración de abajo, agregue un operador y para verificar si validShot es verdadero .


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
    if humanoid and validShot then
    -- Eliminar salud del personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end

Ahora el evento remoto de daño es más seguro y evitará que la mayoría de los jugadores lo abusen. Tenga en cuenta que algunos jugadores maliciosos a menudo encontrarán formas de evitar la validación; mantener los eventos remotos seguros es un esfuerzo continuo.

Su láser está ahora completado, con un sistema de detección de golpes básico que utiliza intersección rayo-superficie, emisión de rayos. Pruebe el tutorial Detectando la entrada del usuario para averiguar cómo puede agregar una acción de recarga a su láser, o crear un mapa de juego divertido y pruebe su láser con otros jugadores!

Código Final

Controlador de herramientas


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
-- Compruebe si se ha acumulado suficiente tiempo desde 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()
-- Crea un rayo desde la ubicación de la ratón 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- La dirección de la unidad del rayo se multiplica por una distancia máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast desde la ubicación del rayo hacia su dirección
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Restablecer el punto de intersección 3D
return raycastResult.Position
else
-- No se golpeó ningún objeto, así que cálculo 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
-- Ignora el personaje 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)
-- Compruebe si se golpearon algunos objetos entre la posición de inicio y final
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- La instancia golpeada será un hijo de un modelo de aplicación de modelado
-- Si se encuentra un humanoid en el modelo, 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 de finalización según 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)

Reproductor láser


local LaserRenderer = {}
local Debris = game:GetService("Debris")
local SHOT_DURATION = 0.15 -- Tiempo que el láser es visible para
-- Crea un rayo láser desde una posición de inicio hacia una posición de 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
-- Añadir láser al servicio de Debris para ser eliminado y limpiado
Debris:AddItem(laserPart, SHOT_DURATION)
-- Reproduce el sonido de disparo del arma
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRenderer

Administrador de servidores


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Encuentra la maneta 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 carácter golpeado y la posición de golpe
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Compruebe si está disparando a través de las 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, ignore el tiro
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Informar 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
-- Eliminar salud del personaje
humanoid.Health -= LASER_DAMAGE
end
end
-- Conecta eventos a funciones apropiadas
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

Administrador de clientes


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)