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.
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.
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.
Na parte superior do script, declare uma constante chamada MAX_MOUSE_DISTANCE com um valor de 1000 .
Crie uma função chamada 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-- Conecte eventos às funções apropriadastool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)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.Parentlocal MAX_MOUSE_DISTANCE = 1000local 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.
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 2Dlocal 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.
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 mouseLocationlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- O vetor de direção da unidade do raio multiplicado por uma distância máximalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCEChame 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 mouseLocationlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- O vetor de direção da unidade do raio multiplicado por uma distância máximalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Raio de corte a partir da origem do raio em direção à sua direçãolocal 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 RaycastResult | Descrição |
---|---|
Instância | A BasePart ou Terrain célula que o raio interseccionou. |
Posição | Onde a interseção ocorreu; geralmente um ponto diretamente na superfície de uma parte ou terreno. |
Material | O material no ponto de colisão. |
Normal | O 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.
Crie uma if statement para verificar se raycastResult existe.
Se raycastResult tiver um valor, retorne sua propriedade Position .
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 .
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.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Crie uma função chamada fireWeapon sob a função getWorldMousePosition.
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 raioreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal 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.
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 .
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 laserlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendDeclare 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áximalocal directionVector = targetDirection * MAX_LASER_DISTANCEend
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
Continue a função fireWeapon e declare uma variável chamada weaponRaycastParams . Atribua um novo objeto RaycastParams a ele.
Crie uma tabela que contém o personagem local do jogador e atribua-o à propriedade weaponRaycastParams.FilterDescendantsInstances.
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.
Declare uma variável vazia chamada hitPosition .
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 fimlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendSe 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 fimlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Calcula a posição final com base na distância máxima do laserhitPosition = tool.Handle.Position + directionVectorendendNavegue 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.
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 fimlocal hitPositionif weaponRaycastResult thenhitPosition = 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 jogadorlocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Player hit")endendelse-- Calcula a posição final com base na distância máxima do laserhitPosition = tool.Handle.Position + directionVectorend
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.
Selecione a aba Teste no Studio.
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.
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.
Crie um ModuleScript chamado LaserRender, que é o pai de StarterPlayerScripts sob StarterPlayer.
Abra o script e renomeie a tabela de módulos para o nome do script LaserRender .
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.
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 fimfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererDeclare uma variável chamada startPosition e defina a propriedade Position do toolHandle como seu valor. Essa será a posição do laser do jogador.
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.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendDeclare 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.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal 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.
Declare uma variável laserPart e atribua a ela uma nova instância Part.
Definir as seguintes propriedades de laserPart :
- Tamanho : Vector3.new(0.2, 0.2, laserDistance)
- CFrame : laserCFrame
- Anchored : true
- Pode Colidir : falso
- Cor: : Color3.fromRGB(225, 0, 0) (uma cor vermelha forte)
- Material : Enum.Material.Neon
Parente laserPart para Workspace .
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.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-- Adicione laser ao serviço Debris para ser removido e limpoDebris:AddItem(laserPart, SHOT_DURATION)end
Agora a função para renderizar o laser é completa, pode ser chamada pelo Controlador de Ferramentas .
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.ParentNa 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 laserhitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endTeste 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.
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.
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 = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Crie 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.3local timeOfPreviousShot = 0-- Verifique se o tempo passou desde que o tiro anterior foi disparadolocal function canShootWeapon()endlocal function getWorldMousePosition()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).
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 disparadolocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendNo final da função fireWeapon, atualize timeOfPreviousShot a cada vez que a arma for disparada usando tick.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endDentro 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() thentool.Handle.Activate:Play()fireWeapon()endend
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.
Crie um Pasta em ReplicatedStorage chamado Eventos .
Insira um Evento Remoto na pasta Eventos e nomeie-o DamageCharacter .
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.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Substitua 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 thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel)endendelse-- Calcula a posição final com base na distância máxima do laserhitPosition = tool.Handle.Position + directionVectorend
O servidor precisa dar dano ao jogador que foi atingido quando o evento é disparado.
Insira um Script em ServerScriptService e nomeie-o ServerLaserManager.
Declare uma variável chamada LASER_DAMAGE e definível para 10 , ou um valor de sua escolha.
Crie uma função chamada damageCharacter com dois parâmetros chamados playerFired e characterToDamage .
Dentro da função, encontre o Humanoid do personagem e subtrai LASER_DAMAGE de sua saúde.
Conecte a função damageCharacter à DamageCharacter evento remoto na pasta Eventos.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Remover saúde do personagemhumanoid.Health -= LASER_DAMAGEendend-- Conecte eventos às funções apropriadaseventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)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.
Insira um Evento Remoto na pasta de Eventos no ReplicatedStorage e nomeie-o LaserFired.
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 + directionVectorendtimeOfPreviousShot = 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.
No script ServerLaserManager, crie uma função chamada playerFiredLaser acima de damageCharacter com dois parâmetros chamados 2> playerFired2> e 5> endPosition5>.
Conecte a função ao evento remoto LaserFired .
-- Informe a todos os clientes que um laser foi disparado para que eles possam exibir o laserlocal function playerFiredLaser(playerFired, endPosition)end-- Conecte eventos às funções apropriadaseventsFolder.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á.
Crie uma função obterPlayerToolHandle acima da função playerFiredLaser com um parâmetro chamado player.
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á segurandolocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Informe a todos os clientes que um laser foi disparado para que eles possam exibir o laserlocal 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.
Na função playerFiredLaser, chame a função getPlayerToolHandle com playerFired como argumento e atribua o valor a uma variável chamada 1> toolHandle1>.
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 laserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
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.
Crie um LocalScript em StarterPlayerScripts chamado ClientLaserManager.
Dentro do script, requer o módulo LaserRender .
Crie uma função chamada criarJogadorLaser com os parâmetros playerWhoShot, toolHandle e 1> endPosition1>.
Conecte a função ao evento remoto LaserFired na pasta Eventos.
Na função, use uma declaração se para verificar se playerWhoShot não é igual a playerLocalPlayer .
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 jogadorlocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)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.
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() thenfireWeapon()endendNa 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.
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 limpoDebris:AddItem(laserPart, SHOT_DURATION)-- Reproduz o som de tiro da armalocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
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.
In ToolController , navigate to the line where the DamageCharacter remote event is fired in the fireWeapon function.
Adicionar hitPosition como argumento.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
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.
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 personagemhumanoid.Health -= LASER_DAMAGEendendEmbaixo da função getPlayerToolHandle, crie uma função chamada isHitValid com três parâmetros: playerFired, 1> characterToDamage1> e 4> hitPosition4>.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
A primeira verificação será a distância entre a posição de hit e o personagem hit.
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.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10Na 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 acertolocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
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.
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 acertolocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Verifique se você está atirando através das 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)-- Se uma instância foi atingida que não era o personagem, ignore o tiroif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendDeclare 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>.
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 personagemhumanoid.Health -= LASER_DAMAGEendend
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)