Détection des coups

*Ce contenu est traduit en utilisant l'IA (Beta) et peut contenir des erreurs. Pour consulter cette page en anglais, clique ici.

Detection des coups de feu est le processus d'identification de quand les explosions se heurtent aux joueurs, puis réduit leur santé en conséquence. À un niveau élevé, vous pouvez penser à ce travail comme :

  1. Un chèque physiquement simulé de savoir si un projectile a touché la cible.
  2. Un contrôle instantané de savoir si le blaster était visé sur la cible.

Le type de détection de hit que vous utilisez dépend des exigences en matière de jeu de votre expérience. Par exemple, un chèque physiquement simulé est approprié pour une expérience de dodgeball où les balles doivent quitter la main à une certaine vitesse, tomber pendant qu'elles se déplacent dans l'air ou changer de direction à partir des conditions météo. Cependant, un check instantané est un meilleur match pour une expérience de laser tag où les rayons doivent avoir une vites

En utilisant l'expérience de balise laser expérience comme référence, cette section du tutoriel vous apprend sur les scripts derrière la détection des coups dans l'espace 3D, y compris des conseils sur :

  • Obtenir la direction de l'explosion à partir des valeurs de caméra actuelles et du taperde blaster du joueur.
  • Lancer des rayons dans un chemin direct du blaster au moment de l'explosion.
  • Validation de l'explosion pour empêcher l'exploitation des données du blaster.
  • Réduction de la santé du joueur en fonction des dégâts d'explosion de chaque type de blaster et du nombre de rayons qui touchent le joueur.

Après avoir terminé cette section, vous pouvez explorer d'autres sujets de développement pour améliorer votre expérience de partie, tels que l'audio, l'éclairage et les effets spéciaux.

Obtenez la direction de l'explosion

Après qu'un joueur ait fait exploser son blaster, ReplicatedStorage > Blaster > attemptBlastClient > 1> blastClient1> > 4> generateBlastData4> appelle deux fonctions pour démarrer le processus de détection des tirs : 7> rayDirections()7> et 0> rayResults()0>.

générer des données d'explosion

local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)

Les entrées pour rayDirections sont simples : la position de la caméra actuelle et les valeurs de rotation, et le taperde blaster du joueur. Si l'expérience de laser de samuel ne donnait que des lasers qui produisaient un seul faisceau laser, ReplicatedStorage > LaserRay > 1>

Cependant, car l'échantillon fournit un type de blaster supplémentaire qui produit plusieurs rayons laser avec une large portée horizontale, getDirectionsForBlast doit calculer la direction pour chaque rayon laser de la portée en fonction de leurs angles dans la configuration du blaster :

obtenir les directions pour le bombardement

if numLasers == 1 then
-- Pour les lasers simples, ils visent tout droit
table.insert(directions, originCFrame.LookVector)
elseif numLasers > 1 then
-- Pour plusieurs lasers, répartissez-les uniformément horizontalement
-- sur un intervalle laserSpreadDegrees autour du centre
local leftAngleBound = laserSpreadDegrees / 2
local rightAngleBound = -leftAngleBound
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
for angle = rightAngleBound, leftAngleBound, degreeInterval do
local direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
table.insert(directions, direction)
end
end

Pour démontrer ce concept encore davantage, si vous incluez un troisième type de blaster avec une large répartition verticale, vous pouvez créer une nouvelle attribut de blaster, comme spreadDirection, puis ajuster le spreadDirection calcul pour utiliser un autre axe. Par exemple, notez la différence dans les calculs de CFrame ci-dessous pour ce taperde


if numLasers == 1 then
table.insert(directions, originCFrame.LookVector)
elseif numLasers > 1 then
local leftAngleBound = laserSpreadDegrees / 2
local rightAngleBound = -leftAngleBound
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
for angle = rightAngleBound, leftAngleBound, degreeInterval do
local direction
if spreadDirection == "vertical" then
direction = (originCFrame * CFrame.Angles(math.rad(angle), 0, 0)).LookVector
else
direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
end
table.insert(directions, direction)
end
end
return directions

En fin de compte, la fonction rayDirections() renvoie un tableau de Vectors qui représente la direction de chaque faisceau laser. Si c'est utile, vous pouvez ajouter un peu de journal pour avoir une idée de ce que ressemble ces données.

générer des données d'explosion

local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
for _, direction in rayDirections do -- nouvelle ligne
print(direction) -- nouvelle ligne
end -- nouvelle ligne
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)

Rayons de distribution

castLaserRay() , la deuxième fonction dans ReplicatedStorage > Blaster > 0> attemptBlastClient0>

Cette information est particulièrement utile pour les expériences de tir en première personne parce qu'elle vous permet de voir quand et où les explosions se superposent aux joueurs ou à l'environnement. Par exemple, la image suivante montre deux rayons qui se projettent en parallèle l'un de l'autre. Selon leur point d'origine et leur direction, le rayon A manque le mur et continue jusqu'à ce qu'il atteigne sa distance maximale, tandis que le rayon B se heurte au mur

A diagram where Ray A continues through the wall, and Ray B collides with the wall.

Les paramètres castLaserRay() spécifient que les appels Raycast() doivent prendre en compte chaque partie dans l'espace de travail sauf le personnage qui a explosé. Le script génère alors un rayon pour chaque direction dans la table 1> dirigeons1>. Si un rayon touche quelque chose, il génère un 4>Datatype.RaycastResult

La valeur Instance est la plus critique de ces propriétés pour le jeu de tir laser sur la environnementparce qu'elle communique quand les rayons se heurtent à d'autres joueurs. Pour récupérer cette information, l'

castLaserRay() then uses Position and Normal to create a new 0> Datatype.CFrame

castLaserRay

if result then
-- La bombe a frappé quelque chose, vérifiez si c'était un joueur.
destination = CFrame.lookAt(result.Position, result.Position + result.Normal)
taggedPlayer = getPlayerFromDescendant(result.Instance)
else
-- La bombe n'a rien frappé, donc sa destination est
-- le point à sa distance maximale.
local distantPosition = origin + rayDirection * MAX_DISTANCE
destination = CFrame.lookAt(distantPosition, distantPosition - rayDirection)
taggedPlayer = nil
end

Valider l'explosion

Pour empêcher la triche, le chapitre précédent implémenter les blasters explique comment blastClient notifie le serveur de l'explosion en utilisant un Class.RemoteEvent</

  1. Tout d'abord, getValidatedRayResults appelle validateRayResult pour vérifier que chaque entrée dans la table rayResults du client est un 1> Datatype.CFrame1> et un 4> Player4> (ou zéro).

  2. Ensuite, il appelle isRayAngleFromOriginValid pour comparer les angles attendus du laser au laser répandu du client. Ce code en particular montre l'avantage d'utiliser ReplicatedStorage car le serveur peut appeler getDirectionsForBlast lui-même, stocker le retour comme les données « attendues » et ensuite comparer avec les données du client.

    Comme la validation du blaster du chapitre précédent, isRayAngleFromOriginValid repose sur une valeur de tolérance pour déterminer ce qui constitue une différence d'angle "excessive" :

    est RayAngleFromOriginValide

    local claimedDirection = (rayResult.destination.Position - originCFrame.Position).Unit
    local directionErrorDegrees = getAngleBetweenDirections(claimedDirection, expectedDirection)
    return directionErrorDegrees <= ToleranceValues.BLAST_ANGLE_SANITY_CHECK_TOLERANCE_DEGREES

    Roblox abstrait les bits les plus impliqués de la mathématique, ce qui donne lieu à un résultat être un court, hautement réutilisable fonction d'aide avec une application dans une gamme de situations :

    obtenirAngleBetweenDirections

    local 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)
    end
  3. Le prochain test est le plus intuitif. Alors que getValidatedBlastData utilise DISTANCE_SANITY_CHECK_TOLERANCE_STUDS pour vérifier que le joueur qui a fait exploser était près du point d'origine du faisceau, isPlayerNearPosition utilise une logique identique pour vérifier si le joueur marqué était près du point d'arrivée

    est joueur près de la position

    local distanceFromCharacterToPosition = position - character:GetPivot().Position
    if distanceFromCharacterToPosition.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
    return false
    end
  4. La dernière vérification isRayPathObstructed utilise une variante de l'opération de raycast pour vérifier si la destination du rayon est derrière un mur ou une autre obstruction à partir de la position du client. Par exemple, si un joueur malveillant supprimait systématiquement toutes les murs de l'expérience pour marquer d'autres joueurs, le serveur vérifierait et confirmerait que les rayons sont invalides car il sait que chaque position d'objet dans l'environnement.

    est bloqué

    local scaledDirection = (rayResult.destination.Position - blastData.originCFrame.Position)
    scaledDirection *= (scaledDirection.Magnitude - 1) / scaledDirection.Magnitude

Aucune stratégie anti-exploit n'est complète, mais il est important de considérer comment les joueurs malveillants peuvent approcher votre expérience afin que vous puissiez mettre en place des vérifications qui peuvent être exécutées sur le serveur pour détecter le comportement suspect.

Réduire la santé des joueurs

Après avoir vérifié qu'un joueur a marqué un autre joueur, les étapes finales dans la création du principal gameplay loop dans l'expérience de samuelaser sont de réduire la santé du joueur marqué, d'augmenter le classementset de réapparaître le joueur dans la manche.

En commençant par réduire la santé du joueur marqué, Spawning and Respawning couvre la distinction entre Player et Class.

Les expériences stockent les valeurs de dégâts dans l'attribut damagePerHit de chaque blaster. Par exemple

Health n'accepte pas les valeurs négatives, donc onPlayerTagged a une certaine logique pour garder la santé du joueur au-dessus de zéro. Après avoir vérifié que la santé du joueur est au-dessus de zéro, il compare la santé à damagePerHit

Cette approche de la problématique peut sembler un peu compliquée. Par exemple, pourquoi ne pas simplement définir la santé du joueur à zéro si c'était négatif ? La raison est que le définir les valeurs de santé vallait le champ de force actif. L'utilisation de la méthode Humanoid:TakeDamage() garantit que les joueurs ne prennent pas de dégâts pendant que leurs champs de force sont actifs.

sur le joueur tagué

local function onPlayerTagged(playerBlasted: Player, playerTagged: Player, damageAmount: number)
local character = playerTagged.Character
local isFriendly = playerBlasted.Team == playerTagged.Team
-- Désactiver le lancer
if isFriendly then
return
end
local humanoid = character and character:FindFirstChild("Humanoid")
if humanoid and humanoid.Health > 0 then
-- Évitez la santé négative
local damage = math.min(damageAmount, humanoid.Health)
-- TakeDamage assure que la santé n'est pas réduite si ForceField est actif
humanoid:TakeDamage(damage)
if humanoid.Health <= 0 then
-- Récompense le joueurBlasted a point for tagging playerTagged
Scoring.incrementScore(playerBlasted, 1)
end
end
end

La prochaine étape est d'augmenter le classements. Il pourrait sembler inutile pour LaserBlastHandler d'inclure le joueur qui a explosé avec les données d'explosion, mais sans cette information, l'expérience ne peut pas créditer le joueur avec le tagging quelqu'un de sortir. Enfin, le joueur tagué réapparaît dans la manche, que vous pouvez examiner dans Spawning and Respawning.

Les cinq chapitres de ce cours couvrent la boucle de jeu principal de l'expérience, mais il y a encore beaucoup de zones à explorer, telles que :

  • Visueles de blaster : Voir ReplicatedStorage > FirstPersonBlasterVisuals et 0> ServerScriptService 0> > 3> ThirdPersonBlasterVisuals 3> .
  • Audio : Voir ReplicatedStorage > SoundHandler .
  • Modes personnalisés : Comment pouvez-vous modifier cette expérience pour introduire de nouveaux types d'objectifs, tels que le score du plus grand nombre de points avant la fin du temps imparti ?

Pour une logique de jeu étendue pour l'expérience de marquage laser, ainsi que des ressources environnementales réutilisables de haute qualité, consultez le modèle Laser Tag.