Steuerungsverhalten verstehen Warteschlange

Stellen Sie sich eine Spielszene vor, in der ein Raum mit von KI gesteuerten Entitäten überfüllt ist. Aus irgendeinem Grund müssen sie den Raum verlassen und durch eine Tür gehen. Anstatt sie in einem chaotischen Fluss über einander laufen zu lassen, sollten Sie ihnen beibringen, wie man höflich geht, wenn man in der Schlange steht. Dieses Tutorial präsentiert die Warteschlange Lenkverhalten mit unterschiedlichen Ansätzen, um eine Menschenmenge zu bewegen, während Reihen von Entitäten gebildet werden.

Hinweis: Obwohl dieses Tutorial mit AS3 und Flash geschrieben wurde, sollten Sie in der Lage sein, in fast jeder Spieleentwicklungsumgebung dieselben Techniken und Konzepte anzuwenden. Sie müssen ein grundlegendes Verständnis von mathematischen Vektoren haben.


Einführung

Warteschlange, Im Rahmen dieses Tutorials steht der Prozess, in einer Reihe zu stehen und eine Reihe von Zeichen zu bilden, die geduldig darauf warten, irgendwo anzukommen. Wenn sich der erste in der Linie bewegt, folgt der Rest, wodurch ein Muster entsteht, das aussieht wie ein Zug, der Wagen zieht. Beim Warten sollte ein Charakter niemals die Linie verlassen.

Um das Verhalten der Warteschlange zu veranschaulichen und die verschiedenen Implementierungen zu zeigen, ist eine Demo mit einer "Warteschlangenszene" der beste Weg. Ein gutes Beispiel ist ein Raum mit KI-gesteuerten Entitäten, die alle versuchen, den Raum zu verlassen und durch die Tür zu gehen:


Boids verlassen den Raum und gehen durch die Tür ohne das Warteschlangenverhalten. Klicken Sie, um Kräfte anzuzeigen.

Diese Szene wurde mit zwei zuvor beschriebenen Verhaltensweisen erstellt: Such- und Kollisionsvermeidung.

Die Türöffnung besteht aus zwei rechteckigen Hindernissen, die nebeneinander angeordnet sind und zwischen sich einen Spalt bilden (Türöffnung). Die Charaktere suchen einen Punkt dahinter. Wenn dort, werden die Zeichen am unteren Rand des Bildschirms positioniert.

Im Moment sieht die Szene ohne das Warteschlangenverhalten aus wie eine Horde Wilden, die sich gegenseitig auf den Kopf treten, um am Ziel anzukommen. Wenn wir fertig sind, wird die Menge den Platz problemlos verlassen und Reihen bilden.


Voraus sehen

Die erste Fähigkeit, die ein Charakter erlangen muss, um in einer Reihe zu stehen, besteht darin, herauszufinden, ob jemand vor ihm ist. Anhand dieser Informationen kann er entscheiden, ob er fortfahren oder aufhören soll, sich zu bewegen.

Trotz der fortgeschritteneren Möglichkeiten, die Nachbarn zu prüfen, verwende ich eine vereinfachte Methode, die auf der Entfernung zwischen einem Punkt und einem Zeichen basiert. Dieser Ansatz wurde im Verhalten zur Vermeidung von Kollisionen verwendet, um auf zukünftige Hindernisse zu prüfen:


Testen Sie mit dem Ahead-Punkt auf Nachbarn.

Ein Punkt genannt voraus wird vor dem Zeichen projiziert. Wenn der Abstand zwischen diesem Punkt und einem Nachbarzeichen kleiner oder gleich ist MAX_QUEUE_RADIUS, es bedeutet, dass jemand voraus ist und der Charakter aufhören muss sich zu bewegen.

Das voraus Punkt wird wie folgt berechnet (Pseudocode):

 // Sowohl qa als auch voraus sind mathematische Vektoren. Qa = normalisieren (Geschwindigkeit) * MAX_QUEUE_AHEAD; voraus = qa + Position;

Die Geschwindigkeit, die auch die Richtung des Charakters angibt, wird normalisiert und mit skaliert MAX_QUEUE_AHEAD um einen neuen Vektor zu erzeugen qa. Wann qa wird dem hinzugefügt Position Vektor ist das Ergebnis einen Punkt vor dem Zeichen und eine Entfernung von MAX_QUEUE_AHEAD Einheiten davon entfernt.

All dies kann in der getNeighborAhead () Methode:

 private Funktion 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); für (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; 

Die Methode prüft den Abstand zwischen voraus Punkt und alle anderen Zeichen, wobei das erste Zeichen zurückgegeben wird, dessen Entfernung kleiner oder gleich ist MAX_QUEUE_AHEAD. Wird kein Zeichen gefunden, kehrt die Methode zurück Null.


Erstellen der Warteschlangenmethode

Wie bei allen anderen Verhaltensweisen ist das Warteschlange Kraft wird durch eine Methode namens berechnet Warteschlange():

 private function queue (): Vector3D var neighbour: Boid = getNeighborAhead (); if (nachbar! = null) // TODO: Maßnahmen ergreifen, da der Nachbar voraus ist zurückgeben new Vector3D (0, 0); 

Das Ergebnis von getNeighborAhead () in in der Variablen gespeichert Nachbar. Ob Nachbarn! = null es bedeutet, dass jemand voraus ist; Ansonsten ist der Weg frei.

Das Warteschlange(), Wie alle anderen Verhaltensmethoden muss eine Kraft zurückgegeben werden, bei der es sich um die Lenkkraft handelt, die mit der Methode selbst zusammenhängt. Warteschlange() wird jetzt eine Kraft ohne Stärke zurückgeben, also keine Effekte erzeugen.

Das aktualisieren() Die Methode aller Charaktere in der Eingangsszene ist bisher (Pseudo-Code):

 public function update (): void var doorway: Vector3D = getDoorwayPosition (); Lenken = Suchen (Tür); // suche die Türöffnung lenkung = lenkung + collisionAvoidance (); // vermeide Hindernisse beim Lenken = Lenken + Warteschlange (); // Warteschlange auf dem Weg control = truncate (Lenkung, MAX_FORCE); Lenkung = Lenkung / Masse; Geschwindigkeit = verkürzt (Geschwindigkeit + Lenkung, MAX_SPEED); Position = Position + Geschwindigkeit;

Schon seit Warteschlange() Gibt eine Nullkraft zurück, bewegen sich die Zeichen weiter, ohne Zeilen zu bilden. Es ist an der Zeit, dass sie Maßnahmen ergreifen, wenn ein Nachbar unmittelbar vor der Tür entdeckt wird.


Einige Worte zum Anhalten der Bewegung

Das Lenkverhalten basiert auf Kräften, die sich ständig ändern, sodass das gesamte System sehr dynamisch wird. Je nach Implementierung ist es umso schwieriger, einen bestimmten Kraftvektor zu lokalisieren und zu löschen, je mehr Kräfte involviert sind.

Die in dieser Lenkverhaltensreihe verwendete Implementierung fügt alle Kräfte zusammen. Um eine Kraft aufzuheben, muss diese folglich neu berechnet, invertiert und erneut zum aktuellen Lenkkraftvektor addiert werden.

Das passiert so ziemlich im Ankunftsverhalten, bei dem die Geschwindigkeit aufgehoben wird, damit der Charakter aufhört, sich zu bewegen. Was passiert aber, wenn mehr Kräfte zusammenwirken, wie Kollisionsvermeidung, Flucht und mehr??

In den folgenden Abschnitten werden zwei Ideen vorgestellt, wie ein Charakter aufhören kann, sich zu bewegen. Der erste Ansatz verwendet einen "harten Stopp" -Ansatz, der direkt auf den Geschwindigkeitsvektor wirkt und alle anderen Lenkkräfte ignoriert. Der zweite verwendet einen Kraftvektor, genannt Bremse, alle anderen Lenkkräfte auf grausame Weise aufheben und den Charakter schließlich zum Stillstand bringen.


Stoppen der Bewegung: "Hard Stop"

Mehrere Lenkkräfte basieren auf dem Geschwindigkeitsvektor des Charakters. Wenn sich dieser Vektor ändert, sind alle anderen Kräfte betroffen, wenn sie neu berechnet werden. Die Idee des "harten Stopps" ist ziemlich einfach: Wenn ein Zeichen vor Ihnen liegt, "verkleinern" wir den Geschwindigkeitsvektor:

 private function queue (): Vector3D var neighbour: Boid = getNeighborAhead (); if (nachbar! = null) Velocity.scaleBy (0.3);  return new Vector3D (0, 0); 

Im obigen Code steht der Geschwindigkeit Vektor wird auf skaliert 30% der aktuellen Größe (Länge), während ein Charakter voraus ist. Infolgedessen wird die Bewegung drastisch reduziert, sie wird jedoch letztendlich wieder zu ihrer normalen Größe zurückkehren, wenn sich der Charakter, der den Weg blockiert, bewegt.

Das ist einfacher zu verstehen, wenn analysiert wird, wie Bewegung berechnet wird jedes Update:

 Geschwindigkeit = verkürzt (Geschwindigkeit + Lenkung, MAX_SPEED); Position = Position + Geschwindigkeit;

Wenn die Geschwindigkeit Die Kraft schrumpft immer weiter, ebenso die Lenkung Kraft, weil es auf dem basiert Geschwindigkeit Macht. Es entsteht ein Teufelskreis, der für einen extrem niedrigen Wert endet Geschwindigkeit. Dann hört der Charakter auf, sich zu bewegen.

Wenn der Verkleinerungsvorgang beendet ist, wird bei jedem Spielupdate der Wert erhöht Geschwindigkeit ein wenig Vektor, der das beeinflußt Lenkung Kraft auch. Eventuell bringen mehrere Updates beide Geschwindigkeit und Lenkung Vektor zurück zu ihren normalen Größen.

Der "Hardstop" -Ansatz führt zu folgendem Ergebnis:


Warteschlangenverhalten mit "Hard Stop" -Ansatz. Klicken Sie, um Kräfte anzuzeigen.

Obwohl dieses Ergebnis ziemlich überzeugend ist, fühlt es sich wie ein "Roboter" an. Eine echte Menschenmenge hat normalerweise keine leeren Felder zwischen ihren Mitgliedern.


Bewegung stoppen: Bremskraft

Der zweite Ansatz zum Anhalten der Bewegung versucht, ein weniger "robotisches" Ergebnis zu erzielen, indem alle aktiven Lenkkräfte mit a aufgehoben werden Bremse Macht:

 private Funktionswarteschlange (): Vector3D var v: Vector3D = Velocity.clone (); var Bremse: Vector3D = neuer Vector3D (); var nachbar: Boid = getNeighborAhead (); if (nachbar! = null) brake.x = -steering.x * 0,8; bremsen.y = -lenken.y * 0,8; v.scaleBy (-1); Bremse = Bremse.add (v);  bremse zurück; 

Anstatt das zu schaffen Bremse Kraft durch erneute Berechnung und Umkehrung jeder der aktiven Lenkkräfte, Bremse wird basierend auf dem Strom berechnet Lenkung Vektor, der alle zum Moment hinzugefügten Lenkkräfte enthält:


Darstellung der Bremskraft.

Das Bremse Kraft erhält beides sein x und y Komponenten aus dem Lenkung Kraft, aber umgekehrt und mit einer Skala von 0,8. Es bedeutet das Bremse hat 80% der Größenordnung von Lenkung und zeigt in die entgegengesetzte Richtung.

Spitze: Verwendung der Lenkung Gewalt direkt ist gefährlich. Ob Warteschlange() ist das erste Verhalten, das auf einen Charakter angewendet wird Lenkung Kraft wird "leer" sein. Als Konsequenz, Warteschlange() muss aufgerufen werden nach allen anderen Steuerungsmethoden, damit es auf das vollständige und endgültige zugreifen kann Lenkung Macht.

Das Bremse force muss auch die Geschwindigkeit des Charakters aufheben. Das geschieht durch Hinzufügen -Geschwindigkeit zum Bremse Macht. Danach die Methode Warteschlange() kann das Finale zurückgeben Bremse Macht.

Das Ergebnis der Verwendung der Bremskraft ist das Folgende:


Warteschlangenverhalten mit dem Bremskraftansatz. Klicken Sie, um Kräfte anzuzeigen.

Überlappung der Charaktere

Der Bremsansatz erzeugt ein natürlicheres Ergebnis als der "Roboter", da alle Charaktere versuchen, die leeren Felder zu füllen. Es stellt sich jedoch ein neues Problem: Zeichen überlappen sich.

Um dies zu beheben, kann der Bremsansatz mit einer leicht modifizierten Version des "Hardstop" -Ansatzes verbessert werden:

 private Funktionswarteschlange (): Vector3D var v: Vector3D = Velocity.clone (); var Bremse: Vector3D = neuer Vector3D (); var nachbar: Boid = getNeighborAhead (); if (nachbar! = null) brake.x = -steering.x * 0,8; bremsen.y = -lenken.y * 0,8; v.scaleBy (-1); Bremse = Bremse.add (v); if (Entfernung (Position, Nachbarposition) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Ein neuer Test wird verwendet, um Nachbarn in der Nähe zu überprüfen. Diesmal anstelle der voraus Um den Abstand zu messen, prüft der neue Test den Abstand zwischen den Zeichen Position Vektor:


Überprüfen Sie in der Nähe befindliche Nachbarn innerhalb des Radius MAX_QUEUE_RADIUS, der an der Position zentriert ist, und nicht am Vorauspunkt.

Dieser neue Test prüft, ob in der Nähe Zeichen vorhanden sind MAX_QUEUE_RADIUS Radius, aber jetzt ist es zentriert Position Vektor. Wenn sich jemand in Reichweite befindet, bedeutet dies, dass die Umgebung zu überfüllt wird und die Zeichen sich wahrscheinlich überschneiden.

Die Überlappung wird durch die Skalierung von reduziert Geschwindigkeit Vektor bis zu 30% seiner aktuellen Größe bei jeder Aktualisierung. Genau wie beim "Hardstop" -Ansatz wird das verkleinert Geschwindigkeit Vektor reduziert die Bewegung drastisch.

Das Ergebnis scheint weniger "roboterhaft" zu sein, ist aber nicht ideal, da sich die Charaktere an der Tür noch überschneiden:


Warteschlangenverhalten mit "Hardstop" und Bremskraft kombiniert. Klicken Sie, um Kräfte anzuzeigen.

Trennung hinzufügen

Obwohl die Charaktere auf überzeugende Weise versuchen, die Tür zu erreichen, füllen sie alle leeren Räume, wenn der Pfad eng wird, kommen sie sich an der Tür zu nahe.

Dies kann durch Hinzufügen einer Trennkraft gelöst werden:

 private Funktionswarteschlange (): Vector3D var v: Vector3D = Velocity.clone (); var Bremse: Vector3D = neuer Vector3D (); var nachbar: Boid = getNeighborAhead (); if (nachbar! = null) brake.x = -steering.x * 0,8; bremsen.y = -lenken.y * 0,8; v.scaleBy (-1); Bremse = Bremse.add (v); Bremse = Bremse.add (Trennung ()); if (Entfernung (Position, Nachbarposition) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Früher im Führungsverhalten der Führungslinien verwendet, wurde die Trennkraft zu der hinzugefügt Bremse force bewirkt, dass die Charaktere aufhören, sich zu bewegen, während sie versuchen, sich voneinander zu entfernen.

Das Ergebnis ist ein überzeugendes Publikum, das versucht, die Tür zu erreichen:


Warteschlangenverhalten mit "Hardstop", Bremskraft und Trennung kombiniert. Klicken Sie, um Kräfte anzuzeigen.

Fazit

Das Verhalten der Warteschlange ermöglicht es den Charakteren, in einer Reihe zu stehen und geduldig auf das Ziel zu warten. Einmal angekommen, versucht ein Charakter nicht, durch das Springen von Positionen zu "betrügen". es wird sich nur bewegen, wenn sich der Charakter davor bewegt.

Die in diesem Tutorial verwendete Eingangsszene zeigte, wie vielseitig und anpassungsfähig dieses Verhalten sein kann. Einige Änderungen führen zu unterschiedlichen Ergebnissen, die sich an die unterschiedlichsten Situationen anpassen lassen. Das Verhalten kann auch mit anderen kombiniert werden, z. B. Kollisionsvermeidung.

Ich hoffe, dass Ihnen dieses neue Verhalten gefallen hat und Sie damit beginnen werden, Ihr Publikum mit bewegenden Menschenmassen zu füllen!