Comprendre les comportements de pilotage File d'attente

Imaginez une scène de jeu où une salle est encombrée d’entités contrôlées par l’IA. Pour une raison quelconque, ils doivent quitter la pièce et passer une porte. Au lieu de les faire marcher les uns sur les autres dans un flux chaotique, apprenez-leur à partir poliment en faisant la queue. Ce tutoriel présente les queue comportement de conduite avec différentes approches pour faire bouger la foule tout en formant des rangées d'entités.

Remarque: Bien que ce tutoriel ait été écrit avec AS3 et Flash, vous devriez pouvoir utiliser les mêmes techniques et concepts dans presque tous les environnements de développement de jeux. Vous devez avoir une compréhension de base des vecteurs mathématiques.


introduction

Faire la queue, Dans le cadre de ce didacticiel, vous devez faire la queue pour former une rangée de personnages qui attendent patiemment d'arriver quelque part. Lorsque le premier de la ligne se déplace, le reste suit, créant un motif qui ressemble à un train tirant des wagons. En attendant, un personnage ne doit jamais quitter la ligne.

Afin d'illustrer le comportement de la file d'attente et de montrer les différentes implémentations, une démo avec une "scène de file d'attente" est la meilleure solution. Un bon exemple est une pièce encombrée d’entités contrôlées par l’intelligence artificielle, qui essaient toutes de quitter la pièce et de passer la porte:


Boids quittant la pièce et passant par la porte sans se comporter en file d'attente. Cliquez pour afficher les forces.

Cette scène a été réalisée en utilisant deux comportements décrits précédemment: recherche et évitement de collision.

La porte est constituée de deux obstacles rectangulaires placés côte à côte avec un espace entre eux (la porte). Les personnages cherchent un point situé derrière cela. Là-bas, les personnages sont repositionnés en bas de l'écran..

À l'heure actuelle, sans le comportement de la file d'attente, la scène ressemble à une horde de sauvages qui se marchent sur la tête pour se rendre à destination. Quand nous aurons terminé, la foule quittera la place en douceur, formant des rangées..


Voir à l'avance

La première capacité qu'un personnage doit obtenir pour faire la queue est de savoir s'il y a quelqu'un devant lui. Sur la base de ces informations, il peut décider de poursuivre ou d’arrêter de bouger..

Malgré l'existence de moyens plus sophistiqués de vérification des voisins, je vais utiliser une méthode simplifiée basée sur la distance entre un point et un personnage. Cette approche a été utilisée dans le comportement d'évitement de collision pour vérifier les obstacles à venir:


Testez les voisins en utilisant le point d'avance.

Un point appelé devant est projeté devant le personnage. Si la distance entre ce point et un personnage voisin est inférieure ou égale à MAX_QUEUE_RADIUS, cela signifie qu'il y a quelqu'un devant et que le personnage doit cesser de bouger.

le devant Le point est calculé comme suit (pseudo-code):

 // qa et ahead sont des vecteurs mathématiques qa = normaliser (vélocité) * MAX_QUEUE_AHEAD; en avant = qa + position;

La vitesse, qui donne également la direction du personnage, est normalisée et mise à l'échelle par MAX_QUEUE_AHEAD pour produire un nouveau vecteur appelé qa. Quand qa est ajouté à la position vecteur, le résultat est un point en avant du personnage et une distance de MAX_QUEUE_AHEAD unités loin de lui.

Tout cela peut être emballé dans le getNeighborAhead () méthode:

 fonction privée getNeighborAhead (): Boid var i: int; var ret: Boid = null; var qa: Vector3D = velocity.clone (); qa.normalize (); qa.scaleBy (MAX_QUEUE_AHEAD); ahead = position.clone (). add (qa); pour (i = 0; i < Game.instance.boids.length; i++)  var neighbor :Boid = Game.instance.boids[i]; var d :Number = distance(ahead, neighbor.position); if (neighbour != this && d <= MAX_QUEUE_RADIUS)  ret = neighbor; break;   return ret; 

La méthode vérifie la distance entre le devant point et tous les autres caractères, renvoyant le premier caractère dont la distance est inférieure ou égale à MAX_QUEUE_AHEAD. Si aucun caractère n'est trouvé, la méthode retourne nul.


Création de la méthode de mise en file d'attente

Comme pour tous les autres comportements, le force de file d'attente est calculé par une méthode nommée queue():

 file d'attente de fonction privée (): Vector3D var voisin: Boid = getNeighborAhead (); if (voisin! = null) // TODO: agissez parce que le voisin est en avance return new Vector3D (0, 0); 

Le résultat de getNeighborAhead () dans stocké dans la variable voisin. Si voisin! = null cela signifie qu'il y a quelqu'un d'avance; sinon, le chemin est libre.

le queue(), comme toutes les autres méthodes de comportement, doit renvoyer une force qui est la force de direction liée à la méthode elle-même. queue() retournera une force sans ampleur pour le moment, donc elle ne produira aucun effet.

le mettre à jour() La méthode de tous les caractères de la scène de porte, jusqu'à présent, est (pseudo-code):

 fonction publique update (): void var porte: Vector3D = getDoorwayPosition (); direction = chercher (porte); // cherche la porte direction = direction + collisionAvoidance (); // évite les obstacles Steering = Steering + queue (); // file d'attente le long du trajet direction = truncate (direction, MAX_FORCE); direction = direction / masse; vélocité = tronqué (vélocité + direction, MAX_SPEED); position = position + vitesse;

Puisque queue() renvoie une force nulle, les personnages continueront à se déplacer sans former de rangées. Il est temps de leur demander de prendre des mesures lorsqu'un voisin est détecté juste devant.


Quelques mots sur l'arrêt du mouvement

Les comportements de direction sont basés sur des forces qui changent constamment, de sorte que l'ensemble du système devient très dynamique. En fonction de la mise en œuvre, plus il y a de forces impliquées, plus il devient difficile d'identifier et d'annuler un vecteur de force spécifique..

L'implémentation utilisée dans cette série de comportements de direction additionne toutes les forces. Par conséquent, pour annuler une force, elle doit être recalculée, inversée et ajoutée au vecteur de force de direction actuel..

C'est à peu près ce qui se passe dans le comportement d'arrivée, où la vélocité est annulée pour que le personnage cesse de bouger. Mais que se passe-t-il lorsque plusieurs forces agissent de concert, comme éviter des collisions, fuir, etc.??

Les sections suivantes présentent deux idées pour faire cesser un personnage. Le premier utilise une approche "arrêt brutal" qui agit directement sur le vecteur vitesse, en ignorant toutes les autres forces de direction. Le second utilise un vecteur de force, nommé frein, pour annuler gracieusement toutes les autres forces de direction, pour finalement arrêter le personnage de bouger.


Arrêt du mouvement: "Arrêt difficile"

Plusieurs forces de direction sont basées sur le vecteur vitesse du personnage. Si ce vecteur change, toutes les autres forces seront affectées lorsqu’elles seront recalculées. L'idée de "blocage" est assez simple: s'il y a un caractère devant nous, nous "rétrécissons" le vecteur vitesse:

 file d'attente de fonction privée (): Vector3D var voisin: Boid = getNeighborAhead (); if (neighbour! = null) velocity.scaleBy (0.3);  retourne le nouveau Vector3D (0, 0); 

Dans le code ci-dessus, le rapidité le vecteur est mis à l'échelle 30% de sa magnitude actuelle (longueur) pendant qu'un personnage est en avance. En conséquence, le mouvement est considérablement réduit, mais il retrouvera finalement son ampleur normale lorsque le personnage bloquant le passage se déplace..

C'est plus facile à comprendre en analysant comment le mouvement est calculé chaque mise à jour:

 vélocité = tronqué (vélocité + direction, MAX_SPEED); position = position + vitesse;

Si la rapidité la force continue à diminuer, la pilotage la force, car il est basé sur la rapidité Obliger. Cela crée un cercle vicieux qui aboutira à une valeur extrêmement faible pour rapidité. C'est à ce moment que le personnage cesse de bouger.

À la fin du processus de réduction, chaque mise à jour du jeu augmente le rapidité vecteur un peu, affectant la pilotage force aussi. Finalement, plusieurs mises à jour après apporteront les deux rapidité et pilotage vector retour à leurs grandeurs normales.

L'approche "arrêt difficile" produit le résultat suivant:


Comportement en file d'attente avec approche "arrêt définitif". Cliquez pour afficher les forces.

Même si ce résultat est assez convaincant, cela ressemble à un résultat "robotique". Une vraie foule n'a généralement pas d'espaces vides entre leurs membres.


Mouvement d'arrêt: force de freinage

La deuxième approche pour arrêter le mouvement tente de créer un résultat moins "robotique" en annulant toutes les forces de direction actives à l'aide d'un frein Obliger:

 file d'attente de fonction privée (): Vector3D var v: Vector3D = velocity.clone (); frein var: Vector3D = nouveau Vector3D (); var voisin: Boid = getNeighborAhead (); if (voisin! = nul) brake.x = -steering.x * 0.8; brake.y = -teering.y * 0,8; v.scaleBy (-1); frein = frein.add (v);  retour frein; 

Au lieu de créer le frein la force en recalculant et en inversant chacune des forces de direction actives, frein est calculé en fonction du courant pilotage vecteur, qui contient toutes les forces de direction ajoutées au moment:


Représentation de la force de freinage.

le frein la force reçoit à la fois sa X et y composants de la pilotage force, mais inversé et avec une échelle de 0.8. Cela signifie que frein a 80% de la magnitude de pilotage et pointe dans la direction opposée.

Pointe: En utilisant le pilotage forcer directement est dangereux. Si queue() est le premier comportement à appliquer à un personnage, le pilotage la force sera "vide". En conséquence, queue() doit être invoqué après toutes les autres méthodes de direction, afin qu'il puisse accéder à la version complète et finale pilotage Obliger.

le frein La force doit également annuler la vélocité du personnage. Cela se fait en ajoutant -rapidité au frein Obliger. Après cela, la méthode queue() peut retourner la finale frein Obliger.

Le résultat de l'utilisation de la force de freinage est le suivant:


Comportement en file d'attente en utilisant l'approche de la force de freinage. Cliquez pour afficher les forces.

Chevauchement des caractères

L’approche de freinage produit un résultat plus naturel que l’ancien "robotique", car tous les personnages essaient de remplir les espaces vides. Cependant, cela introduit un nouveau problème: les caractères se chevauchent.

Pour résoudre ce problème, l'approche des freins peut être améliorée avec une version légèrement modifiée de l'approche "arrêt brutal":

 file d'attente de fonction privée (): Vector3D var v: Vector3D = velocity.clone (); frein var: Vector3D = nouveau Vector3D (); var voisin: Boid = getNeighborAhead (); if (voisin! = nul) brake.x = -steering.x * 0.8; brake.y = -teering.y * 0,8; v.scaleBy (-1); frein = frein.add (v); if (distance (position, voisin.position) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Un nouveau test est utilisé pour vérifier les voisins proches. Cette fois au lieu d'utiliser le devant point pour mesurer la distance, le nouveau test vérifie la distance entre les caractères position vecteur:


Vérifier les voisins proches dans le rayon MAX_QUEUE_RADIUS centré sur la position au lieu du point situé en avant.

Ce nouveau test vérifie s’il existe des personnages proches dans la liste. MAX_QUEUE_RADIUS rayon, mais maintenant il est centré à la position vecteur. Si quelqu'un est à portée de main, cela signifie que la zone environnante est surpeuplée et que les personnages commencent probablement à se chevaucher..

Le chevauchement est atténué en mettant à l'échelle le rapidité vecteur à 30% de sa magnitude actuelle à chaque mise à jour. Comme dans l’approche «arrêt définitif», la réduction de la rapidité vecteur réduit considérablement le mouvement.

Le résultat semble moins "robotique", mais ce n'est pas idéal, car les personnages se chevauchent toujours à la porte:


Comportement en file d'attente avec "arrêt brutal" et force de freinage combinés. Cliquez pour afficher les forces.

Ajout de séparation

Même si les personnages essaient d'atteindre la porte de manière convaincante, en comblant tous les espaces vides lorsque le chemin devient étroit, ils se rapprochent trop près les uns des autres..

Cela peut être résolu en ajoutant une force de séparation:

 file d'attente de fonction privée (): Vector3D var v: Vector3D = velocity.clone (); frein var: Vector3D = nouveau Vector3D (); var voisin: Boid = getNeighborAhead (); if (voisin! = nul) brake.x = -steering.x * 0.8; brake.y = -teering.y * 0,8; v.scaleBy (-1); frein = frein.add (v); frein = frein.add (séparation ()); if (distance (position, voisin.position) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Auparavant utilisé dans le comportement suivant le leader, la force de séparation ajoutée au frein la force fera que les personnages cessent de bouger en même temps qu'ils essaient de rester éloignés les uns des autres.

Le résultat est une foule convaincante essayant d'atteindre la porte:


Comportement en file d'attente avec "arrêt brutal", force de freinage et séparation combinés. Cliquez pour afficher les forces.

Conclusion

Le comportement de la file d'attente permet aux personnages de faire la queue et d'attendre patiemment d'arriver à la destination. Une fois en ligne, un personnage n'essaiera pas de "tricher" en sautant de positions. il ne bougera que lorsque le personnage juste devant lui bouge.

La scène de la porte utilisée dans ce didacticiel montre à quel point ce comportement peut être modifié et modifié. Quelques changements produisent des résultats différents, qui peuvent être ajustés à une grande variété de situations. Le comportement peut également être combiné avec d'autres, tels que l'évitement de collision.

J'espère que vous avez aimé ce nouveau comportement et commencez à l'utiliser pour ajouter des foules en mouvement à votre jeu.!