Detección de golpes 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 cómo lanzar un láser desde el bláster en Crear herramientas de jugador y detectar si golpea o no a un jugador.

Raycasting para encontrar colisiones

Raycasting crea un rayo invisible desde una posición de inicio hacia una dirección determinada con una longitud definida.Si el rayo se choca con objetos o terreno en su camino, devolverá información sobre la colisión, como la posición y el objeto con el que se chocó.

Lanzar rayos desde A hacia B chocando con una pared

Encuentra 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 del mouse 2D del jugador en la pantalla directamente hacia adelante desde la cámara al mundo del juego.El rayo se colisionará con lo que el jugador esté apuntando con el ratón.

  1. Abre el script del controlador de herramientas dentro de la herramienta Blaster de Crear herramientas para jugadores.Si aún no has completado ese tutorial, puedes descargar el modelo Blaster y insertarlo 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
    -- 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 del mouse de 2D 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 del ratón de 2D, sus propiedades X y Y se pueden usar como parámetros para la función >, que crea un desde la pantalla en el mundo del juego 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 del ratón de 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 origen y dirección de screenToWorldRay .

La longitud del vector de dirección determina hasta dónde viajará el rayo.El rayo debe ser tan largo como el MAX_MOUSE_DISTANCE , así que tendrás que multiplicar el vector de dirección por MAX_MOUSE_DISTANCE .

  1. Declare una variable llamada direcciónVector y asigne el valor de multiplicado por >.


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crear un rayo desde la ubicación del ratón de 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- El vector de dirección de la unidad del rayo multiplicado por una distancia máxima
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
  2. Llama a la función Raycast de espacio de trabajo, pasando la propiedad Origen de screenToWorldRay como primer argumento y directionVector como segundo.Asigna esto a una variable llamada raycastResult .


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crear un rayo desde la ubicación del ratón de 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- El vector de dirección de la unidad 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 lanzamiento de rayos 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
InstanciaLa celda BasePart o Terrain que el rayo interseccionó.
PosiciónDonde ocurrió la intersección; por lo general, un punto directamente en la superficie de una parte o terreno.
MaterialesEl material en el punto de colisión.
NormalesEl vector normal del rostro interseccionado. Esto se puede usar para determinar hacia qué dirección apunta el rostro.

La propiedad Posición será la posición del objeto sobre el que el mouse se desplaza.Si el mouse no se desplaza 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 Posición .

  3. Si raycastResult es nil entonces encuentra el final del raycast.Calcula la posición 3D del mouse agregando screenToWorldRay.Origin y directionVector juntos.


local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Crear un rayo desde la ubicación del ratón de 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- El vector de dirección de la unidad 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
-- Devuelve el punto de intersección de 3D
return raycastResult.Position
else
-- No se golpeó ningún objeto, así que calcula la posición al final del rayo
return screenToWorldRay.Origin + directionVector
end
end

Fuego hacia el objetivo

Ahora que se conoce la posición del ratón 3D, se puede utilizar como posición de objetivo para disparar un láser hacia .Un segundo rayo se puede lanzar entre la arma del jugador y la posición objetivo usando la función Raycast .

  1. Declare una constante llamada MAX_LASER_DISTANCE en la parte superior del script y asignársela a 500 o al rango elegido para el láser blaster.


    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 bajo la función getWorldMousePosition.

  3. Llama a getWorldMousePosition y asigna el resultado a una variable llamada posición del mouse . Esta será la posición objetivo para el lanzamiento de rayos.


    -- No se golpeó ningún objeto, así que calcula la posición al final del rayo
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end

Esta vez, el vector de dirección para la función de lanzamiento de rayos representará la dirección desde la posición de la herramienta del jugador hasta la ubicación objetivo.

  1. Declare una variable llamada dirección objetivo y calcule el vector de dirección restrayendo la posición de la herramienta de mouseLocation.

  2. Normaliza el vector usando su propiedad Unidad . Esto le da una magnitud de 1, lo que facilita multiplicarla 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. Declare una variable llamada direcciónVector y asigne 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 utilizar para almacenar parámetros adicionales para la función de lanzamiento de rayos.Se usará en su láser blaster para asegurarse de que el raycast no choque accidentalmente con el jugador que dispara el arma.Cualquier parte incluida en la propiedad de un objeto RaycastParams se ignorará en el raycast.

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

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

  3. Lanzamiento de rayos desde la posición del mango de la herramienta del jugador, en una dirección hacia el directionVector.Recuerde agregar weaponRaycastParams como argumento esta vez.Asigna esto a una variable llamada weaponRaycastResult .


local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local tool = script.Parent
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local function getWorldMousePosition()

local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calcular un vector de dirección normalizado y multiplicar por la distancia 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)
end

Por último, tendrás que comprobar que la operación de lanzamiento de rayos 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 calcularse para crear el láser.

  1. Declare una variable vacía llamada hitPosition .

  2. Usa una declaración si para verificar si weaponRaycastResult tiene un valor. Si se golpeó un objeto, asigna weaponRaycastResult.Position a hitPosition .


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Compruebe si algún objeto fue golpeado entre la posición de inicio y final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Si weaponRaycastResult no tiene valor, calcular la posición final del raycast agregando juntos la posición **** del mango de la herramienta con el directionVector.Asigna esto a hitPosition .


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Compruebe si algún objeto fue golpeado entre la posición de inicio y final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Calcular la posición final basada en la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. Navegue hasta la función toolActivated y llame 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

Compruebe el objeto golpeado

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

Primero, necesitarás encontrar el modelo de caracteres **** .Si se golpeó una parte del personaje, no puedes asumir que el padre del personaje golpeado sería el personaje.El láser podría haber golpeado una parte del cuerpo, un accesorio o una herramienta, todas las 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 uno.Si encuentras un modelo y contiene un humanoide, en la mayoría de los casos puedes asumir que es un personaje.

  1. Añade el código resaltado a continuación a la declaración weaponRaycastResult si para comprobar si un personaje fue golpeado.


    -- Compruebe si algún objeto fue golpeado 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 humanoide 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 final basada en la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end

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

Prueba con múltiples jugadores

Se necesitan dos jugadores para probar si el rayo de armas está encontrando a otros jugadores, por lo que debe iniciar 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 de inicio para iniciar 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, prueba disparar al otro jugador con la arma haciendo clic en él.El "Golpe de jugador" debe mostrarse en la salida cada vez que se dispara a un jugador.

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

Encuentra la posición del láser

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

  1. Crea un ModuleScript llamado LaserRenderer que pertenece a StarterPlayerScripts bajo StarterPlayer.

  2. Abre el script y renombra la tabla de módulos al nombre del script LaserRenderer .

  3. Declare una variable llamada DURACIÓN DE DISPARO con un valor de 0.15 .Esta será la cantidad de tiempo (en segundos) que el láser estará 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 está visible para
    -- Crear un rayo láser desde una posición de inicio hacia una posición final
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Declare una variable llamada startPosition y establezca la propiedad Posición de toolHandle como su valor.Esta será la posición del láser blaster del jugador.

  6. Declara una variable llamada distancia láser y resta endPosition de startPosition para encontrar la diferencia entre los dos vectores.Usa la propiedad Magnitud 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. 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 comienzo y el final del rayo.Usa CFrame.lookAt para crear un nuevo CFrame ubicado en startPosition y dirigido hacia endPosition .Multiplica esto por un nuevo CFrame con un valor del eje Z de la mitad de negativo 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 parte láser

Ahora que sabe dónde crear un rayo láser, debe agregar el propio rayo. Esto se puede hacer fácilmente con una pieza de neón.

  1. Declare una variable laserPart y asigne a ella una nueva instancia Part.

  2. Establezca las siguientes propiedades de laserPart :

    1. Tamaño : Vector3.new(0.2, 0.2, distancia láser)
    2. CFrame : laserCFrame
    3. Anclado : verdadero
    4. Puede colisionar : false
    5. Color : Color3.fromRGB(225, 0, 0) (un color rojo fuerte)
    6. Material : Enum.Material.Neón
  3. Padre laserPart a Espacio de trabajo .

  4. Añade la parte al servicio Debris para que se elimine después de la cantidad de segundos en la variable SHOT_DURATION.


    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.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ñade un rayo láser al servicio de escombros para ser eliminado y limpiado
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Ahora que 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 Controlador de herramientas , declare una variable llamada LaserRenderer y requiera el script del 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. Al final de la función fireWeapon de la función, llama a la función LaserRenderer createLaser usando el control del instrumento y hitPosition como argumentos.


    -- Calcular la posición final basada en la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. Prueba la arma haciendo clic en el botón de reproducir. Un rayo láser debe ser visible entre la arma y el ratón cuando la herramienta esté activada.

Controlar la puntuarde disparo de las armas

Las armas necesitan un retraso entre cada disparo para evitar que los jugadores causen demasiado daño en un corto período de tiempo.Esto se puede controlar verificando si se ha pasado suficiente tiempo desde que un jugador disparó por última vez.

  1. Declare una variable en la parte superior del Controlador de herramientas llamado Tasa de fuego .Este será el tiempo mínimo entre cada disparo.Dale un valor de tu elección; este ejemplo usa 0.3 segundos.

  2. Declare otra variable debajo llamada tiempo de disparo anterior 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 verá cuánto tiempo ha pasado desde el último disparo y devolverá verdadero o falso.


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- Compruebe si se ha pasado suficiente tiempo desde que se disparó el último disparo
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. Dentro de la función declare una variable llamada tiempo actual ; asignarle 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 arbitraria ampliamente utilizada para calcular el tiempo).

  5. Resta el timeOfPreviousShot de currentTime y devuelve falso si el resultado es más pequeño que FIRE_RATE ; de lo contrario, devuelve verdadero .


    -- Compruebe si se ha pasado suficiente tiempo desde que se disparó 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 actualizar timeOfPreviousShot cada vez que se dispara 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 si y llama a canShootWeapon para verificar si la arma se puede disparar.


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

Cuando pruebes el bláster 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ña al jugador

Los clientes no pueden dañar a otros clientes directamente; el servidor debe ser responsable de emitir daños cuando un jugador sea 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. Crea una carpeta en ReplicatedStorage llamada Eventos .

  2. Inserta un evento remoto en la carpeta de eventos y nombralo Daño de personaje .

  3. En Controlador de herramientas , 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. Reemplazar la declaración de impresión con una línea de Luau para disparar el evento remoto Daño de personaje con la variable 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 basada en la distancia máxima del láser
    hitPosition = tool.Handle.Position + directionVector
    end

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

  1. Inserte un script en ServerScriptService y llámalo ServerLaserManager .

  2. Declare una variable llamada LASER_DAMAGE y establezca el valor en 10 o un valor de su 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 Humanoide del personaje y resta LASER_DAMAGE de su salud.

  5. Conecta la función damageCharacter a el evento remoto Daño de personaje 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
    -- Quitar la salud del personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Conectar eventos a funciones apropiadas
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Prueba el blaster con 2 jugadores iniciando un servidor local.Cuando disipes al otro jugador, su salud disminuirá por el número asignado a LASER_DAMAGE.

Renderizar los rayos láser de otro jugador

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, entonces 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 disparo.Esto significaría que el cliente disparando el arma vería un retraso entre cuando activan el arma y cuando ven el rayo láser; el arma se sentiría lenta como resultado.

Para resolver este problema, cada cliente creará sus propios rayos láser.Esto significa que el cliente que dispara el arma verá instantáneamente el rayo láser.Otros clientes experimentarán un pequeño retraso entre cuando otro jugador dispara y aparece un rayo.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 debe decirle al servidor que ha disparado un láser y proporcionar la posición final.

  1. Inserta un evento remoto en la carpeta de eventos en ReplicatedStorage y ponle el nombre disparado por láser .

  2. Localiza la función fireWeapon en el script Controlador de herramientas .Al final de la función, activa 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 que el cliente ha disparado y decirle a todos los clientes la posición de inicio y final del rayo láser para que también lo puedan renderizar.

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

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


    -- Notifica 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 de inicio del láser.Esto podría enviarse desde el cliente, pero es mejor evitar confiar en el cliente donde sea posible.La posición del mango 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 getPlayerToolHandle 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 por la arma y devolver el objeto de agarre.


    local LASER_DAMAGE = 10
    -- Encuentra el mango de la herramienta que el jugador está sosteniendo
    local function getPlayerToolHandle(player)
    local weapon = player.Character:FindFirstChildOfClass("Tool")
    if weapon then
    return weapon:FindFirstChild("Handle")
    end
    end
    -- Notifica 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 al 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 (por lo que el cliente para ese jugador no renderiza el láser dos veces), el mango del bláster (que actúa como una posición de inicio para el láser) y la posición final del láser

  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 que usan playerFired , toolHandle y endPosition como argumentos.


    -- Notifica 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 FireAllClients se ha llamado, cada cliente recibirá un evento del servidor para renderizar un rayo láser.Cada cliente puede reutilizar el módulo Renderizador láser anterior para renderizar el rayo láser usando la posición de manejo y la posición final del herramientaenviada por el servidor.El jugador que disparó el rayo láser en primer lugar debe 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 de eventos.

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

  6. Dentro de la declaración if, llama a la función createLaser de LaserRenderer del módulo 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.Coloca a cada cliente en diferentes lados de tu monitor para que puedas ver ambas ventanas a la vez.Cuando dispares en un solo cliente deberías ver el 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 Controlador de herramientas , navegue hasta la función herramientaActivada y elimine 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 de LaserRenderer , declare una variable llamada disparo de sonido y use el método FindFirstChild() de toolHandle para verificar el sonido Activar .

  3. Usa una declaración si para verificar si existe shootingSound; si lo hace, llama a su función Jugar .


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

Asegurar controles remotos usando validación

Si el servidor no está verificando los datos de las solicitudes entrantes, un pirata informático puede abusar de las funciones y eventos remotos y usarlos para enviar valores falsos al servidor.Es importante utilizar validación del lado del servidor para evitar esto.

En su forma actual, el evento remoto DamageCharacter es muy vulnerable al ataque.Los piratas informáticos 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 verificar que los valores que se envían al servidor sean 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.
  • Lanzar rayos entre el arma que disparó el láser y la posición de golpe para asegurarse de que el disparo fuera posible y no pasara por ninguna pared.

Cliente

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

  1. En Controlador de herramientas , navegue hasta la línea en la que se ejecuta el evento remoto de Daño de personaje 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, por lo que el ServerLaserManager debe ajustarse 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
    -- Quitar la salud del personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Debajo de la función getPlayerToolHandle , cree una función llamada isHitValid con tres parámetros: playerFired , characterToDamage y hitPosition .


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

La primera comprobación será la distancia entre la posición de golpe y el personaje golpeado.

  1. Declare una variable llamada MAX_HIT_PROXIMITY en la parte superior del script y asigne un valor de 10 .Esta será la distancia máxima permitida entre el golpe y el personaje.Se necesita una tolerancia porque el personaje puede haberse movido ligeramente desde que el cliente disparó el evento.


    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.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 golpe de personaje 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 comprobación implicará 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 porque algo estaba bloqueando el disparo.

  1. Copia el código a continuación para realizar esta verificar, comprobar. Devuelve verdadero al final de la función: si llega al finalizar, todas las comprobaciones han pasado.


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Validar la distancia entre el golpe de personaje y la posición de golpe
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 then
    return false
    end
    -- Compruebe si dispara 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, ignora el disparo
    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 disparo válido .Asigne al 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, 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
    -- Quitar la salud del personaje
    humanoid.Health -= LASER_DAMAGE
    end
    end

Ahora el evento remoto de dañoCharacter 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 blaster está ahora completo, con un sistema de detección de golpes básico que utiliza intersección rayo-superficie, emisión de rayos.Prueba el tutorial Detección de la entrada del usuario para descubrir cómo puedes agregar una acción de recarga a tu láser blaster, o crear un mapa divertido de juego y probar tu láser blaster 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 pasado suficiente tiempo desde que se disparó el último disparo
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 del ratón de 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- El vector de dirección de la unidad del rayo multiplicado por una distancia máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast desde el origen del roy hacia su dirección
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Devuelve el punto de intersección de 3D
return raycastResult.Position
else
-- No se golpeó ningún objeto, así que calcula la posición al final del rayo
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calcular un vector de dirección normalizado y multiplicar por la distancia 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 algún objeto fue golpeado 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 humanoide 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 final basada en la distancia máxima del láser
hitPosition = tool.Handle.Position + directionVector
end
timeOfPreviousShot = tick()
eventsFolder.LaserFired:FireServer(hitPosition)
LaserRenderer.createLaser(tool.Handle, hitPosition)
end
local function toolEquipped()
tool.Handle.Equip:Play()
end
local function toolActivated()
if canShootWeapon() then
fireWeapon()
end
end
tool.Equipped:Connect(toolEquipped)
tool.Activated:Connect(toolActivated)

Renderizador láser


local LaserRenderer = {}
local Debris = game:GetService("Debris")
local SHOT_DURATION = 0.15 -- Tiempo que el láser está visible para
-- Crear un rayo láser desde una posición de inicio hacia una posición final
function LaserRenderer.createLaser(toolHandle, endPosition)
local startPosition = toolHandle.Position
local laserDistance = (startPosition - endPosition).Magnitude
local laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)
local laserPart = Instance.new("Part")
laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)
laserPart.CFrame = laserCFrame
laserPart.Anchored = true
laserPart.CanCollide = false
laserPart.Color = Color3.fromRGB(255, 0, 0)
laserPart.Material = Enum.Material.Neon
laserPart.Parent = workspace
-- Añade un rayo láser al servicio de escombros para ser eliminado y limpiado
Debris:AddItem(laserPart, SHOT_DURATION)
-- Reproduce el sonido de disparo de la 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
-- Encuentra el mango de la herramienta que el jugador está sosteniendo
local function getPlayerToolHandle(player)
local weapon = player.Character:FindFirstChildOfClass("Tool")
if weapon then
return weapon:FindFirstChild("Handle")
end
end
local function isHitValid(playerFired, characterToDamage, hitPosition)
-- Validar la distancia entre el golpe de personaje y la posición de golpe
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Compruebe si dispara 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, ignora el disparo
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Notifica a todos los clientes que se ha disparado un láser para que puedan mostrar el láser
local function playerFiredLaser(playerFired, endPosition)
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
end
end
function damageCharacter(playerFired, characterToDamage, hitPosition)
local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
if humanoid and validShot then
-- Quitar la salud del personaje
humanoid.Health -= LASER_DAMAGE
end
end
-- Conectar eventos a funciones apropiadas
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

Gestor de láseres cliente


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)