- Ein Flowfield ist eine zweidimensionale Zellstruktur und hat eine frei wählbare Anzahl von Zellen, die alle einen frei wählbaren Zellradius besitzen.
- Ein Flowfield ist unsichtbar (und nur für dieses Tutorial visualisiert).
- Es scannt alle innerhalb des Feldes liegenden Objekte und notiert sich je Objekt die „Kosten“ (zwischen 1 und 255). Je „teurer“ ein Objekt, desto eher wird das Flowfield versuchen, einen Weg zum Ziel zu generieren, der an diesem Objekt vorbeiführt.
- Wird eine der Zellen als Ziel markiert, berechnet das Flowfield pro Zelle die bestmögliche Wegrichtung, die ein Objekt einschlagen müsste, um zu diesem Ziel zu gelangen.
- Jedes in der Welt befindliche Objekt kann nun selbst entscheiden, ob es dieser Wegrichtung folgt oder nicht.
- In jeder Welt kann jeweils nur ein Flowfield generiert werden.
Im unten abgebildeten Beispiel ist das gelbe Objekt das Ziel und die pinke Kugel die Spielfigur:
Die Zielposition kann in Echtzeit verändert werden. Auch die Hindernisse dürfen sich währenddessen bewegen:
Wie konfiguriert man ein Flowfield?
Für dieses Beispiel wird vorausgesetzt, dass zuvor die GameObject
-Klassen „Impassable
„, „Wall
„, „Player
“ und „Enemy
“ angelegt wurden.
Anlegen einer Welt-Klasse
using KWEngine3; using KWEngine3.GameObjects; using KWEngine3.Helper; using OpenTK.Mathematics; using OpenTK.Windowing.GraphicsLibraryFramework; namespace FlowfieldTutorial { public class FlowFieldWorld : World { public override void Act() { } public override void Prepare() { SetCameraPosition(0, 15, 0); SetCameraTarget(0, 0, 0); SetColorAmbient(1, 1, 1); Impassable impassable1= new Impassable(); impassable1.SetScale(4, 1, 2); impassable1.SetColor(1, 1, 1); impassable1.FlowFieldCost = 255; // Höchste Kosten bedeutet: unüberwindbar! impassable1.IsCollisionObject = true; AddGameObject(impassable1); Wall wall1 = new Impassable(); wall1.SetScale(4, 1, 2); wall1.SetColor(1, 1, 1); wall1.SetPosition(0, 0, 4); wall1.FlowFieldCost = 255; // Höchste Kosten bedeutet: unüberwindbar! wall1.IsCollisionObject = true; AddGameObject(wall1); Player p = new Player(); p.SetPosition(-4.5f, 0.5f, 3.5f); p.SetColor(1, 1, 0); p.IsCollisionObject = true; AddGameObject(p); Enemy e = new Enemy(); e.SetModel("KWSphere"); e.SetPosition(4.5f, 0.5f, 3.5f); e.SetColor(1, 0, 1); e.IsCollisionObject = true; AddGameObject(e); // Hier findet das Anlegen und die Konfiguration des Flowfields statt: FlowField f = new FlowField( 0, // x-Position der Feldmitte 0, // y-Position der Feldmitte 0, // z-Position der Feldmitte 10, // Anzahl der Zellen in X-Richtung 8, // Anzahl der Zellen in Z-Richtung 0.5f, // Radius je Zelle 1, // Höhe des Flowfields (für Wegfindung irrelevant) typeof(Impassable), typeof(Wall) // Liste der Klassen, die das Flowfield beachten soll ); f.IsVisible = true; // Visualisiert das Flowfield (zu Debugging-Zwecken) SetFlowField(f); // Verankert das Flowfield-Objekt in der Welt } } }
Act()
-Methode in der Welt zum Festlegen des Ziels
Im obigen Code-Beispiel fehlt der Inhalt der Act()
-Methode. Dieser wird jetzt hier separat vorgestellt.
public override void Act() { // Wenn die linke Maustaste betätigt wird... if(Mouse.IsButtonPressed(MouseButton.Left)) { // ...ermittle die 3D-Koordinaten des Mauszeigers... Vector3 cursorPos = HelperIntersection.GetMouseIntersectionPointOnPlane( Plane.XZ, // ...auf der XZ-Ebene... 0 // ...auf der Höhe 0. ); // Hole anschließend die Referenz auf das aktuelle Flowfield: FlowField f = GetFlowField(); // Wenn das FlowField nicht leer ist und außerdem noch die // Mauszeigerkoordinaten innerhalb des Flowfields liegen,... if(f != null && f.ContainsXZ(cursorPos); { // ...setze im Flowfield die für diese Koordinaten // zuständige Zelle als Ziel: f.SetTarget(cursorPosWorld); } } }
Eine Klasse muss sich jetzt an diesem Flowfield orientieren
using KWEngine3.GameObjects; using KWEngine3.Helper; using OpenTK.Mathematics; using OpenTK.Windowing.GraphicsLibraryFramework; namespace FlowFieldTutorial { public class Player : GameObject { public override void Act() { // Erstelle eine Variable, die die aktuelle Bewegungsrichtung festlegt: Vector3 myNewDirection = Vector3.Zero; // Erfrage die Referenz auf das Flowfield in der aktuellen Welt: FlowField f = CurrentWorld.GetFlowField(); // Ist die Referenz nicht leer, ... if(f != null) { // ...prüfe, ob die eigene Position innerhalb des Flowfields liegt // und ob das Flowfield gerade ein gesetztes Ziel hat: if(f.ContainsXZ(this.Position) && f.HasTarget) { // Überschreibe die Bewegungsrichtung mit der vom Flowfield // vorgeschlagenen Bewegung: myNewDirection = f.GetBestDirectionForPosition(this.Position); } } // Wenn die Bewegung nicht 0 ist, dann bewege die Figur entlang dieser // Richtung mit der Beispielgeschwindigkeit 0.01f: if(myNewDirection != Vector3.Zero) { MoveAlongVector(myNewDirection, 0.01f); } // Die folgenden Zeilen sind nur normale Kollisionsbehandlung und haben // mit dem Flowfield selbst nichts zu tun: List<Intersection> intersections = GetIntersections(); foreach(Intersection intersection in intersections) { MoveOffset(intersection.MTV); } } } }
Was muss ich tun, wenn sich Objekte innerhalb des Flowfields verändern?
Das Flowfield muss neu berechnet werden, wenn…
- …sich Objekte innerhalb des Flowfields in ihrer Position, Größe oder Rotation verändern.
- …sich die Kosten eines Objekts innerhalb des Flowfields verändern.
FlowField f = KWEngine.CurrentWorld.GetFlowField(); if(f != null) { f.Update(); }
Wichtig:
Weil das Aktualisieren des Flowfield viele Berechnungen erfordert, wird jedes Flowfield intern mit lediglich 30FPS aktualisiert.
Schreibe einen Kommentar