Detecting hits é o processo de identificar quando explosões colidem com jogadores, então reduzir sua saúde apropriadamente. Em um nível alto, você pode pensar nisso como:
- Um cheque fisicamente simulado de se um projétil atingiu o alvo.
- Uma verificação instantânea de se o blaster estava apontado para o alvo.
O tipo de detecção de hit que você usa depende dos requisitos de jogoplay da sua experiência. Por exemplo, um cheque fisicamente simulado é apropriado para uma experiência de dodgeball onde as bolas precisam deixar a mão em uma certa velocidade, cair como elas se movem pelo ar ou alterar direção de condições meteorológicas. No entanto, um check instantâneo é melhor para uma experiência de laser tag onde os raios devem ter uma velocidade quase infinita
Usando a experiência de laser de exemplo como referência, esta seção do tutorial ensina você sobre os scripts por trás da detecção de hit no espaço 3D, incluindo orientações sobre:
- Obtendo a direção de explosão a partir dos valores da câmera atual e do digitarde arma do jogador.
- Projeta raios em um caminho direto do blaster à medida que ele explode.
- Validando a explosão para impedir a exploração de dados do blaster.
- Reduzindo a saúde do jogador de acordo com o dano de explosão de cada tipo de blaster e quantos raios atingem o jogador.
Depois de concluir esta seção, você pode explorar tópicos de desenvolvimento adicionais para melhorar sua jogabilidade, como áudio, iluminação e efeitos especiais.
Obter direção de explosão
Depois que um jogador atira no seu blaster, Armazenamento Replicado > Blaster > tentarBlastClient > 1> blastClient1> > 4> generateBlastData4> chama duas funções para iniciar o processo de detecção de hit: 7> rayDirections()7> e 0> rayResults()
gerarDadosExplosivos
local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)
As entradas para rayDirections são simples: a posição e valores de rotação atual da câmera, e o digitarde laser do jogador. Se a experiência de rastreamento de laser só fornecer jogadores lasers que produzem um único laser, Armazenamento Replicado > LaserRay >
No entanto, porque a amostra fornece um tipo de blaster adicional que produz vários feixes de laser com uma larga, distribuição horizontal, getDirectionsForBlast deve calcular a direção para cada feixe de laser da distribuição de acordo com seus ângulos dentro da configuração do blaster:
obterDireçõesParaBlast
if numLasers == 1 then-- Para lasers únicos, eles apontam diretotable.insert(directions, originCFrame.LookVector)elseif numLasers > 1 then-- Para múltiplos lasers, espalhe-os uniformemente horizontalmente-- sobre um intervalo laserSpreadDegrees ao redor do centrolocal leftAngleBound = laserSpreadDegrees / 2local rightAngleBound = -leftAngleBoundlocal degreeInterval = laserSpreadDegrees / (numLasers - 1)for angle = rightAngleBound, leftAngleBound, degreeInterval dolocal direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVectortable.insert(directions, direction)endend
Para demonstrar este conceito ainda mais, se você incluir um terceiro tipo de blaster com uma larga, vertical spread, você pode criar um novo atributo de blaster, como spreadDirection, então ajustar a CFrame cálculo para usar um diferente digitar. Por exemplo, observe a diferença nas cálculos de
if numLasers == 1 thentable.insert(directions, originCFrame.LookVector)elseif numLasers > 1 thenlocal leftAngleBound = laserSpreadDegrees / 2local rightAngleBound = -leftAngleBoundlocal degreeInterval = laserSpreadDegrees / (numLasers - 1)for angle = rightAngleBound, leftAngleBound, degreeInterval dolocal directionif spreadDirection == "vertical" thendirection = (originCFrame * CFrame.Angles(math.rad(angle), 0, 0)).LookVectorelsedirection = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVectorendtable.insert(directions, direction)endendreturn directions
Em última análise, a função rayDirections() retorna uma tabela de Vectors que representa a direção de cada feixe de laser. Se for útil, você pode adicionar algum log para ter uma visão de como esses dados se parecem.
gerarDadosExplosivos
local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)for _, direction in rayDirections do -- nova linhaprint(direction) -- nova linhaend -- nova linhalocal rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)
Raios de Drenagem
castLaserRay() , a segunda função em Armazenamento Replicado > Blaster > 0> tentB
Essas informações são particularmente úteis para experiências de primeira pessoa de shooter, pois permitem que você veja quando e onde as explosões interagem com os jogadores ou o ambiente. Por exemplo, a seguinte imagem mostra duas raias que estão se projetando em paralelo umas com as outras. De acordo com seu ponto de origem e direção, a Ray A erra a parede e continua até que ela atinja sua distância máxima, enqu
Os parâmetros castLaserRay() especificam que Raycast() chamadas devem considerar cada parte na área de trabalho exceto o personagem que explodiu. O script então cria um raio para cada direção na tabela 2>directions2>. Se um raio atingir algo, ele gera um 5> Datatype.RaycastResult5>, que
- Distance – A distância entre a origem do raio e o ponto de interseção.
- Material – O Enum.Material no ponto de interseção.
O valor Instance é o mais crítico dessas propriedades para a experiência de jogo de laser de raios com outros jogadores. Para recuperar essas informações, a experiência usa a função armazenamento de có
castLaserRay() então usa Position e Normal para criar um novo 0> Datatype.CFrame
castLaserRay
if result then-- O impacto explodiu algo, verifique se era um jogador.destination = CFrame.lookAt(result.Position, result.Position + result.Normal)taggedPlayer = getPlayerFromDescendant(result.Instance)else-- A explosão não atingiu nada, então seu destino é-- o ponto em sua distância máxima.local distantPosition = origin + rayDirection * MAX_DISTANCEdestination = CFrame.lookAt(distantPosition, distantPosition - rayDirection)taggedPlayer = nilend
Validar o Explosão
Para evitar cheating, o capítulo anterior Implementando Blasters explica como blastClient notifica o servidor do blast usando um RemoteEvent para que ele possa verificar todos
Primeiro, getValidatedRayResults chamadas validateRayResult para verificar que cada entrada na tabela rayResults do cliente é um 1> Datatype.CFrame1> e um 4> Player4> (ou nil).
Em seguida, ele chama isRayAngleFromOriginValid para comparar os ângulos esperados do laser ao laser espalhado ao cliente. Este código em particular mostra a vantagem de usar ReplicatedStorage porque o servidor pode chamar getDirectionsForBlast mesmo, armazenar a saída como os dados "esperados" e depois compará-la com os dados do
Exatamente como a validação de blaster do capítulo anterior, isRayAngleFromOriginValid confia em um valor de tolerância para determinar o que constitui uma diferença "excessiva" em ângulos:
éRayAngleFromOriginValidlocal claimedDirection = (rayResult.destination.Position - originCFrame.Position).Unitlocal directionErrorDegrees = getAngleBetweenDirections(claimedDirection, expectedDirection)return directionErrorDegrees <= ToleranceValues.BLAST_ANGLE_SANITY_CHECK_TOLERANCE_DEGREESO Roblox abstrai os bits mais envolvidos de matemática, então o resultado é uma função de auxílio curta, altamente reutilizável com aplicabilidade em uma variedade de experiências:
obterAngleBetweenDirectionslocal function getAngleBetweenDirections(directionA: Vector3, directionB: Vector3)local dotProduct = directionA:Dot(directionB)local cosAngle = math.clamp(dotProduct, -1, 1)local angle = math.acos(cosAngle)return math.deg(angle)endA próxima verificação é a mais intuitiva. Embora getValidatedBlastData use DISTANCE_SANITY_CHECK_TOLERANCE_STUDS para verificar se o jogador que explodiu estava perto do ponto de origem do feixe, isPlayerNearPosition usa lógica idêntica para verificar se o jogador marcado estava perto do ponto de destino do fe
isPlayerNearPositionlocal distanceFromCharacterToPosition = position - character:GetPivot().Positionif distanceFromCharacterToPosition.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS thenreturn falseendO último check isRayPathObstructed usa uma variação da operação de raycast para verificar se o destino do ray está atrás de uma parede ou outra obstrução da posição do cliente. Por exemplo, se um jogador malicioso remover todas as paredes da experiência para marcar outros jogadores, o servidor verificaria e confirmaria que os raios são inválidos, pois conhece todas as posições de todos os objetos dentro do ambiente.
isRayPathObstruídolocal scaledDirection = (rayResult.destination.Position - blastData.originCFrame.Position)scaledDirection *= (scaledDirection.Magnitude - 1) / scaledDirection.Magnitude
Nenhuma estratégia de anti-exploit é abrangente, mas é importante considerar como os jogadores maliciosos podem abordar sua experiência para que você possa colocar cheques em seu lugar para que o servidor possa executar para detectar comportamentos suspeitos.
Reduzir a saúde do jogador
Depois de verificar que um jogador marcou outro jogador, os passos finais na conclusão do loop de jogo principal na experiência de rastreamento de laser são reduzir a saúde do jogador marcado, incrementar a tabela de classificação e respawnar o jogador de volta para a rodada.
Começando com a redução da saúde do jogador atacado, Spawning and Respawning cobre a distinção entre Player e Class.
As experiências armazenam valores de dano na atributo damagePerHit de cada blaster. Por exemplo
Health não aceita valores negativos, então onPlayerTagged tem alguma lógica para manter a saúde do jogador em ou acima de zero. Depois de verificar que a saúde do jogador está acima de zero, ele compara a saúde para damagePerHit e
Essa abordagem pode parecer um pouco confusa. Por exemplo, por que não apenas definir a saúde do jogador para zero se for negativo? A razão é porque definir os valores de saúde contorna o campo de força. Usando o método Humanoid:TakeDamage(), garante que os jogadores não sofram dano enquanto seus campos de força estiverem ativos.
em Jogador Vinculado
local function onPlayerTagged(playerBlasted: Player, playerTagged: Player, damageAmount: number)
local character = playerTagged.Character
local isFriendly = playerBlasted.Team == playerTagged.Team
-- Desabilitar Iniciar / executaramigável
if isFriendly then
return
end
local humanoid = character and character:FindFirstChild("Humanoid")
if humanoid and humanoid.Health > 0 then
-- Evite saúde negativa
local damage = math.min(damageAmount, humanoid.Health)
-- TakeDamage garante que a saúde não seja reduzida se o ForceField estiver ativo
humanoid:TakeDamage(damage)
if humanoid.Health <= 0 then
-- Atribuiu um ponto para o jogadorBlasted um ponto por marcar o jogadorTagged
Scoring.incrementScore(playerBlasted, 1)
end
end
end
O próximo passo é incrementar a tabela de classificação. Pode ter parecido desnecessário para LaserBlastHandler incluir o jogador que explodiu ao lado dos dados de explosão, mas sem essa informação, a experiência não pode créditar o jogador com a etiqueta de alguém fora. Finalmente, o jogador etiquetado respawna de volta à rodada, que você pode revisar em Spawning and Respawning.
Os cinco capítulos neste tutorial cobrem o loop de jogo principal da experiência, mas ainda há muitas áreas para explorar, como:
- Visuais do Blaster : Veja ReplicatedStorage > FirstPersonBlasterVisuals e 0> ServerScriptService 0> > 3> ThirdPersonBlasterVisuals 3> .
- Áudio : Veja Armazenamento Replicado > Gerenciador de Som .
- Modos Personalizados : Como você pode modificar essa experiência para introduzir novos tipos de objetivos, como pontuação do maior número de pontos antes que o tempo acabe?
Para uma lógica de jogo estendida para a experiência de laser tag, bem como recursos ambientais reutilizáveis e de alta qualidade, revise o Laser Tag modelo de tema.