Detecção de Hit com Lasers

*Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

Neste Tutorial, você aprenderá a ativar um laser a partir do blaster em Ferramentas de Criação de Jogador e detectar se ele atinge ou não um jogador.

Raiocasting para encontrar colisões

Raycasting cria um raio invisível a partir de uma posição de início para uma direção definida com uma longitude definida. Se o raio colidir com objetos ou terreno em seu caminho, ele retornará informações sobre a colisão, como a posição e o objeto que colidiu.

Raycast de A para B colidindo com uma parede

Encontrando o Local do Mouse

Antes de um laser ser ativado, você deve primeiro saber onde o jogador está mirando. Isso pode ser encontrado por raycasting a partir do local de mouse 2D do jogador na tela diretamente para a frente da câmera para o mundo do jogo. O laser colidirá com o que o jogador estiver mirando com o mouse.

  1. Abra o script ToolController dentro do script Blaster da ferramenta Criando Ferramentas de Jogador. Se você ainda não completou esse tutorial, você pode baixar o modelo Blaster e inseri-lo no StarterPack.

  2. Na parte superior do script, declare uma constante chamada MAX_MOUSE_DISTANCE com um valor de 1000 .

  3. Crie uma função chamada 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
    -- Conecte eventos às funções apropriadas
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. Use a função GetMouseLocation da UserInputService para obter a localização 2D do mouse do jogador na tela. Atribua isso a uma variável chamada mouseLocation .


    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    end

Agora, a localização 2D do mouse é conhecida, suas propriedades X e Y podem ser usadas como parâmetros para a função Camera:ViewportPointToRay(), que cria um 1> Datatype.Ray1> do mouse na mundodo jogo 3D.

  1. Use as propriedades X e Y de mouseLocation como argumentos para a função 1> Class.Camera:ViewportPointToRay()|ViewportPointToRay() . Atribua isso a uma variável chamada4> screenToWorldRay4>.


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crie um raio a partir do local do mouse 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    end

É hora de usar a função Raycast para verificar se o raio atingiu um Objeto. Isso requer uma posição de início e um vetor de direção: neste exemplo, você usará as propriedades de início e direção de screenToWorldRay .

A longitude do vetor de direção determina a distância que o raio irá percorrer. O raio precisa ser tão longo quanto o MAX_MOUSE_DISTANCE, então você terá que multiplicar o vetor de direção por MAX_MOUSE_DISTANCE.

  1. Declare uma variável chamada direçãoVector e atribua-lhe o valor de screenToWorldRay.Direction multiplicado por MAX_MOUSE_DISTANCE.


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crie um raio a partir do 2D mouseLocation
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- O vetor de direção da unidade do raio multiplicado por uma distância máxima
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
  2. Chame a função de espaço de trabalho Raycast , passando a propriedade origem de screenToWorldRay como primeiro argumento e 1> durationVector1> como segundo. Atribua isso a uma variável chamada 4> raycastResult4>.


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Crie um raio a partir do 2D mouseLocation
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- O vetor de direção da unidade do raio multiplicado por uma distância máxima
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
    -- Raio de corte a partir da origem do raio em direção à sua direção
    local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)

Informações de Colisão

Se a operação de raycast encontrar um objeto atingido pelo ray, ele retornará uma RaycastResult, que contém informações sobre a colisão entre o ray e o Objeto.

Propriedade RaycastResultDescrição
InstânciaA BasePart ou Terrain célula que o raio interseccionou.
PosiçãoOnde a interseção ocorreu; geralmente um ponto diretamente na superfície de uma parte ou terreno.
MaterialO material no ponto de colisão.
NormalO vetor normal do rosto interseccionado. Isso pode ser usado para determinar em qual direção o rosto está apontando.

A propriedade Posição será a posição do objeto que o mouse está hovering over. Se o mouse não estiver hovering over nenhum objeto dentro de uma distância de MAX_MOUSE_DISTANCE , raycastResult será nil.

  1. Crie uma if statement para verificar se raycastResult existe.

  2. Se raycastResult tiver um valor, retorne sua propriedade Position .

  3. Se raycastResult é nil, então encontre o fim do raycast. Calcular a posição 3D do mouse adicionando screenToWorldRay.Origin e directionVector juntos.


local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Crie um raio a partir do 2D mouseLocation
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- O vetor de direção da unidade do raio multiplicado por uma distância máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raio de corte a partir da origem do raio em direção à sua direção
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Retorne o ponto de interseção 3D
return raycastResult.Position
else
-- Nenhum objeto foi atingido, então cálculo a posição no final do raio
return screenToWorldRay.Origin + directionVector
end
end

Ativando em direção ao alvo

Agora que a posição do mouse 3D é conhecida, ele pode ser usado como uma posição de alvo para disparar um laser para. Um segundo raio pode ser cast entre a arma do jogador e a posição de alvo usando a função Raycast .

  1. Declare uma constante chamada MAX_LASER_DISTANCE no topo do script e atribua-a a 500 ou a sua escolha de alcance para o laser Blaster.


    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  2. Crie uma função chamada fireWeapon sob a função getWorldMousePosition.

  3. Chame getWorldMousePosition e atribua o resultado a uma variável chamada mousePosition . Essa será a posição de destino para o raycast.


    -- Nenhum objeto foi atingido, então cálculo a posição no final do raio
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end

Desta vez, o vetor de direção para a função raycast representará a direção a partir da posição da ferramenta do jogador até a localização do alvo.

  1. Declare uma variável chamada direção de destino e cálculo do vértice de direção subtrair a posição da ferramenta da posição da ferramenta mouseLocation .

  2. Normalize o vetor usando sua propriedade Unidade . Isso o torna uma dimensão de 1, o que facilita multiplicar por uma lateralmente.


    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- Calcula um vetor de direção normalizado e multiplica pela distância do laser
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. Declare uma variável chamada direçãoVector e atribua a ela o targetDirection multiplicado por MAX_LASER_DISTANCE.


    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    -- A direção para a qual a arma deve ser disparada, multiplicada por uma distância máxima
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

Um objeto RaycastParams pode ser usado para armazenar parâmetros adicionais para a função raycast. Ele será usado em seu laser blaster para garantir que o raycast não acidentalmente colida com o jogador que está atirando a arma. Quaisquer peças incluídas na propriedade Datatype.RaycastParams.FilterDescendantsInstances|FilterDescendantsInst

  1. Continue a função fireWeapon e declare uma variável chamada weaponRaycastParams . Atribua um novo objeto RaycastParams a ele.

  2. Crie uma tabela que contém o personagem local do jogador e atribua-o à propriedade weaponRaycastParams.FilterDescendantsInstances.

  3. Raycast a partir da posição da alça da ferramenta do jogador, em uma direção para o directionVector . Lembre-se de adicionar weaponRaycastParams como argumento desta vez. Atribua isso a uma variável chamada 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()
-- Calcula um vetor de direção normalizado e multiplica pela distância do laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- A direção para atirar a arma se multiplicou por uma distância máxima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignore o personagem do jogador para impedi-lo de se machucar
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
end

Finalmente, você precisará verificar se a operação de raycast retornou um valor. Se um valor for retornado, um objeto será atingido pelo ray e um laser pode ser criado entre a arma e o local de acerto. Se nada for retornado, a posição final precisa ser calculada para criar o laser.

  1. Declare uma variável vazia chamada hitPosition .

  2. Use uma declaração se para verificar se weaponRaycastResult tem um valor. Se um objeto for atingido, atribua weaponRaycastResult.Position a 1> hitPosition1>.


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Verifique se quaisquer objetos foram atingidos entre a posição de início e fim
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Se weaponRaycastResult não tiver valor, calcular a posição final do raycast adicionando juntos a posição da alça de ferramenta com o durationVector. Atribua isso à 1> hitPosition1>.


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Verifique se quaisquer objetos foram atingidos entre a posição de início e fim
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Calcula a posição final com base na distância máxima do laser
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. Navegue até a função toolActivated e chame a função fireWeapon para que o laser atire cada vez que a ferramenta for ativada.


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

Verificando o Objeto Hit

Para encontrar se o objeto atingido pelo laser faz parte do personagem de um jogador ou apenas uma paisagem, você precisará procurar um Humanoid, como todos os personagens têm um.

Primeiro, você precisará encontrar o modelo de personagem . Se uma parte do personagem for atingida, você não pode assumir que a parte pai do objeto atingido seria o personagem. O laser pode ter atingido uma parte do corpo, um acessório ou uma ferramenta, todos os quais estão localizados em diferentes partes da hierarquia do personagem.

Você pode usar FindFirstAncestorOfClass para encontrar um ancestral de modelo de personagem do objeto atingido pelo laser, se houver um. Se você encontrar um modelo e ele contiver um humanóide, na maioria dos casos, você pode assumir que é um personagem.

  1. Adicione o código destacado abaixo à weaponRaycastResult se a declaração for verificada para ver se um personagem foi atingido.


    -- Verifique se quaisquer objetos foram atingidos entre a posição de início e fim
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- A instância atingida será uma filha de um modelo de personagem
    -- Se um humanóide for encontrado no modelo, é provável que seja o personagem de um jogador
    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    print("Player hit")
    end
    end
    else
    -- Calcula a posição final com base na distância máxima do laser
    hitPosition = tool.Handle.Position + directionVector
    end

Agora o laser deve imprimir Player hit para a janela de saída sempre que a operação de laser atingir outro jogador.

Testando com vários jogadores

Dois jogadores são necessários para testar se o raycast da arma está encontrando outros jogadores, então você precisa iniciar um servidor local.

  1. Selecione a aba Teste no Studio.

  2. Certifique-se de que o menu suspenso de jogadores está definido como "2 Jogadores" e clique no botão Iniciar para iniciar um servidor local com 2 clientes. Três janelas aparecerão. A primeira janela será o servidor local, as outras janelas serão os clientes para Jogador1 e Jogador2.

  3. Em um cliente, teste a atirar no outro jogador com a arma clicando emle. "Player hit" deve ser exibido na saída cada vez que um jogador é atingido.

Você pode saber mais sobre a aba Teste aqui.

Encontrando a posição do laser

O blaster deve atirar um raio vermelho de luz em seu alvo. A função para isso estará dentro de um ModuleScript para que possa ser reutilizado em outros scripts mais tarde. Primeiro, o script precisará encontrar a posição onde o laser deve ser renderizado.

  1. Crie um ModuleScript chamado LaserRender, que é o pai de StarterPlayerScripts sob StarterPlayer.

  2. Abra o script e renomeie a tabela de módulos para o nome do script LaserRender .

  3. Declare uma variável chamada DURAÇÃO_DE_DISPARO com um valor de 0.15 . Essa será a quantidade de tempo (em segundos) que o laser está visível.

  4. Crie uma função de LaserRender chamada criarLaser com dois parâmetros chamados toolHandle e endPosition .


    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- Tempo que o laser está visível para
    -- Crie um laser a partir de uma posição de início para uma posição de fim
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Declare uma variável chamada startPosition e defina a propriedade Position do toolHandle como seu valor. Essa será a posição do laser do jogador.

  6. Declare uma variável chamada laserDistance e subtrai endPosition de startPosition para encontrar a diferença entre os dois vértices. Use a propriedade 1> Magnitude1> desta para obter a longitude do feixe laser.


    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. Declare uma variável laserCFrame para armazenar a posição e orientação do feixe de laser. A posição precisa ser o ponto central do início e fim do feixe. Use CFrame.lookAt para criar uma nova Datatype.CFrame


    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

Criando a Laser Part

Agora que você sabe onde criar um feixe laser, você precisa adicionar o próprio feixe. Isso pode ser feito facilmente com uma peça Neon.

  1. Declare uma variável laserPart e atribua a ela uma nova instância Part.

  2. Definir as seguintes propriedades de laserPart :

    1. Tamanho : Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame : laserCFrame
    3. Anchored : true
    4. Pode Colidir : falso
    5. Cor: : Color3.fromRGB(225, 0, 0) (uma cor vermelha forte)
    6. Material : Enum.Material.Neon
  3. Parente laserPart para Workspace .

  4. Adicione a peça ao serviço Debris para que ela seja removida depois da quantidade de segundos na variável 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
    -- Adicione laser ao serviço Debris para ser removido e limpo
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Agora a função para renderizar o laser é completa, pode ser chamada pelo Controlador de Ferramentas .

  1. Na parte superior do script ToolController , declare uma variável chamada LaserRender e requerir o LaserRenderModuleScript localizado em PlayerScripts.


    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    local tool = script.Parent
  2. Na parte inferior da função fireWeapon, chame a função LaserRender createLaser usando o cabo de ferramenta e hitPosition como argumentos.


    -- Calcula a posição final com base na distância máxima do laser
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. Teste a arma clicando no botão Play. Um raio laser deve ser visível entre a arma e o mouse quando a ferramenta for ativada.

Controlando a taxa de tiro da arma

As armas precisam de um atraso entre cada tiro para impedir que os jogadores causem muito dano em um curto período de tempo. Isso pode ser controlado verificando se o tempo suficiente passou desde que um jogador último disparou.

  1. Declare uma variável na parte superior do ToolController chamado FIRE_RATE . Isso será o tempo mínimo entre cada tiro. Dê um valor à sua escolha; este exemplo usa 0.3 segundos.

  2. Declare outra variável abaixo chamada timeOfPreviousShot com um valor de 0 . Isso armazena a última vez que o jogador atirou e será atualizado com cada tiro.


    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. Crie uma função chamada canShootWeapon sem parâmetros. Essa função olhará para quanto tempo passou desde o último tiro e retornará true ou false.


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- Verifique se o tempo passou desde que o tiro anterior foi disparado
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. Dentro da função, declare uma variável chamada currentTime ; atribua-lhe o resultado da chamada da função Global.RobloxGlobals.tick()| tick() . Isso retorna o tempo que elapsed, em segundos, desde o 1º de janeiro de 1970 (uma data arbitrária amplamente usada para calcular o tempo).

  5. Subtrai o timeOfPreviousShot de currentTime e retorne false se o resultado for menor que 1> FIRE_RATE1>; caso contrário, retorne 4> true4> .


    -- Verifique se o tempo passou desde que o tiro anterior foi disparado
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. No final da função fireWeapon, atualize timeOfPreviousShot a cada vez que a arma for disparada usando tick.


    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. Dentro da função toolActivated, crie uma declaração se e chame canShootWeapon para verificar se a arma pode ser disparada.


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

Ao testar o blaster, você deve encontrar que, independentemente de quão rápido você clique, sempre haverá um pequeno atraso de 0,3 segundos entre cada tiro.

Dano ao Jogador

Os clientes não podem danificar outros clientes diretamente; o servidor precisa ser responsável por emitir dano quando um jogador for atingido.

Os clientes podem usar um RemoteEvent para dizer ao servidor que um personagem foi atingido. Estes devem ser armazenados em Armazenamento Replicado , onde eles são visíveis tanto para o cliente quanto para o servidor.

  1. Crie um Pasta em ReplicatedStorage chamado Eventos .

  2. Insira um Evento Remoto na pasta Eventos e nomeie-o DamageCharacter .

  3. In ToolController , create variables at the start of the script for ReplicatedStorage and the Events folder.


    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. Substitua a declaração de impressão "Player hit" em fireWeapon com uma linha de Lua para disparar o evento remoto DamageCharacter com a variável 1> characterModel1> 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
    -- Calcula a posição final com base na distância máxima do laser
    hitPosition = tool.Handle.Position + directionVector
    end

O servidor precisa dar dano ao jogador que foi atingido quando o evento é disparado.

  1. Insira um Script em ServerScriptService e nomeie-o ServerLaserManager.

  2. Declare uma variável chamada LASER_DAMAGE e definível para 10 , ou um valor de sua escolha.

  3. Crie uma função chamada damageCharacter com dois parâmetros chamados playerFired e characterToDamage .

  4. Dentro da função, encontre o Humanoid do personagem e subtrai LASER_DAMAGE de sua saúde.

  5. Conecte a função damageCharacter à DamageCharacter evento remoto na pasta 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
    -- Remover saúde do personagem
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Conecte eventos às funções apropriadas
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Teste o blaster com 2 jogadores iniciando um servidor local. Quando você atira no outro jogador, sua saúde diminuirá pelo número atribuído a LASER_DAMAGE .

Renderizando Lasers de Outros Jogadores

Atualmente, o feixe laser é criado pelo cliente que está disparando a arma, então apenas eles poderão ver o feixe laser.

Se o feixe de laser foi criado no servidor, então todos poderiam vê-lo. No entanto, haveria um pequeno atraso entre o cliente que atira a arma e o servidor que recebe a informação sobre o tiro. Isso significaria que o cliente que atira a arma veria um atraso entre quando eles ativam a arma e quando eles vêem o laser; a arma ficaria com lag ao resultado.

Para resolver este problema, todos os clientes criarão seus próprios raios de laser. Isso significa que o cliente que atirar na arma verá o laser instantaneamente. Outros clientes experimentarão um pequeno atraso entre quando outro jogador atira e um laser aparece. Este é o melhor cenário de caso: não há como comunicar um cliente de laser a outros clientes mais rápido.

Cliente do Tirador

Primeiro, o cliente precisa dizer ao servidor que ele disparou um laser e fornecer a posição de fim.

  1. Insira um Evento Remoto na pasta de Eventos no ReplicatedStorage e nomeie-o LaserFired.

  2. Localize a função fireWeapon na script ToolController . No final da função, fire o evento remoto LaserFired usando 1> hitPosition1> como argumento.


    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    eventsFolder.LaserFired:FireServer(hitPosition)
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end

O Servidor

O servidor agora deve receber o evento que o cliente disparou e dizer a todos os clientes a posição de início e fim do laser para que eles também possam renderizá-lo.

  1. No script ServerLaserManager, crie uma função chamada playerFiredLaser acima de damageCharacter com dois parâmetros chamados 2> playerFired2> e 5> endPosition5>.

  2. Conecte a função ao evento remoto LaserFired .


    -- Informe a todos os clientes que um laser foi disparado para que eles possam exibir o laser
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- Conecte eventos às funções apropriadas
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

O servidor precisa da posição de início do laser. Isso pode ser enviado do cliente, mas é melhor evitar confiar no cliente onde possível. A posição da alça da arma do personagem será a posição de início, para que o servidor possa encontrá-lo a partir de lá.

  1. Crie uma função obterPlayerToolHandle acima da função playerFiredLaser com um parâmetro chamado player.

  2. Use o seguinte código para procurar o personagem do jogador pela arma e retornar o Objetode cabo.


    local LASER_DAMAGE = 10
    -- Encontre a alça da ferramenta que o jogador está segurando
    local function getPlayerToolHandle(player)
    local weapon = player.Character:FindFirstChildOfClass("Tool")
    if weapon then
    return weapon:FindFirstChild("Handle")
    end
    end
    -- Informe a todos os clientes que um laser foi disparado para que eles possam exibir o laser
    local function playerFiredLaser(playerFired, endPosition)

O servidor agora pode chamar FireAllClients no evento remoto LaserFired para enviar as informações necessárias para renderizar o laser para os clientes. Isso inclui o jogador que disparou o laser (para que o cliente para aquele jogador não renderize o laser duas vezes) e a posição final do laser.

  1. Na função playerFiredLaser, chame a função getPlayerToolHandle com playerFired como argumento e atribua o valor a uma variável chamada 1> toolHandle1>.

  2. Se toolHandle existe, fire the LaserFired event for all client using playerFired , toolHandle e 1> endPosition1> como argumentos.


    -- Informe a todos os clientes que um laser foi disparado para que eles possam exibir o laser
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

Renderizando nos Clientes

Agora FireAllClients foi chamado, cada cliente receberá um evento do servidor para renderizar um laser beam. Cada cliente pode reutilizar o módulo LaserRenderer do módulo de ferramentas para renderizar o laser beam usando a posição e o endereço de posição enviados pelo servidor. O jogador que disparou o laser beam na primeira vez deve ignorar este evento caso contrário eles'll see 2 lasers.

  1. Crie um LocalScript em StarterPlayerScripts chamado ClientLaserManager.

  2. Dentro do script, requer o módulo LaserRender .

  3. Crie uma função chamada criarJogadorLaser com os parâmetros playerWhoShot, toolHandle e 1> endPosition1>.

  4. Conecte a função ao evento remoto LaserFired na pasta Eventos.

  5. Na função, use uma declaração se para verificar se playerWhoShot não é igual a playerLocalPlayer .

  6. Dentro da declaração if, chame a função createLaser do módulo LaserRender usando toolHandle e endPosition como argumentos.


    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))
    local eventsFolder = ReplicatedStorage.Events
    -- Exibir o laser de outro jogador
    local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
    if playerWhoShot ~= Players.LocalPlayer then
    LaserRenderer.createLaser(toolHandle, endPosition)
    end
    end
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. Teste o blaster com 2 jogadores iniciando um servidor local. Posicione cada cliente em diferentes lados de sua tela para que você possa ver ambas as janelas ao mesmo tempo. Quando você atira em um cliente, você deve ver o laser no outro cliente.

Efeitos Sonoros

O efeito de som de tiro só é executado no cliente que está atirando o projétil. Você precisará mover o código para reproduzir o som para que outros jogadores também possam ouvi-lo.

  1. No script ToolController , vá até a função toolActivated e remova a linha que toca o som de Ativação.


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. Na parte inferior da função createLaser em LaserRender , declare uma variável chamada shootingSound e use o método 1> Class.Instance:FindFirstChild()|FindFirstChild()1> do método 4> toolHandle4> para verificar se o som 7> Ativar7> está ativado.

  3. Use uma declaração se para verificar se shootingSound existe; se for, chame sua função Jogar .


    laserPart.Parent = workspace
    -- Adicione laser ao serviço Debris para ser removido e limpo
    Debris:AddItem(laserPart, SHOT_DURATION)
    -- Reproduz o som de tiro da arma
    local shootingSound = toolHandle:FindFirstChild("Activate")
    if shootingSound then
    shootingSound:Play()
    end
    end

Protegendo Controles Remotos usando Validação

Se o servidor não estiver verificando dados de pedidos entrantes, um hacker pode abusar de funções e eventos remotos e usá-los para enviar valores falsos para o servidor. É importante usar validação do lado do servidor para evitar isso.

Em sua forma atual, o evento remoto DamageCharacter é muito vulnerável a ataques. Hackers podem usar este evento para danificar qualquer jogador que eles quiserem no jogo sem atirar neles.

A validação é o processo de verificar se os valores enviados ao servidor são realistas. Neste caso, o servidor precisará:

  • Verifique se a distância entre o jogador e a posição atingida pelo laser está dentro de um determinado limite.
  • Raycast entre a arma que disparou o laser e a posição de acerto para garantir que o tiro fosse possível e não passasse por nenhuma parede.

Cliente

O cliente precisa enviar o servidor a posição atingida pelo raycast para que ele possa verificar a distância que é realista.

  1. In ToolController , navigate to the line where the DamageCharacter remote event is fired in the fireWeapon function.

  2. Adicionar hitPosition como argumento.


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

Servidor

O cliente está agora enviando um parâmetro extra através do evento remoto DamageCharacter, então o ServerLaserManager precisa ser ajustado para aceitá-lo.

  1. No script ServerLaserManager , adicione um parâmetro hitPosition à função damageCharacter.


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Remover saúde do personagem
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Embaixo da função getPlayerToolHandle, crie uma função chamada isHitValid com três parâmetros: playerFired, 1> characterToDamage1> e 4> hitPosition4>.


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

A primeira verificação será a distância entre a posição de hit e o personagem hit.

  1. Declare uma variável chamada MAX_HIT_PROXIMITY na parte superior do script e atribua-lhe um valor de 10 . Isso será a distância máxima permitida entre o hit e o personagem. Um tolerância é necessária, pois o personagem pode ter se movido um pouco desde que o cliente disparou o evento.


    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    local MAX_HIT_PROXIMITY = 10
  2. Na função isHitValid, calcular a distância entre o personagem e a posição de acerto. Se a distância for maior que MAX_HIT_PROXIMITY então retornar falso .


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Verifique a distância entre o personagem atingido e a posição de acerto
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

A segunda verificação envolverá um raycast entre a arma disparada e a posição de hit. Se o raycast retornar um objeto que não seja o personagem, você pode assumir que o tiro não foi válido, pois algo estava bloqueando o tiro.

  1. Copie o código abaixo para executar este verificar / conferir. Retorne verdadeiro no final da função: se ele chegar ao terminar/parar/sair, todos os testes passaram.


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Verifique a distância entre o personagem atingido e a posição de acerto
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 then
    return false
    end
    -- Verifique se você está atirando através das 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)
    -- Se uma instância foi atingida que não era o personagem, ignore o tiro
    if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
    return false
    end
    end
    return true
    end
  2. Declare uma variável na função damageCharacter chamada válidoShot . Atribua ao resultado de uma chamada à função isHitValid com três argumentos: 1> playerFired1> , 4> characterToDamage4> e 7> hitPosition7>.

  3. Na declaração abaixo, adicione um operador e para verificar se validShot é verdadeiro .


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
    if humanoid and validShot then
    -- Remover saúde do personagem
    humanoid.Health -= LASER_DAMAGE
    end
    end

Agora, o evento remoto de dano é mais seguro e impedirá que a maioria dos jogadores abusar dela. Observe que alguns jogadores maliciosos geralmente encontrarão maneiras ao redor da validação; manter os eventos remotos seguros é um esforço contínuo.

Seu laser de fusão ainda está completo, com um sistema de detecção de hit básico usando raycasting. Tente o tutorial Detectando a entrada do usuário para descobrir como você pode adicionar uma ação de recarregamento ao seu laser de fusão, ou criar um mapa de jogo divertido e experimentar seu laser de fusão com outros jogadores!

Código Final

Controlador de Ferramentas


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
-- Verifique se o tempo passou desde que o tiro anterior foi disparado
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()
-- Crie um raio a partir do local do mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- O vetor de direção da unidade do raio multiplicado por uma distância máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raio de chato da direção da fonte
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Retorne o ponto de interseção 3D
return raycastResult.Position
else
-- Nenhum objeto foi atingido, então cálculo a posição no final do raio
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calcula um vetor de direção normalizado e multiplica pela distância do laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- A direção para a qual a arma deve ser disparada, multiplicada por uma distância máxima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignore o personagem do jogador para impedi-lo de se machucar
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Verifique se quaisquer objetos foram atingidos entre a posição de início e fim
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- A instância atingida será uma filha de um modelo de personagem
-- Se um humanóide for encontrado no modelo, é provável que seja o personagem de um jogador
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
-- Calcula a posição final com base na distância máxima do laser
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)

LaserRender


local LaserRenderer = {}
local Debris = game:GetService("Debris")
local SHOT_DURATION = 0.15 -- Tempo que o laser está visível para
-- Crie um laser a partir de uma posição de início para uma posição de fim
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
-- Adicione laser ao serviço Debris para ser removido e limpo
Debris:AddItem(laserPart, SHOT_DURATION)
-- Reproduz o som de tiro da arma
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRenderer

Gerenciador de Laser do Servidor


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Encontre a alça da ferramenta que o jogador está segurando
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)
-- Verifique a distância entre o personagem atingido e a posição de acerto
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Verifique se você está atirando através das 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)
-- Se uma instância foi atingida que não era o personagem, ignore o tiro
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Informe a todos os clientes que um laser foi disparado para que eles possam exibir o laser
local function playerFiredLaser(playerFired, endPosition)
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
end
end
function damageCharacter(playerFired, characterToDamage, hitPosition)
local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
if humanoid and validShot then
-- Remover saúde do personagem
humanoid.Health -= LASER_DAMAGE
end
end
-- Conecte eventos às funções apropriadas
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

Cliente Laser Gerenciador


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts:WaitForChild("LaserRenderer"))
local eventsFolder = ReplicatedStorage.Events
-- Exibir o laser de outro jogador
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)