Steuerungsverhalten verstehen Bewegungsmanager

Lenkverhalten ist ideal, um realistische Bewegungsmuster zu erstellen, aber sie sind noch größer, wenn Sie sie einfach steuern, verwenden und kombinieren können. In diesem Lernprogramm werde ich die Implementierung eines Bewegungsmanagers für alle zuvor besprochenen Verhaltensweisen besprechen und behandeln.

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.


Lenkkräfte kombinieren

Wie zuvor diskutiert, erzeugt jedes Lenkverhalten eine resultierende Kraft (als "Lenkkraft" bezeichnet), die zu dem Geschwindigkeitsvektor addiert wird. Die Richtung und Größe dieser Kraft wird den Charakter antreiben und ihn nach einem Muster bewegen (suchen, fliehen, wandern usw.). Die allgemeine Berechnung lautet:

 Lenken = Suchen (); // Dies kann ein beliebiges Verhalten sein lenken = verkürzen (Lenkung, max_force) Lenkung = Lenkung / Massengeschwindigkeit = verkürzt (Geschwindigkeit + Lenkung, max_geschwindigkeit) position = position + Geschwindigkeit

Da es sich bei der Lenkkraft um einen Vektor handelt, kann er zu jedem anderen Vektor addiert werden (genau wie die Geschwindigkeit). Die eigentliche "Magie" besteht jedoch darin, dass Sie mehrere Lenkkräfte zusammen hinzufügen können - es ist so einfach wie:

 lenkung = nichts (); // der Nullvektor, was "Zero-Force-Betrag" bedeutet Lenkung = Lenkung + Suchvorgang (); Lenken = Lenken + Fliehen (); (…) Lenkung = verkürzt (Lenkung, max_force) Lenkung = Lenkung / Massengeschwindigkeit = verkürzt (Geschwindigkeit + Lenkung, max_speed) Position = Position + Geschwindigkeit

Die kombinierten Lenkkräfte ergeben einen Vektor, der darstellt alles diese Kräfte. Im obigen Code-Snippet bewirkt die resultierende Lenkkraft, dass der Charakter währenddessen etwas sucht zur selben Zeit es wird etwas fliehen sonst.

Nachfolgend einige Beispiele für Lenkkräfte, die zu einer einzigen Lenkkraft kombiniert werden:


Lenkkräfte kombiniert.

Komplexe Muster mühelos

Die Kombination der Lenkkräfte erzeugt mühelos extrem komplexe Bewegungsmuster. Stellen Sie sich vor, wie schwer es wäre, Code zu schreiben, damit ein Zeichen etwas sucht, aber gleichzeitig einen bestimmten Bereich meidet, ohne Vektoren und Kräfte zu verwenden?

Dies würde die Berechnung von Entfernungen, Flächen, Pfaden, Graphen und dergleichen erfordern. Wenn sich Dinge bewegen, müssen alle diese Berechnungen von Zeit zu Zeit wiederholt werden, da sich die Umgebung ständig ändert.

Beim Lenkverhalten sind alle Kräfte dynamisch. Sie sollen bei jedem Spiel-Update berechnet werden, sodass sie natürlich und nahtlos auf Änderungen in der Umgebung reagieren.

Die folgende Demo zeigt Schiffe, die den Mauszeiger suchen, aber gleichzeitig aus der Bildschirmmitte fliehen:


Die Schiffe suchen den Mauszeiger (grau), fliehen jedoch aus der Bildschirmmitte (orange). Klicken Sie, um Kräfte anzuzeigen.

Bewegungsmanager

Um auf einfache und einfache Weise mehrere Lenkverhalten gleichzeitig zu verwenden, a Bewegungsmanager ist praktisch. Die Idee ist, eine "Black Box" zu erstellen, die in jede vorhandene Entität eingefügt werden kann, um diese Verhaltensweisen ausführen zu können.

Der Manager hat einen Verweis auf die Entität, in die er eingesteckt ist (der "Host"). Der Manager stellt dem Host eine Reihe von Methoden zur Verfügung, z suchen() und fliehen(). Jedes Mal, wenn solche Methoden aufgerufen werden, aktualisiert der Manager seine internen Eigenschaften, um einen Lenkkraftvektor zu erzeugen.

Nachdem der Manager alle Aufrufe verarbeitet hat, addiert er die resultierende Lenkkraft zum Geschwindigkeitsvektor des Hosts. Dies ändert den Geschwindigkeitsvektor des Hosts entsprechend dem aktiven Verhalten.

Die folgende Abbildung zeigt die Architektur:


Bewegungsmanager: Plugin-Architektur.

Dinge generisch machen

Der Manager verfügt über eine Reihe von Methoden, die jeweils ein bestimmtes Verhalten darstellen. Jedes Verhalten muss mit verschiedenen externen Informationen versorgt werden, damit es funktionieren kann.

Das Suchverhalten benötigt zum Beispiel einen Punkt in dem Raum, der zur Berechnung der Lenkkraft zu diesem Ort verwendet wird. Verfolgung benötigt mehrere Informationen von seinem Ziel, z. B. aktuelle Position und Geschwindigkeit. Ein Punkt im Raum kann als eine Instanz von ausgedrückt werden Punkt oder Vector2D, beide ziemlich gängigen Klassen in jedem Rahmen.

Das im Verfolgungsverhalten verwendete Ziel kann jedoch alles sein. Um den Bewegungsmanager generisch genug zu machen, muss er ein Ziel erhalten, das unabhängig von seinem Typ einige "Fragen" beantworten kann, z.Was ist deine momentane Geschwindigkeit??". Unter Verwendung einiger Prinzipien der objektorientierten Programmierung kann dies mit erreicht werden Schnittstellen.

Angenommen die Schnittstelle IBoid beschreibt eine Entität, die vom Bewegungsmanager gehandhabt werden kann. Jede Klasse im Spiel kann Steuerungsverhalten verwenden, sofern sie implementiert wird IBoid. Diese Schnittstelle hat die folgende Struktur:

 öffentliche Schnittstelle IBoid function getVelocity (): Vector3D; function getMaxVelocity (): Anzahl; Funktion getPosition (): Vector3D; function getMass (): Anzahl; 

Bewegungsmanager-Struktur

Nun, da der Manager mit allen Spielelementen auf generische Weise interagieren kann, kann seine Grundstruktur erstellt werden. Der Manager besteht aus zwei Eigenschaften (der resultierenden Lenkkraft und der Hostreferenz) und einem Satz öffentlicher Methoden, eine für jedes Verhalten:

 öffentliche Klasse SteeringManager öffentliche Steuerung Var: Vector3D; public var host: IBoid; // Die öffentliche Funktion des Konstruktors SteeringManager (host: IBoid) this.host = host; this.steering = new Vector3D (0, 0);  // Die öffentliche API (eine Methode für jedes Verhalten) public function seek (target: Vector3D, slowingRadius: Number = 20): void  public function flucht (target: Vector3D): void  öffentliche Funktion wandern (): void  public function evade (Ziel: IBoid): void  öffentliche Funktionsverfolgung (Ziel: IBoid): void  // Die Aktualisierungsmethode. // Sollte aufgerufen werden, nachdem alle Verhalten aufgerufen wurden. Public function update (): void  // Die interne Lenkkraft zurücksetzen. public function reset (): void  // Die interne private API-Funktion doSeek (Ziel: Vector3D, slowingRadius: Number = 0): Vector3D  private Funktion doFlee (Ziel: Vector3D): Vector3D  private Funktion doWander (): Vector3D  private Funktion doEvade (Ziel: IBoid): Vector3D  private Funktion DoPursuit (Ziel: IBoid): Vector3D 

Wenn der Manager instanziiert wird, muss er eine Referenz auf den Host erhalten, an den er angeschlossen ist. Dadurch kann der Manager den Geschwindigkeitsvektor des Hosts entsprechend den aktiven Verhaltensweisen ändern.

Jedes Verhalten wird durch zwei Methoden dargestellt, eine öffentliche und eine private. Am Beispiel von seek:

 public function seek (Ziel: Vector3D, langsamerRadius: Zahl = 20): void  private Funktion doSeek (Ziel: Vektor3D, langsamerRadius: Zahl = 0): Vector3D 

Die Öffentlichkeit suchen() wird aufgerufen, um dem Manager mitzuteilen, dass er dieses Verhalten anwenden soll. Die Methode hat keinen Rückgabewert und ihre Parameter beziehen sich auf das Verhalten selbst, z. B. einen Punkt im Raum. Unter der Haube die private Methode doSeek () wird aufgerufen, und sein Rückgabewert, die berechnete Lenkkraft für dieses bestimmte Verhalten, wird zum Manager hinzugefügt Lenkung Eigentum.

Der folgende Code demonstriert die Implementierung von seek:

 // Die Publizierungsmethode. // Empfängt ein zu suchendes Ziel und einen verlangsamenden Radius (wird verwendet, um die Ankunft durchzuführen). public function seek (Ziel: Vector3D, langsamerRadius: Zahl = 20): void Steering.incrementBy (doSeek (Ziel, langsamerRadius));  // Die tatsächliche Implementierung von search (einschließlich des Ankunftscodes) der privaten Funktion doSeek (Ziel: Vector3D, slowingRadius: Number = 0): Vector3D var force: Vector3D; var Entfernung: Anzahl; erwünscht = targetSubtract (host.getPosition ()); Abstand = gewünschte Länge; erwünscht.normalize (); wenn (Entfernung) <= slowingRadius)  desired.scaleBy(host.getMaxVelocity() * distance/slowingRadius);  else  desired.scaleBy(host.getMaxVelocity());  force = desired.subtract(host.getVelocity()); return force; 

Alle anderen Verhaltensmethoden werden auf ähnliche Weise implementiert. Das Verfolgung() Die Methode sieht zum Beispiel so aus:

 öffentliche Funktionsverfolgung (Ziel: IBoid): void Steering.incrementBy (doPursuit (Ziel));  private Funktion doPursuit (Ziel: IBoid): Vector3D distance = target.getPosition (). subtract (host.getPosition ()); var updatesNeeded: Number = distance.length / host.getMaxVelocity (); var tv: Vector3D = target.getVelocity (). clone (); tv.scaleBy (updatesNeeded); targetFuturePosition = target.getPosition (). clone (). add (tv); return doSeek (targetFuturePosition); 

Wenn Sie den Code aus den vorherigen Tutorials verwenden, müssen Sie ihn nur in der Form von anpassen Verhalten() und Verhalten (), Sie können dem Bewegungsmanager hinzugefügt werden.


Anwenden und Aktualisieren von Lenkkräften

Jedes Mal, wenn eine Verhaltensmethode aufgerufen wird, wird die resultierende Kraft zu der des Managers hinzugefügt Lenkung Eigentum. Infolgedessen sammelt diese Eigenschaft alle Lenkkräfte.

Wenn alle Verhalten aufgerufen wurden, muss der Manager die aktuelle Lenkkraft auf die Geschwindigkeit des Hosts anwenden, damit er sich entsprechend dem aktiven Verhalten bewegt. Es wird im durchgeführt aktualisieren() Methode des Bewegungsmanagers:

 public function update (): void var Velocity: Vector3D = host.getVelocity (); var position: Vector3D = host.getPosition (); abschneiden (Lenken, MAX_FORCE); Steering.scaleBy (1 / host.getMass ()); Geschwindigkeit.inkrementBy (Lenkung); abschneiden (Geschwindigkeit, host.getMaxVelocity ()); position.incrementBy (Geschwindigkeit); 

Die obige Methode muss vom Host (oder von einer anderen Spieleinheit) aufgerufen werden, nachdem alle Verhalten aufgerufen wurden. Andernfalls ändert der Host niemals seinen Geschwindigkeitsvektor, um ihn an das aktive Verhalten anzupassen.


Verwendungszweck

Nehmen wir an, eine Klasse namens Beute sollte sich mit dem Lenkverhalten bewegen, hat aber momentan weder einen Lenkcode noch den Bewegungsmanager. Seine Struktur wird so aussehen:

 öffentliche Klasse Beute public var position: Vector3D; public var Geschwindigkeit: Vector3D; öffentliche Var-Masse: Anzahl; öffentliche Funktion Beute (posX: Number, posY: Number, totalMass: Number) position = new Vector3D (posX, posY); Geschwindigkeit = neuer Vector3D (-1, -2); Masse = Gesamtmasse; x = Position.x; y = Position.y;  public function update (): void Velocity.normalize (); Velocity.scaleBy (MAX_VELOCITY); Geschwindigkeit.SkalaBy (1 / Masse); abschneiden (Geschwindigkeit, MAX_VELOCITY); position = position.add (Geschwindigkeit); x = Position.x; y = Position.y; 

Mit dieser Struktur können die Klasseninstanzen mithilfe der Euler-Integration verschoben werden, genau wie die erste Demo des Lernprogramms für Suchvorgänge. Um den Manager verwenden zu können, benötigt er eine Eigenschaft, die auf den Bewegungsmanager verweist, und er muss das implementieren IBoid Schnittstelle:

 öffentliche Klasse Prey implementiert IBoid public var position: Vector3D; public var Geschwindigkeit: Vector3D; öffentliche Var-Masse: Anzahl; öffentliche Var-Steuerung: SteeringManager; öffentliche Funktion Beute (posX: Number, posY: Number, totalMass: Number) position = new Vector3D (posX, posY); Geschwindigkeit = neuer Vector3D (-1, -2); Masse = Gesamtmasse; Lenkung = neuer SteeringManager (dies); x = Position.x; y = Position.y;  public function update (): void Velocity.normalize (); Velocity.scaleBy (MAX_VELOCITY); Geschwindigkeit.SkalaBy (1 / Masse); abschneiden (Geschwindigkeit, MAX_VELOCITY); position = position.add (Geschwindigkeit); x = Position.x; y = Position.y;  // Nachfolgend sind die Methoden aufgeführt, die von der IBoid-Schnittstelle benötigt werden. öffentliche Funktion getVelocity (): Vector3D Rücklaufgeschwindigkeit;  public function getMaxVelocity (): Number return 3;  public function getPosition (): Vector3D return position;  public function getMass (): Number return mass; 

Das aktualisieren() Die Methode muss entsprechend geändert werden, damit auch der Manager aktualisiert werden kann:

 public function update (): void // Lass die Beute herumlaufen… Steering.wander (); // Aktualisieren Sie den Manager, damit der Geschwindigkeitsvektor der Beute geändert wird. // Der Manager führt auch die Euler-Intergration aus und ändert // den "position" -Vektor. Steueraktualisierung (); // Nachdem der Manager seine internen Strukturen aktualisiert hat, müssen wir nur // unsere Position entsprechend dem Vektor "position" aktualisieren. x = Position.x; y = Position.y; 

Alle Verhalten können gleichzeitig verwendet werden, solange alle Methodenaufrufe vor dem Manager ausgeführt werden aktualisieren() Aufruf, der die angesammelte Lenkkraft auf den Geschwindigkeitsvektor des Hosts anwendet.

Der folgende Code demonstriert eine andere Version von Prey's aktualisieren() Methode, aber dieses Mal wird eine Position in der Karte gesucht und ein anderer Charakter (beide gleichzeitig) umgangen:

 public function update (): void var destination: Vector3D = getDestination (); // der Ort, an dem var hunter gesucht werden soll: IBoid = getHunter (); // hol die Entität, die uns jagt // Suche nach dem Ziel und entkomme dem Jäger (gleichzeitig!) Steering.seek (Ziel); lenkung.evade (jäger); // Aktualisieren Sie den Manager, damit der Geschwindigkeitsvektor der Beute geändert wird. // Der Manager führt auch die Euler-Intergration aus und ändert // den "position" -Vektor. Steueraktualisierung (); // Nachdem der Manager seine internen Strukturen aktualisiert hat, müssen wir nur // unsere Position entsprechend dem Vektor "position" aktualisieren. x = Position.x; y = Position.y; 

Demo

Die folgende Demo zeigt ein komplexes Bewegungsmuster, bei dem mehrere Verhalten miteinander kombiniert werden. Es gibt zwei Arten von Charakteren in der Szene: die Jäger und das Beute.

Der Jäger wird es tun verfolgen eine Beute, wenn es nahe genug kommt; es wird so lange verfolgen, wie die Ausdauer vorhanden ist; Wenn ihm die Kraft ausgeht, wird die Verfolgung unterbrochen und der Jäger wird es tun wandern bis es seine Ausdauer wieder erlangt.

Hier ist der Jäger aktualisieren() Methode:

 public function update (): void if (resting && stamina ++> = MAX_STAMINA) resting = false;  if (Beute! = null &&! ruhen) Lenkung.Pursuit (Beute); Ausdauer - = 2; wenn (Ausdauer <= 0)  prey = null; resting = true;   else  steering.wander(); prey = getClosestPrey(position);  steering.update(); x = position.x; y = position.y; 

Die Beute wird wandern unbegrenzt. Wenn der Jäger zu nahe kommt, wird er es tun ausweichen. Wenn sich der Mauszeiger in der Nähe befindet und kein Jäger in der Nähe ist, wird die Beute angezeigt suchen der Mauszeiger.

Hier ist die Beute aktualisieren() Methode:

 öffentliche Funktionsaktualisierung (): void var distance: Number = Vector3D.distance (position, Game.mouse); hunter = getHunterWithinRange (Position); if (Jäger! = null) Steering.evade (Jäger);  if (Entfernung <= 300 && hunter == null)  steering.seek(Game.mouse, 30);  else if(hunter == null) steering.wander();  steering.update(); x = position.x; y = position.y; 

Das Endergebnis (Grau ist Wandern, Grün ist Suchen, Orange wird verfolgt, Rot ist Ausweichen):


Die Jagd. Klicken Sie, um Kräfte anzuzeigen.

Fazit

Ein Bewegungsmanager ist sehr nützlich, um mehrere Lenkverhalten gleichzeitig zu steuern. Die Kombination solcher Verhaltensweisen kann sehr komplexe Bewegungsmuster erzeugen, die es einer Spieleinheit ermöglichen, eine Sache gleichzeitig zu suchen, während sie einer anderen entgeht.

Ich hoffe, dass Ihnen das in diesem Tutorial besprochene Managementsystem gefallen hat und es in Ihren Spielen verwendet. Danke fürs Lesen! Vergessen Sie nicht, auf dem neuesten Stand zu bleiben, indem Sie uns auf Twitter, Facebook oder Google folgen+.