Tutoriais intermediários

Detecção de impacto 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á como lançar um laser do blaster em Criar ferramentas para jogadores e detectar se ele atinge um jogador.

Raycasting para encontrar colisões

Raycasting cria um raio invisível a partir de uma posição inicial em direção a uma direção específica com um comprimento definido. 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 com o qual colidiu.

Raycast de A para B colidindo com uma parede

Encontrar a localização do mouse

Antes que um laser possa ser disparado, você deve primeiro saber onde o jogador está mirando. Isso pode ser encontrado através do raycasting a partir da localização 2D do mouse do jogador na tela, diretamente para frente a partir da câmera, no mundo 3D. O raio irá colidir com o que o jogador está mirando com o mouse.

  1. Abra o script ToolController dentro da ferramenta Blaster de Criar ferramentas para jogadores. Se você ainda não completou aquele tutorial, você pode baixar o modelo Blaster e inseri-lo no StarterPack.

  2. No topo 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
    -- Conectar eventos às funções apropriadas
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. Use a função GetMouseLocation de 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 que 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 Ray a partir da tela no mundo 3D.

  1. Use as propriedades X e Y de mouseLocation como argumentos para a função ViewportPointToRay(). Atribua isso a uma variável chamada screenToWorldRay.


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

É hora de usar a função Raycast para verificar se o raio atinge um objeto. Isso requer uma posição inicial e um vetor de direção: neste exemplo, você usará as propriedades de origem e direção de screenToWorldRay.

O comprimento do vetor de direção determina quão longe o raio viajará. O raio precisa ser tão longo quanto MAX_MOUSE_DISTANCE, portanto, você terá que multiplicar o vetor de direção por MAX_MOUSE_DISTANCE.

  1. Declare uma variável chamada directionVector e atribua a ela o valor de screenToWorldRay.Direction multiplicado por MAX_MOUSE_DISTANCE.


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


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

Informações sobre colisão

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

Propriedade RaycastResultDescrição
InstanceO BasePart ou Terrain célula com a qual o raio intersectou.
PositionOnde 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 da face intersectada. Isso pode ser usado para determinar para onde a face está apontando.

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

  1. Crie uma instrução if para verificar se raycastResult existe.

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

  3. Se raycastResult for nil, então encontre o final do raycast. Calcule a posição 3D do mouse adicionando screenToWorldRay.Origin e directionVector juntos.


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

Dispare em direção ao alvo

Agora que a posição 3D do mouse é conhecida, ela pode ser usada como uma posição alvo para disparar um laser. Um segundo raio pode ser lançado entre a posição da arma do jogador e a posição alvo usando a função Raycast.

  1. Declare uma constante chamada MAX_LASER_DISTANCE no topo do script e atribua-a a 500, ou ao seu alcance escolhido para o blaster a laser.


    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. Esta será a posição alvo para o raycast.


    -- Nenhum objeto foi atingido, então calcule 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 da posição da ferramenta do jogador para a localização alvo.

  1. Declare uma variável chamada targetDirection e calcule o vetor de direção subtraindo a posição da ferramenta de mouseLocation.

  2. Normalize o vetor usando sua propriedade Unit. Isso dará a ele uma magnitude de 1, o que facilita a multiplicação por um comprimento depois.


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


    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    -- A direção para disparar a arma, 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 no seu blaster a laser para garantir que o raycast não colida acidentalmente com o jogador que dispara a arma. Quaisquer peças incluídas na propriedade FilterDescendantsInstances de um objeto RaycastParams serão ignoradas no raycast.

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

  2. Crie uma tabela contendo o personagem local do jogador e atribua-a à propriedade weaponRaycastParams.FilterDescendantsInstances.

  3. Faça o raycast a partir da posição da ferramenta do jogador, em direção ao directionVector. Lembre-se de adicionar weaponRaycastParams como um 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()
-- Calcule um vetor de direção normalizado e multiplique pela distância do laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- A direção para disparar a arma multiplicada por uma distância máxima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorar o personagem do jogador para evitar que eles se machuquem
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 foi atingido pelo raio e um laser pode ser criado entre a arma e o local atingido. Se nada foi retornado, a posição final precisa ser calculada para criar o laser.

  1. Declare uma variável vazia chamada hitPosition.

  2. Use uma instrução if para verificar se weaponRaycastResult tem um valor. Se um objeto foi atingido, atribua weaponRaycastResult.Position a hitPosition.


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Verifique se objetos foram atingidos entre a posição inicial e final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Se weaponRaycastResult não tiver valor, calcule a posição final do raycast adicionando a posição da ferramenta com directionVector. Atribua isso a hitPosition.


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Verifique se objetos foram atingidos entre a posição inicial e final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Calcule 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 dispare toda vez que a ferramenta é ativada.


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

Verifique o objeto atingido

Para descobrir se o objeto atingido pelo laser faz parte do personagem de um jogador ou apenas uma parte da paisagem, você precisará procurar um Humanoid, uma vez que todo personagem possui um.

Primeiro, você precisará encontrar o modelo do personagem. Se uma parte do personagem foi atingida, você não pode supor que o pai do objeto atingido seria o personagem. O laser poderia 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 do modelo do personagem do objeto atingido pelo laser, se existir. Se você encontrar um modelo e ele contiver um humanoide, na maioria dos casos você pode assumir que é um personagem.

  1. Adicione o código destacado abaixo à instrução if de weaponRaycastResult para verificar se um personagem foi atingido.


    -- Verifique se objetos foram atingidos entre a posição inicial e final
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- A instância atingida será um filho de um modelo de personagem
    -- Se um humanoide for encontrado no modelo, então é provavelmente o personagem de um jogador
    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    print("Jogador atingido")
    end
    end
    else
    -- Calcule a posição final com base na distância máxima do laser
    hitPosition = tool.Handle.Position + directionVector
    end

Agora, o blaster a laser deve imprimir Jogador atingido na janela Saída toda vez que a operação de raycast atinge outro jogador.

Teste com múltiplos 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. Veja simulação multi-cliente para instruções.

Em um cliente, teste disparando o outro jogador com a arma clicando neles. "Jogador atingido" deve ser exibido na saída toda vez que um jogador for atingido.

Encontrar a posição do laser

O blaster deve disparar um feixe de luz vermelho em seu alvo. A função para isso ficará dentro de um ModuleScript para que possa ser reutilizada em outros scripts posteriormente. Primeiro, o script precisará encontrar a posição onde o feixe de laser deve ser renderizado.

  1. Crie um ModuleScript chamado LaserRenderer, parentado a StarterPlayerScripts sob StarterPlayer.

  2. Abra o script e renomeie a tabela do módulo para o nome do script LaserRenderer.

  3. Declare uma variável chamada SHOT_DURATION com um valor de 0.15. Esse será o tempo (em segundos) que o laser estará visível.

  4. Crie uma função do LaserRenderer chamada createLaser com dois parâmetros chamados toolHandle e endPosition.


    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- Tempo que o laser estará visível
    -- Crie um feixe de laser de uma posição inicial em direção a uma posição final
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Declare uma variável chamada startPosition e defina a propriedade Position de toolHandle como seu valor. Essa será a posição do blaster a laser do jogador.

  6. Declare uma variável chamada laserDistance e subtraia endPosition de startPosition para encontrar a diferença entre os dois vetores. Use a propriedade Magnitude disso para obter o comprimento do feixe de 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 médio do início e do final do feixe. Use CFrame.lookAt para criar um novo CFrame localizado em startPosition e voltado para endPosition. Multiplique isso por um novo CFrame com um valor de eixo Z de metade da negativa de laserDistance para obter o ponto médio.


    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

Criar a parte do laser

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

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

  2. Defina as seguintes propriedades de laserPart:

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

  4. Adicione a parte ao serviço Debris para que ela seja removida após a 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 o feixe de laser ao serviço Debris para ser removido e limpo
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Agora a função para renderizar o feixe de laser está completa, ela pode ser chamada pelo ToolController.

  1. No topo do script ToolController, declare uma variável chamada LaserRenderer e requer o módulo LaserRenderer 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. No final da função fireWeapon, chame a função createLaser do LaserRenderer usando o punho da ferramenta e hitPosition como argumentos.


    -- Calcule 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 feixe de laser deve ser visível entre a arma e o mouse quando a ferramenta é ativada.

Controlar a taxa de disparo da arma

As armas precisam de um atraso entre cada disparo para impedir que os jogadores causem dano demais em um curto espaço de tempo. Isso pode ser controlado verificando se tempo suficiente passou desde que um jogador disparou pela última vez.

  1. Declare uma variável no topo do ToolController chamada FIRE_RATE. Este será o tempo mínimo entre cada disparo. Dê-lhe um valor à sua escolha; este exemplo usa 0.3 segundos.

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


    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 verificará quanto tempo passou desde o disparo anterior e retornará verdadeiro ou falso.


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- Verifique se tempo suficiente passou desde que o disparo anterior foi feito
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. Dentro da função, declare uma variável chamada currentTime; atribua a ela o resultado da chamada da função tick(). Isso retornará quanto tempo se passou, em segundos, desde 1º de janeiro de 1970 (uma data arbitrária amplamente usada para calcular o tempo).

  5. Subtraia timeOfPreviousShot de currentTime e retorne false se o resultado for menor que FIRE_RATE; caso contrário, retorne true.


    -- Verifique se tempo suficiente passou desde que o disparo anterior foi feito
    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 toda 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 instrução if e chame canShootWeapon para verificar se a arma pode ser disparada.


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end

Quando você testar o blaster, deve descobrir que não importa quão rápido você clique, sempre haverá um pequeno atraso de 0.3 segundos entre cada disparo.

Causar dano ao jogador

Clientes não podem causar dano a outros clientes diretamente; o servidor precisa ser responsável por aplicar dano quando um jogador é atingido.

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

  1. Crie uma Pasta em ReplicatedStorage chamada Events.

  2. Insira um RemoteEvent na pasta Events e nomeie-o DamageCharacter.

  3. No ToolController, crie variáveis no início do script para ReplicatedStorage e na pasta Events.


    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    local tool = script.Parent
    local eventsFolder = ReplicatedStorage.Events
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  4. Substitua a instrução print "Jogador atingido" em fireWeapon por uma linha de Luau para disparar o evento remoto DamageCharacter com a variável characterModel como um argumento.


    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
    -- Calcule a posição final com base na distância máxima do laser
    hitPosition = tool.Handle.Position + directionVector
    end

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

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

  2. Declare uma variável chamada LASER_DAMAGE e defina-a como 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 subtraia LASER_DAMAGE de sua saúde.

  5. Conecte a função damageCharacter ao evento remoto DamageCharacter na pasta Events.


    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    function damageCharacter(playerFired, characterToDamage)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Remover saúde do personagem
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Conectar eventos às funções apropriadas
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Teste o blaster com 2 jogadores iniciando um servidor local. Quando você disparar no outro jogador, a saúde dele diminuirá pelo número atribuído a LASER_DAMAGE.

Renderizar feixes de laser de outros jogadores

Atualmente, o feixe de laser é criado pelo cliente disparando a arma, então apenas ele poderá ver o feixe de laser.

Se o feixe de laser fosse criado no servidor, então todos poderiam vê-lo. No entanto, haveria um pequeno atraso entre o cliente atirando a arma e o servidor recebendo a informação sobre o disparo. Isso significaria que o cliente atirando a arma veria um atraso entre quando ativam a arma e quando vêem o feixe de laser; a arma pareceria lenta como resultado.

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

Cliente atirador

Primeiro, o cliente precisa informar ao servidor que disparou um laser e fornecer a posição final.

  1. Insira um RemoteEvent na pasta Events em ReplicatedStorage e nomeie-o LaserFired.

  2. Localize a função fireWeapon no script ToolController. No final da função, dispare o evento remoto LaserFired usando hitPosition como um 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 de que o cliente disparou e informar a todos os clientes a posição inicial e final do feixe de 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 playerFired e endPosition.

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


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

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

O servidor precisa da posição inicial do laser. Isso poderia ser enviado do cliente, mas é melhor evitar confiar no cliente sempre que possível. A posição do punho da arma do personagem será a posição inicial, portanto o servidor pode encontrá-la a partir daí.

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

  2. Use o seguinte código para pesquisar o personagem do jogador em busca da arma e retornar o objeto do punho.


    local LASER_DAMAGE = 10
    -- Encontrar o punho 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
    -- Notificar todos os clientes de que um laser foi disparado para que 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 aos clientes. Isso inclui o jogador que disparou o laser (para que o cliente dessa jogador não renderize o laser duas vezes), o punho do blaster (que serve como uma posição inicial para o laser) 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 toolHandle.

  2. Se toolHandle existir, dispare o evento LaserFired para todos os clientes usando playerFired, toolHandle e endPosition como argumentos.


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

Renderizar nos clientes

Agora que FireAllClients foi chamado, cada cliente receberá um evento do servidor para renderizar um feixe de laser. Cada cliente pode reutilizar o módulo LaserRenderer de antes para renderizar o feixe de laser usando a posição do punho da ferramenta e o valor da posição final enviado pelo servidor. O jogador que disparou o feixe de laser inicialmente deve ignorar este evento, senão verá 2 lasers.

  1. Crie um LocalScript em StarterPlayerScripts chamado ClientLaserManager.

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

  3. Crie uma função chamada createPlayerLaser com os parâmetros playerWhoShot, toolHandle e endPosition.

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

  5. Na função, use uma instrução if para verificar se playerWhoShot não é igual ao LocalPlayer.

  6. Dentro da instrução if, chame a função createLaser do módulo LaserRenderer 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 lados diferentes do seu monitor para que você possa ver ambas as janelas ao mesmo tempo. Quando você disparar em um cliente, deverá ver o laser no outro cliente.

Efeitos sonoros

O efeito sonoro de disparo atualmente só toca no cliente que está disparando o projétil. Você precisará mover o código para tocar 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 Ativar.


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. No final da função createLaser em LaserRenderer, declare uma variável chamada shootingSound e use o método FindFirstChild() de toolHandle para verificar se o som de Ativar existe.

  3. Use uma instrução if para verificar se shootingSound existe; se existir, chame sua função Play().


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

Proteger remotes usando validação

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

Na sua forma atual, o evento remoto DamageCharacter é muito vulnerável a ataques. Hackers poderiam usar esse evento para causar dano a qualquer jogador que desejassem, sem atingi-los.

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

  • Verificar se a distância entre o jogador e a posição atingida pelo laser está dentro de um determinado limite.
  • Realizar um raycast entre a arma que disparou o laser e a posição atingida para garantir que o disparo foi possível e não atravessou nenhuma parede.

Cliente

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

  1. No ToolController, vá até a linha onde o evento remoto DamageCharacter é disparado na função fireWeapon.

  2. Adicione hitPosition como um argumento.


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

Servidor

O cliente agora está 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. Abaixo da função getPlayerToolHandle, crie uma função chamada isHitValid com três parâmetros: playerFired, characterToDamage e hitPosition.


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

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

  1. Declare uma variável chamada MAX_HIT_PROXIMITY no topo do script e atribua a ela um valor de 10. Essa será a distância máxima permitida entre a posição atingida e o personagem. Uma tolerância é necessária porque o personagem pode ter se movido ligeiramente 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, calcule a distância entre o personagem e a posição atingida. Se a distância for maior que MAX_HIT_PROXIMITY, então retorne false.


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Validar a distância entre o personagem atingido e a posição atingida
    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 atingida. Se o raycast retornar um objeto que não seja o personagem, você pode supor que o disparo não foi válido, uma vez que algo estava bloqueando o tiro.

  1. Copie o código abaixo para realizar essa verificação. Retorne true no final da função: se alcançar o final, todas as verificações foram concluídas com sucesso.


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Validar a distância entre o personagem atingido e a posição atingida
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 then
    return false
    end
    -- Verificar se o disparo atravessou 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, então ignore o disparo
    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 validShot. Atribua a ela o resultado de uma chamada para a função isHitValid com três argumentos: playerFired, characterToDamage e hitPosition.

  3. Na instrução if abaixo, adicione um operador and para verificar se validShot é true.


    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 damageCharacter está mais seguro e impedirá a maioria dos jogadores de abusá-lo. Observe que alguns jogadores maliciosos frequentemente encontrarão maneiras de contornar a validação; manter eventos remotos seguros é um esforço contínuo.

Seu blaster a laser agora está completo, com um sistema básico de detecção de impacto usando raycasting. Experimente o tutorial Detectando Entrada do Usuário para descobrir como você pode adicionar uma ação de recarga ao seu blaster a laser, ou crie um mapa divertido e experimente seu blaster a laser com outros jogadores!

Código final

ToolController


local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
local tool = script.Parent
local eventsFolder = ReplicatedStorage.Events
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local FIRE_RATE = 0.3
local timeOfPreviousShot = 0
-- Verifique se tempo suficiente passou desde que o disparo anterior foi feito
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 da localização 2D do mouse
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- O vetor de direção unitário do raio multiplicado por uma distância máxima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast da origem do raio para direção
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Retorne o ponto 3D de interseção
return raycastResult.Position
else
-- Nenhum objeto foi atingido, então calcule a posição no final do raio
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calcule um vetor de direção normalizado e multiplique pela distância do laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- A direção para disparar a arma, multiplicada por uma distância máxima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorar o personagem do jogador para evitar que eles se machuquem
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Verifique se objetos foram atingidos entre a posição inicial e final
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- A instância atingida será um filho de um modelo de personagem
-- Se um humanoide for encontrado no modelo, então é provavelmente 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
-- Calcule 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)

LaserRenderer


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

ServerLaserManager


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Encontrar o punho 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)
-- Validar a distância entre o personagem atingido e a posição atingida
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Verificar se o disparo atravessou 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, então ignore o disparo
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Notificar todos os clientes de que um laser foi disparado para que 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
-- Conectar eventos às funções apropriadas
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

ClientLaserManager


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts:WaitForChild("LaserRenderer"))
local eventsFolder = ReplicatedStorage.Events
-- 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)
©2026 Roblox Corporation, Roblox, o logotipo Roblox e Powering Imagination estão entre nossas marcas registradas e não registradas nos EUA e em outros países.