Implementar comportamento do blaster é o processo de programar uma mecânica de explosão em jogos de tiro em primeira pessoa. Enquanto os jogadores podem disparar com um único clique ou pressionar um botão, criar um comportamento de explosão satisfatório e preciso é importante porque melhora o prazer dos jogadores com a jogabilidade geral.
Usando o jogo de laser tag de exemplo como referência, esta seção do tutorial ensina 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 explosão.
- Verificar se o jogador pode usar seu blaster se ele pressionou recentemente o botão de explosão.
- 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 se a explosão colidir com outro jogador.
- Redefinir o blaster entre cada explosão para dar ao blaster tempo suficiente para esfriar antes que ele possa disparar novamente.
Depois de completar esta seção, você aprenderá sobre os scripts que permitem que o blaster detecte quando suas explosões colidem com outros jogadores, deduzindo então 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 explosão. O tipo de entrada que os jogadores usam para pressionar o botão de explosão depende do dispositivo que estão usando para acessar o jogo. Por exemplo, o jogo 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 explosão. 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. Note que o HUDGui contém um botão para explosão em dispositivos móveis, que é conectado mais tarde no script.
ContextActionService: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 explosão 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, e então testar para ver como a responsividade da explosão impacta a jogabilidade do jogo. Por exemplo, se os jogadores puderem manter o botão pressionado sem acionar a explosão, como isso pode mudar a experiência deles ao marcar outros jogadores?
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- linha atualizada, certifique-se de mudar de volta
attemptBlastClient()
end
endVerificar se o jogador pode disparar
Depois que UserInputHandler detecta uma pressão de botão ou toque na tela, ele chama ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient para verificar se o jogador pode disparar ou não. Como a maioria das verificações no jogo de laser tag de exemplo, isso ocorre duas vezes: primeiro no cliente, depois mais tarde no servidor. attemptBlastClient então chama ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast para realizar uma verificação simples do atributo do jogador blasterStateClient:
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
endSe você examinar ReplicatedStorage ⟩ Blaster ⟩ BlasterState, você pode ver que o jogo tem três estados de blaster: Ready, Blasting e Disabled. Para ver o efeito de cada um desses estados, você pode testar o jogo, selecionar seu jogador no serviço Players, e então observar o atributo blasterStateClient na janela Properties. Note como ele exibe Disabled enquanto você escolhe seu blaster, Ready na maior parte do tempo, e Blasting por menos de um segundo após você pressionar 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 verdadeiro, você pode disparar rapidamente seu blaster sem qualquer atraso, o que é irrealista para a jogabilidade de laser tag.
local function canLocalPlayerBlast(): boolean
return true -- linha atualizada, certifique-se de mudar de volta
endGerar 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 mencionado anteriormente.
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.
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
endEsta função começa usando getBlasterConfig para recuperar o tipo de blaster do jogador. O exemplo fornece dois tipos de blasters: um que produz vários feixes com uma dispersão horizontal ampla, 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, mas do feixe de laser, sobre o qual você aprenderá mais na seção detectar hits do tutorial. Finalmente, após criar a tabela rayResults, generateBlastData tem todas as informações necessárias para retornar os dados da explosão para blastClient.
Notificar o servidor
Uma vez que blastClient tem dados completos para a explosão, ele dispara dois eventos:
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent: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 de explosão e a barra de recarga. Da mesma forma, o RemoteEvent notifica scripts do servidor sobre a explosão, que começa a processar a explosão em ServerScriptService ⟩ 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
endPara 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 tem um blaster equipado?
- 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 o seu próprio jogo. Para demonstrar como fazer esse julgamento, você pode ter uma noção da magnitude típica da mudança posicional adicionando uma instrução de impressão em getValidatedBlastData e testando o jogo.
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- linha atualizada, certifique-se de remover
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Player {player.Name} failed an origin sanity check while blasting`)
return
endÀ medida que você se move e dispara, note a saída. Pode parecer algo assim:
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807Se você aumentar a velocidade de movimento para os jogadores em ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement, então teste novamente, você provavelmente encontrará muitas verificações falhadas devido ao movimento excessivo entre explosões.
local ENABLED_WALK_SPEED = 60 -- linha atualizada, certifique-se de mudar de voltaO servidor então faz o seguinte:
- Valida rayResults.
- Verifica se o jogador pode disparar.
- Redefine o estado do blaster.
- Reduz a saúde de quaisquer jogadores marcados.
- Replica a explosão para todos os outros jogadores para que eles possam ver visuais em terceira pessoa.
Para mais informações sobre essas operações do servidor, veja a seção detectar hits do tutorial.
Redefinir o blaster
No jogo 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 resfriamento ocorre tanto no cliente (blastClient) quanto no servidor (blastServer), com o servidor atuando como a fonte da verdade.
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 faz parte da configuração do blaster em ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder. Após o atraso de secondsBetweenBlasts passar, o jogador pode disparar novamente, e todo o processo se repete. Para ajudar o jogador a entender quando ele pode disparar novamente, o jogo inclui uma barra de recarga.
Neste ponto, os jogadores podem aparecer e reaparecer, mirar e disparar, mas o jogo 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, reduzindo então a quantidade apropriada de saúde do jogador de acordo com as configurações do blaster.