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.
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:
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.
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:
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
.
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.
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.
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:
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.
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:
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:
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:
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:
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:
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!