Implementar comportamento do blaster é o processo de programar uma mecânica de explosão em experiências de tiro em primeira pessoa. Enquanto os jogadores podem disparar com um único clique ou pressionando um botão, criar um comportamento de explosão satisfatório e preciso é importante porque isso aumenta o prazer dos jogadores com a jogabilidade geral.
Usando a experiência de laser tag de exemplo como referência, esta seção do tutorial ensina você sobre os scripts por trás da implementação do comportamento do blaster para dois tipos diferentes de blasters, incluindo orientações sobre:
- Detectar quando os jogadores pressionam o botão de disparo.
- Verificar se o jogador pode usar seu blaster se recentemente pressionou o botão de disparo.
- Gerar dados de explosão que informam ao servidor quem iniciou a explosão, de onde ela veio e qual foi o destino final de cada feixe de laser.
- Notificar o servidor sobre os dados da explosão para que ele possa realizar as ações apropriadas caso a explosão colida com outro jogador.
- Resetar o blaster entre cada explosão para dar ao blaster tempo suficiente para esfriar antes que ele possa disparar novamente.
Após concluir esta seção, você aprenderá sobre os scripts que permitem que o blaster detecte quando suas explosões colidem com outros jogadores, então deduzir a quantidade correspondente de saúde de acordo com cada tipo de blaster.
Detectar entrada do jogador
O primeiro passo para implementar o comportamento do blaster é ouvir quando um jogador pressiona o botão de disparo. O tipo de entrada que os jogadores usam para pressionar o botão de disparo depende do dispositivo que eles estão usando para acessar a experiência. Por exemplo, a experiência de laser tag de exemplo suporta controles de mouse e teclado, gamepads e controles de toque. Você pode ver cada um desses tipos de entrada em ReplicatedStorage ⟩ UserInputHandler.
Este script do cliente usa ContextActionService para vincular MouseButton1 e ButtonR2 à ação de disparo. Isso significa que toda vez que um jogador pressiona o botão esquerdo do mouse ou o botão R2 de um gamepad, um feixe de laser é disparado do blaster. Observe que o HUDGui contém um botão para disparar em dispositivos móveis, que será conectado mais adiante no script.
UserInputHandlerContextActionService:BindAction("_", onBlasterActivated, false,Enum.UserInputType.MouseButton1,Enum.KeyCode.ButtonR2)
Outra observação importante é o uso de Enum.UserInputState.Begin na definição de onBlasterActivated(). Muitas interações da interface do usuário, como escolher um blaster neste exemplo, não ocorrem até que o botão do mouse seja solto (Enum.UserInputState.End), o que dá aos usuários uma última chance de evitar a interação. No entanto, uma mecânica de disparo não parece responsiva a menos que ocorra no instante em que o botão é pressionado.
Para demonstrar, você pode mudar Enum.UserInputState.Begin para Enum.UserInputState.End, então fazer um teste de jogo para ver como a responsividade do disparo impacta a jogabilidade da experiência. Por exemplo, se os jogadores puderem segurar o botão sem disparar, como isso pode mudar a experiência deles ao marcar outros jogadores?
UserInputHandler
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- linha atualizada, tenha certeza de mudar de volta
attemptBlastClient()
end
end
Verificar se o jogador pode disparar
Depois que UserInputHandler detecta uma pressão de botão ou um toque na tela, ele chama ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient para verificar se o jogador pode disparar ou não. Como a maioria das verificações na experiência de laser tag de exemplo, isso ocorre duas vezes: primeiro no cliente e, em seguida, no servidor. attemptBlastClient então chama ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast para realizar uma verificação simples do atributo do jogador blasterStateClient:
canLocalPlayerBlast
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
end
Se você examinar ReplicatedStorage ⟩ Blaster ⟩ BlasterState, você pode ver que a experiência tem três estados de blaster: Ready, Blasting e Disabled. Para ver o efeito de cada um desses estados, você pode testar a experiência, selecionar seu jogador sob o serviço Players, e então observar o atributo blasterStateClient na janela Properties. Note como exibe Disabled enquanto você escolhe seu blaster, Ready na maior parte do tempo, e Blasting por menos de um segundo depois que você pressiona o botão.
Essa leve pausa impede que você dispare tão rapidamente quanto pode clicar. Por exemplo, se você mudar a função para sempre retornar true, poderá disparar rapidamente seu blaster sem nenhuma demora, o que é irrealista para a jogabilidade de laser tag.
canLocalPlayerBlast
local function canLocalPlayerBlast(): boolean
return true -- linha atualizada, tenha certeza de mudar de volta
end
Gerar dados de explosão
Depois de verificar que o blaster do jogador está no estado Ready, attemptBlastClient chama ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient. O primeiro passo que blastClient toma é definir o atributo do jogador blasterStateClient para Blasting, o que evita o mesmo caso de disparo rápido de antes.
O próximo passo é gerar os dados da explosão. Se você revisar ReplicatedStorage ⟩ Blaster ⟩ BlastData, você pode ver que cada explosão consiste em três peças de informação:
- O jogador que inicia a explosão.
- Um DataType.CFrame que representa o ponto de origem da explosão.
- Uma tabela RayResult que contém o destino final de cada feixe de laser e o jogador atingido, se atingiu outro jogador.
Para gerar esses dados, blastClient chama ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient ⟩ generateBlastData, que você pode revisar abaixo.
generateBlastData
local function generateBlastData(): BlastData.Type
local blasterConfig = getBlasterConfig()
local rayDirections = getDirectionsForBlast(
currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(
localPlayer, currentCamera.CFrame.Position, rayDirections)
local blastData: BlastData.Type = {
player = localPlayer,
originCFrame = currentCamera.CFrame,
rayResults = rayResults,
}
return blastData
end
Essa função começa utilizando getBlasterConfig para recuperar o tipo de blaster do jogador. O exemplo fornece dois tipos de blasters: um que produz vários feixes com uma ampla dispersão horizontal, e outro que produz um único feixe. Você pode encontrar suas configurações em ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder.
A função então usa currentCamera.CFrame como o ponto de origem da explosão, passando-o para getDirectionsForBlast. Neste ponto, o código não se trata mais do blaster, trata-se do feixe de laser, sobre o qual você aprenderá mais na seção detect hits do tutorial. Finalmente, após criar a tabela rayResults, generateBlastData possui todas as informações necessárias para retornar os dados da explosão para blastClient.
Notificar o servidor
Uma vez que blastClient tenha os dados completos para a explosão, ele dispara dois eventos:
blastClientlocal laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEventlocal laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEventlaserBlastedBindableEvent:Fire(blastData)laserBlastedEvent:FireServer(blastData)
O BindableEvent notifica outros scripts do cliente sobre a explosão. Por exemplo, ReplicatedStorage ⟩ FirstPersonBlasterVisuals usa este evento para saber quando exibir efeitos visuais, como a animação da explosão e a barra de recarga. Da mesma forma, o RemoteEvent notifica os scripts do servidor sobre a explosão, que começa a processar a explosão em ServerScriptService ⟩ LaserBlastHandler.
LaserBlastHandler
local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
if not validatedBlastData then
return
end
if not canPlayerBlast(playerBlasted) then
return
end
blastServer(playerBlasted)
processTaggedPlayers(playerBlasted, blastData)
for _, replicateToPlayer in Players:GetPlayers() do
if playerBlasted == replicateToPlayer then
continue
end
replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
end
end
Para ajudar a prevenir trapaças, o servidor deve verificar todos os dados que cada cliente envia. Essas verificações incluem:
- BlastData é uma tabela? Contém um Class.CFrame e outra tabela chamada rayResults?
- O jogador está equipado com um blaster?
- O jogador tem um personagem e uma localização dentro do mundo?
- Após enviar os dados da explosão, o jogador se afastou uma distância excessiva de onde disparou o feixe de laser?
Essa última verificação envolve um julgamento, e de acordo com a latência do servidor e a velocidade de movimento do jogador, você pode decidir que diferentes valores são excessivos para sua própria experiência. Para demonstrar como fazer esse julgamento, você pode ter uma noção da magnitude típica da mudança posicional adicionando uma declaração de impressão em getValidatedBlastData e testando a experiência.
getValidatedBlastDatalocal distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Positionprint(distanceFromCharacterToOrigin.Magnitude) -- linha atualizada, tenha certeza de removerif distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS thenwarn(`Player {player.Name} failed an origin sanity check while blasting`)returnend
À medida que você se move e dispara, observe a saída. Pode parecer algo assim:
1.90196299552917483.15495586395263672.57428836822509774.80445861816406252.6434271335601807
Se você aumentar a velocidade de movimento dos jogadores em ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement, então teste novamente, você provavelmente encontrará muitos checks falhados devido ao movimento excessivo entre explosões.
togglePlayerMovementlocal ENABLED_WALK_SPEED = 60 -- linha atualizada, tenha certeza de mudar de volta
O servidor então faz o seguinte:
- Valida rayResults.
- Verifica se o jogador pode disparar.
- Reseta o estado do blaster.
- Reduz a saúde de qualquer jogador marcado.
- Replica a explosão para todos os outros jogadores para que eles possam ver os efeitos visuais em terceira pessoa.
Para mais informações sobre essas operações do servidor, consulte a seção detect hits do tutorial.
Resetar o blaster
Na experiência de laser tag de exemplo, os blasters usam uma mecânica de calor. Em vez de recarregar após um número definido de explosões, eles precisam de tempo para "esfriar" entre cada explosão. Esse mesmo atraso de esfriamento ocorre tanto no cliente (blastClient) quanto no servidor (blastServer), com o servidor atuando como a fonte da verdade.
blastServer
local blasterConfig = getBlasterConfig(player)
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")
task.delay(secondsBetweenBlasts, function()
local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
if currentState == BlasterState.Blasting then
player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
end
end)
O atributo secondsBetweenBlasts é parte da configuração do blaster em ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder. Após o atraso secondsBetweenBlasts passar, o jogador pode disparar novamente, e todo o processo se repete. Para ajudar o jogador a entender quando ele pode disparar novamente, a experiência inclui uma barra de cooldown.
Neste ponto, os jogadores podem aparecer e reaparecer, mirar e disparar, mas a experiência ainda precisa determinar os resultados de cada explosão. Na próxima seção do tutorial, você aprenderá como programar a capacidade do blaster de detectar quando a explosão atinge outro jogador, então reduzir a quantidade apropriada de saúde do jogador de acordo com as configurações do blaster.