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ó.
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.
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.
En la parte superior del script, declare una constante llamada MAX_MOUSE_DISTANCE con un valor de 1000 .
Crea una función llamada getWorldMousePosition .
local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()endlocal function toolActivated()tool.Handle.Activate:Play()end-- Conecta eventos a funciones apropiadastool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)Usa la función GetMouseLocation de UserInputService para obtener la ubicación 2D del mouse del jugador en la pantalla. Asigna esto a una variable llamada mouseLocation .
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
Ahora 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.
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 2Dlocal 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.
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 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- La dirección de la unidad del rayo se multiplica por una distancia máximalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCELlamar 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 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- La dirección de la unidad del rayo se multiplica por una distancia máximalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Raycast desde la dirección de la rayo hacia su direcciónlocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
Información de colisión
Si la operación de raycast encuentra un objeto golpeado por el rayo,返回一个 RaycastResult , que contiene información sobre la colisión entre el rayo y el objeto.
Propiedad de RaycastResult | Descripción |
---|---|
Instancia | La BasePart o Terrain celda que el rayo intersección. |
Posición | Donde ocurrió la intersección; generalmente un punto directamente en la superficie de una parte o terreno. |
Material | El material en el punto de colisión. |
Normal | El 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.
Crea una declaración if para verificar si raycastResult existe.
Si raycastResult tiene un valor, devuelve su propiedad Position .
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 .
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.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Crea una función llamada fireWeapon debajo de la función getWorldMousePosition
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 rayoreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal 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.
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 .
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áserlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendDeclara 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áximalocal directionVector = targetDirection * MAX_LASER_DISTANCEend
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
Continúe con la función fireWeapon y declare una variable llamada weaponRaycastParams . Asigne un nuevo objeto de RaycastParams a ella.
Crear una tabla que contenga el personaje local del jugador y asignarlo a la propiedad weaponRaycastParams.FilterDescendantsInstances
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.
Declara una variable vacía llamada hitPosition .
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 finallocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendSi 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 finallocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Calcular la posición de finalización según la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorendendNavegue 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.
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 finallocal hitPositionif weaponRaycastResult thenhitPosition = 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 thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Player hit")endendelse-- Calcular la posición de finalización según la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorend
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.
Seleccione la pestaña Prueba en Studio.
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.
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.
Crea un ModuleScript llamado LaserRender , padre de StarterPlayerScripts debajo de StarterPlayer.
Abre el script y renombra la tabla de módulos a LaserRender .
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.
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 finalfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererDeclara una variable llamada startPosition y establece la propiedad Position de toolHandle como su valor. Esto será la posición del láser del jugador.
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.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendDeclare 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.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal 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.
Declara una variable laserPart y asigna a ella una nueva instancia Part .
Establece las siguientes propiedades de laserPart :
- Tamaño : Vector3.new(0.2, 0.2, laserDistance)
- CFrame : laserCFrame
- Anclado : true
- Puede colisionar : false
- Color : Color3.fromRGB(225, 0, 0) (un color rojo fuerte)
- Material : Enum.Material.Neon
Padre laserPart a Espacio de Trabajo .
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.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)local laserPart = Instance.new("Part")laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)laserPart.CFrame = laserCFramelaserPart.Anchored = truelaserPart.CanCollide = falselaserPart.Color = Color3.fromRGB(225, 0, 0)laserPart.Material = Enum.Material.NeonlaserPart.Parent = workspace-- Añadir láser al servicio de Debris para ser eliminado y limpiadoDebris:AddItem(laserPart, SHOT_DURATION)end
Ahora la función para renderizar el rayo láser está completa, se puede llamar por el Controlador de herramientas .
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.ParentEn 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áserhitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endPrueba 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.
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.
Declara otra variable debajo llamada timeOfPreviousShot con un valor de 0 . Esto almacena la última vez que el jugador disparó y se actualizará con cada disparo.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Crea una función llamada canShootWeapon sin parámetros. Esta función mirará cuánto tiempo ha pasado desde el último disparo y devolverá true o false.
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- Compruebe si se ha acumulado suficiente tiempo desde el disparo anteriorlocal function canShootWeapon()endlocal function getWorldMousePosition()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).
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 anteriorlocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendAl final de la función fireWeapon, actualiza timeOfPreviousShot cada vez que se disparan las armas usando tick .
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endEn 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() thentool.Handle.Activate:Play()fireWeapon()endend
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.
Crear un directorio en ReplicatedStorage llamado Eventos .
Inserta un evento remoto en la carpeta de eventos y llévalo por nombre DamageCharacter .
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.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Reemplace 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 thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel)endendelse-- Calcular la posición de finalización según la distancia máxima del láserhitPosition = tool.Handle.Position + directionVectorend
El servidor necesita infligir daño al jugador que ha sido golpeado cuando se ha disparado el evento.
Inserta un Script en ServerScriptService y llévalo por nombre ServerLaserManager .
Declara una variable llamada LASER_DAMAGE y establece que 10 , o un valor de tu elección.
Crea una función llamada damageCharacter con dos parámetros llamados playerFired y characterToDamage .
Dentro de la función, encuentra el carácter de Humanoid y sótalo LASER_DAMAGE de su salud.
Conecta la función damageCharacter a el evento remoto DamageCharacter en la carpeta de Eventos.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Eliminar salud del personajehumanoid.Health -= LASER_DAMAGEendend-- Conecta eventos a funciones apropiadaseventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)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.
Inserta un Evento remoto en la carpeta de Eventos en ReplicatedStorage y llévalo por nombre LaserFired .
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 + directionVectorendtimeOfPreviousShot = 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.
En el script ServerLaserManager , crea una función llamada playerFiredLaser por encima de damageCharacter con dos parámetros llamados 1> playerFired1> y 4> endPosition4> .
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áserlocal function playerFiredLaser(playerFired, endPosition)end-- Conecta eventos a funciones apropiadaseventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
El servidor necesita la posición de inicio del láser. Esto 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í.
Crea una función obtenerPlayerToolHandle por encima de la función playerFiredLaser con un parámetro llamado player .
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á sosteniendolocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Informar a todos los clientes que se ha disparado un láser para que puedan mostrar el láserlocal function playerFiredLaser(playerFired, endPosition)
El servidor ahora puede llamar 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.
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> .
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áserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
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
Crea un LocalScript en StarterPlayerScripts llamado ClientLaserManager .
Dentro del script, requiere el módulo LaserRender .
Crea una función llamada crearPlayerLaser con los parámetros playerWhoShot , toolHandle y 1> endPosition1> .
Conecta la función al evento remoto LaserFired en la carpeta Eventos.
En la función, use una declaración si para ver si playerWhoShot no es igual a playerLocalPlayer .
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 jugadorlocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)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.
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() thenfireWeapon()endendEn 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.
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 limpiadoDebris:AddItem(laserPart, SHOT_DURATION)-- Reproduce el sonido de disparo del armalocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
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.
En ToolController , navegue a la línea donde se activa el evento remoto de DamageCharacter en la función fireWeapon.
Agregue hitPosition como argumento.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
Servidor
El cliente ahora está enviando un parámetro adicional a través del evento remoto DamageCharacter, por lo que el ServerLaserManager necesita ser ajustado para aceptarlo.
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 personajehumanoid.Health -= LASER_DAMAGEendendDebajo de la función getPlayerToolHandle , crea una función llamada playerFired con tres parámetros: 1> playerFired1>, 4> characterToDamage4> y 7> hitPosition7>.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
La primera prueba será la distancia entre la posición de golpe y la posición de personaje.
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.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10En 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 golpelocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
La segunda verificación involucrará un raycast entre el arma disparada y la posición 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.
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 golpelocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Compruebe si está disparando a través de las paredeslocal toolHandle = getPlayerToolHandle(playerFired)if toolHandle thenlocal rayLength = (hitPosition - toolHandle.Position).Magnitudelocal rayDirection = (hitPosition - toolHandle.Position).Unitlocal raycastParams = RaycastParams.new()raycastParams.FilterDescendantsInstances = {playerFired.Character}local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)-- Si se golpeó una instancia que no era el personaje, ignore el tiroif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendDeclare 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> .
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 personajehumanoid.Health -= LASER_DAMAGEendend
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)