Unity-Lösung zum Erreichen bewegter Ziele

Was Sie erstellen werden

Bei der Entwicklung von Spielen, die ein Aktionselement beinhalten, müssen wir oft einen Weg finden, mit einem sich bewegenden Ziel zu kollidieren. Solche Szenarien können typischerweise als "ein bewegliches Ziel treffen" -Problem bezeichnet werden. Dies ist besonders bei Tower Defense-Spielen oder bei Raketenbefehlen wie Spielen von Bedeutung. Möglicherweise müssen Sie eine KI oder einen Algorithmus erstellen, der die Bewegung des Feindes ermitteln und darauf schießen kann. 

Mal sehen, wie wir dieses spezielle Problem in Unity lösen können.

1. Das Missile Command Game

Für dieses spezielle Tutorial betrachten wir ein Raketenspiel. Im Spiel haben wir einen Turm auf dem Boden, der Raketen auf einen ankommenden Asteroiden abfeuert. Wir sollten nicht zulassen, dass der Asteroid den Boden berührt. 

Das Spiel ist auf Tap-Basis, bei dem wir den Revolver zielen müssen. Mit menschlicher Unterstützung ist die Spielmechanik ziemlich unkompliziert, da der Turm nur zielen und schießen muss. Stellen Sie sich jedoch vor, dass der Turm automatisch auf ankommende Asteroiden schießen muss. 

Die Herausforderungen für die Auto-Firing-KI

Der Turm muss herausfinden, wie viele Asteroiden sich dem Boden nähern. Sobald alle Asteroiden in der Nähe sind, müsste eine Bedrohungsanalyse durchgeführt werden, um zu bestimmen, auf welche Asteroiden sie sich auswirken sollen. Ein sich langsam bewegender Asteroid ist eine geringere Bedrohung als ein sich schnell bewegender. Auch ein Asteroid, der sich näher am Boden befindet, ist eine unmittelbare Bedrohung. 

Diese Probleme können durch den Vergleich von Geschwindigkeit und Position der ankommenden Asteroiden gelöst werden. Wenn wir erst einmal festgelegt haben, auf welches Ziel wir zielen, erreichen wir das schwierigste Problem. Wann soll der Turm schießen? In welchem ​​Winkel soll es schießen? Wann sollte die Rakete nach dem Abschuss explodieren? Die dritte Frage wird relevant, weil die Raketenexplosion auch den Asteroiden zerstören kann und einen größeren Wirkungsradius hat.

Um das Problem zu vereinfachen, kann der Turm entscheiden, sofort zu schießen. Dann müssen wir nur den Zündwinkel und die Entfernung der Detonation herausfinden. Es kann auch vorkommen, dass der Asteroid den Bereich, in dem er getroffen werden könnte, bereits passiert hat, was bedeutet, dass es keine Lösung gibt!

Sie sollten die mitgelieferte Unity-Quelle herunterladen, um die Lösung in Aktion zu sehen. Wir werden sehen, wie wir diese Lösung ableiten.

2. Die Lösung

Wir werden eine kleine Auffrischung unserer Mathematik an der High School machen, um die Lösung zu finden. Es ist sehr einfach und erfordert das Lösen einer quadratischen Gleichung. Eine quadratische Gleichung sieht so aus axˆ2 + bx + c = 0, woher x ist die zu findende Variable und kommt mit der höchsten Potenz von 2 vor. 

Problem analysieren

Versuchen wir, unser Problem schematisch darzustellen. 

Die grüne Linie zeigt den vorhergesagten Weg, dem der Asteroid folgen soll. Wenn es sich um eine gleichförmige Bewegung handelt, bewegt sich der Asteroid mit konstanter Geschwindigkeit. Unser Turm muss die Rakete auf dem blauen Pfad drehen und abfeuern, damit er zu einem späteren Zeitpunkt mit dem Asteroiden kollidieren kann.

Bei einer gleichförmigen Bewegung ist die von einem Objekt zurückgelegte Entfernung das Produkt aus Zeit und Geschwindigkeit des Objekts, d. H. D = T x S, woher D steht für die Entfernung, T ist die Zeit, um zu reisen D, und S ist die Reisegeschwindigkeit. Angenommen, unser Asteroid und die Raketen würden definitiv kollidieren, können wir die Entfernung der blauen Linie, gefolgt von der Rakete, zeitlich bestimmen t. In der gleichen Zeit t, Unser Asteroid erreicht auch die gleiche Position. 

Im Wesentlichen zur gleichen Zeit t, Der Asteroid erreicht die Kollisionsposition von seiner aktuellen Position aus, und der Flugkörper wird in derselben Zeit auch dieselbe Kollisionsposition erreichen t. Also zur Zeit t, Sowohl der Asteroid als auch die Rakete würden sich in derselben Entfernung vom Turm befinden, wie sie miteinander kollidieren würden.

Geben Sie Math ein

Wir können den Abstand vom Turm zum Asteroiden und zur Rakete zu diesem Zeitpunkt gleich setzen t um unsere quadratische Gleichung mit der Variablen abzuleiten t. Betrachten Sie zwei Punkte auf einer zweidimensionalen Ebene mit Koordinaten (x1, y1) und (x2, y2). Die Distanz D dazwischen kann man mit der untenstehenden Gleichung berechnen.

D2 = (x2-x1) ˆ2 + (y2-y1) ˆ2

Wenn wir die Revolverposition als angeben (Tx, Ty), die Geschossgeschwindigkeit als s und die unbekannte Kollisionsposition als (X, Y), dann kann die obige Gleichung umgeschrieben werden als:

D2 = (X-Tx) ˆ2 + (Y-Ty) ˆ2; D = s * t;

woher t ist die Zeit, die der Flugkörper benötigt, um die Entfernung zurückzulegen D. Wenn wir beide gleich setzen, erhalten wir unsere erste Gleichung für Unbekannte X und Y mit einem anderen unbekannten t.

s 2 * t 2 = (X-Tx) 2 + (Y-Ty) 2

Wir wissen, dass der Asteroid auch dieselbe Kollisionsstelle erreicht (X, Y) in der gleichen Zeit t, und wir haben die folgenden Gleichungen unter Verwendung der horizontalen und vertikalen Komponenten des Geschwindigkeitsvektors des Asteroiden. Wenn die Geschwindigkeit des Asteroiden mit bezeichnet werden kann (Vx, Vy) und die aktuelle Position als (Axt, Ay), dann das unbekannte X und Y kann wie folgt gefunden werden.

X = t * Vx + Ax; Y = t * Vy + Ay;

Wenn Sie diese in der früheren Gleichung einsetzen, erhalten Sie eine quadratische Gleichung mit einer einzigen Unbekannten t

s · 2 · t · 2 = ((t · Vx + Ax) - Tx) · 2 + ((t · Vy + Ay) · Ty) · 2;

Ähnliche Begriffe erweitern und kombinieren:

s · 2 · t · 2 = (t · Vx + Ax) · 2 + Tx · 2 · Tx · (t · Vx + Ax) + (t · Vy + Ay) · 2 + Ty · 2 · Ty · (t · Vy + Ay); sˆ2 * tˆ2 = t V2 * Vxˆ2 + Axˆ2 + 2 * t * Vx * Ax + Txˆ2 - 2 * Tx * (t * Vx + Ax) + tˆ2 * Vyˆ2 + Ayˆ2 + 2 * t * Vy * Ay + Tyˆ2 - 2 * Ty * (t * Vy + Ay); sˆ2 * tˆ2 = t V2 * Vxˆ2 + Axˆ2 + 2 * t * Vx * Ax + Txˆ2 - 2 * Tx * t * Vx - 2 * Tx * Ax + tˆ2 * Vyˆ2 + Ayˆ2 + 2 * t * Vy * Ay + Tyˆ2 - 2 * Ty * t * Vy - 2 * Ty * Ay; 0 = (Vxˆ2 + Vyˆ2 - sˆ2) * tˆ2 + 2 * (Vx * Ax - Tx * Vx + Vy * Ay - Ty * Vy) * t + Ayˆ2 + Tyˆ2 - 2 * Ty * Ay + Axˆ2 + Txˆ2 - 2 * Tx * Axe; (Vxˆ2 + Vyˆ2 - sˆ2) * tˆ2 + 2 * (Vx * (Ax - Tx) + Vy * (Ay - Ty)) · t + (Ay - Ty) 2 + (Ax - Tx) ˆ2 = 0;

Darstellen der Macht von zwei als ˆ2 und das Multiplikationssymbol als * mag das Obige wie Hieroglyphen aussehen lassen, aber es läuft im Wesentlichen auf die endgültige quadratische Gleichung hinaus axˆ2 + bx + c = 0, woher x ist die Variable t, ein ist Vxˆ2 + Vyˆ2 - sˆ2, b ist 2 * (Vx * (Axt - Tx) + Vy * (Ay - Ty)), und c ist (Ay - Ty) ˆ2 + (Axe - Tx) ˆ2. Wir haben die folgenden Gleichungen in der Ableitung verwendet.

(a + b) ˆ2 = aˆ2 + 2 * a * b + bˆ2; (a-b) ˆ2 = aˆ2-2 * a * b + bˆ2;

Die quadratische Gleichung lösen

Um eine quadratische Gleichung zu lösen, müssen wir die Diskriminante berechnen D mit der Formel:

D = b2 - 4 * a * c;

Wenn die Diskriminante kleiner ist als 0 dann gibt es keine lösung, wenn ja 0 dann gibt es eine einzige Lösung, und wenn es eine positive Zahl ist, gibt es zwei Lösungen. Die Lösungen werden nach den unten angegebenen Formeln berechnet.

t1 = (-b + Quadrat (D)) / 2 * a; t2 = (-b - Quadrat (D)) / 2 * a;

Mit diesen Formeln können wir Werte für die zukünftige Zeit ermitteln t wann wird die kollision passieren. Ein negativer Wert für t bedeutet, dass wir die Gelegenheit zum Schießen verpasst haben. Die Unbekannten X und Y kann durch Ersetzen des Wertes von gefunden werden t in ihren jeweiligen Gleichungen.

X = t * Vx + Ax; Y = t * Vy + Ay;

Sobald wir den Kollisionspunkt kennen, können wir unseren Turm drehen, um die Rakete abzufeuern, die auf jeden Fall den sich bewegenden Asteroiden treffen würde t Sekunden.

3. Implementierung in Unity

Für das Beispielprojekt Unity habe ich die Sprite-Erstellungsfunktion der neuesten Unity-Version verwendet, um die erforderlichen Platzhalterelemente zu erstellen. Dies kann mit erreicht werden Erstellen> Sprites> Wie nachfolgend dargestellt.

Wir haben ein Spiel-Skript namens MissileCmdAI die an der Szenenkamera befestigt ist. Es enthält den Hinweis auf das Sprit-Revit, den Raketen-Fertigteil und den Asteroiden-Fertigteil. ich benutze SimplePool von quill18 zur Pflege der Objektpools für Raketen und Asteroiden. Es kann auf GitHub gefunden werden. Es gibt Komponentenskripte für Flugkörper und Asteroiden, die an ihre Prefabs angehängt werden und deren Bewegung nach dem Loslassen handhaben.

Die Asteroiden

Asteroiden werden zufällig in fester Höhe, aber zufälliger horizontaler Position erzeugt und an einer zufälligen horizontalen Position mit einer zufälligen Geschwindigkeit auf den Boden geschleudert. Die Häufigkeit des Laichens von Asteroiden wird mit a gesteuert AnimationKurve. Das SpawnAsteroid Methode in der MissileCmdAI Skript sieht wie folgt aus:

void SpawnAsteroid () GameObject asteroid = SimplePool.Spawn (asteroidPrefab, Vector2.one, Quaternion.identity); Asteroid asteroidScript = asteroid.GetComponent(); asteroidScript.Launch (); SetNextSpawn (); 

Das Starten Methode in der Asteroid Klasse wird unten gezeigt.

public void Launch () // Platziere den Asteroid oben mit zufälligem x und starte ihn unten mit dem Zufallszeichen x bl = Camera.main.ScreenToWorldPoint (new Vector2 (10,0)); br = Camera.main.ScreenToWorldPoint (neuer Vector2 (Screen.width-20,0)); tl = Camera.main.ScreenToWorldPoint (neuer Vector2 (0, Screen.height)); tr = Camera.main.ScreenToWorldPoint (neuer Vector2 (Screen.width, Screen.height)); transform.localScale = Vector2.one * (0.2f + Random.Range (0.2f, 0.8f)); AsteroidSpeed ​​= Random.Range (AsteroidMinSpeed, AsteroidMaxSpeed); asteroidPos.x = Random.Range (tl.x, tr.x); asteroidPos.y = tr.y + 1; destination.y = bl.y; destination.x = Random.Range (bl.x, br.x); Vektor2 Geschwindigkeit = AsteroidSpeed ​​* ((destination-asteroidPos) .normalized); transform.position = asteroidPos; asteroidRb.velocity = Geschwindigkeit; // Eine Geschwindigkeit auf Rigidbody setzen, um sie in Bewegung zu setzen deployDistance = Vector3.Distance (AsteroidPos, Ziel); // Wenn Sie diese große Entfernung zurückgelegt haben, kehren Sie zum Pool zurück void Update () if (Vector2. Abstand (transform.position, asteroidPos)> deployDistance) // Wenn wir die festgelegte Entfernung zurückgelegt haben, kehren Sie zum Pool zurück. ReturnToPool ();  void OnTriggerEnter2D (Collider2D-Projektil) if (projectile.gameObject.CompareTag ("missile")) // Kollision mit Rakete prüfen, Rückkehr zum Pool ReturnToPool (); 

Wie im gesehen Aktualisieren Methode, sobald der Asteroid die vorgegebene Entfernung zum Boden zurückgelegt hat, deployDistance, es würde zu seinem Objektpool zurückkehren. Im Wesentlichen bedeutet dies, dass es mit dem Boden kollidiert ist. Dies würde auch bei einer Kollision mit der Rakete passieren.

Das Targeting

Damit das automatische Targeting funktionieren kann, müssen Sie die entsprechende Methode häufig aufrufen, um den ankommenden Asteroid zu finden und darauf zu zielen. Dies geschieht im MissileCmdAI Skript in seiner Start Methode.

InvokeRepeating ("FindTarget", 1, aiPollTime); // ai-Code-Abfrage setzen

Das Ziel finden Diese Methode durchläuft alle in der Szene vorhandenen Asteroiden, um die nächstgelegenen und schnellsten Asteroiden zu finden. Einmal gefunden, ruft es dann die AcquireTargetLock Methode, um unsere Berechnungen anzuwenden.

void FindTarget () // finde den schnellsten und nächstgelegenen Asteroid GameObject [] aArr = GameObject.FindGameObjectsWithTag ("Asteroid"); GameObject closeAsteroid = null; Asteroid fastAsteroid = null; Asteroid Asteroid; foreach (GameObject in aArr gehen) if (go.transform.position.y(); if (schnellsteAsteroid == null) // finde die schnellste schnellsteAsteroid = Asteroid;  else if (asteroid.asteroidSpeed> fastAsteroid.asteroidSpeed) fastAsteroid = Asteroid;  // wenn wir ein nahestes Ziel haben, andernfalls das schnellste Ziel (wenn nahstersterster! = null) AcquireTargetLock (nächsterAsteroid);  else if (fastAsteroid! = null) AcquireTargetLock (fastAsteroid.gameObject); 

AcquireTargetLock Hier geschieht die Magie, wenn wir unsere Fähigkeiten zur Lösung quadratischer Gleichungen anwenden, um den Zeitpunkt der Kollision zu ermitteln t.

void AcquireTargetLock (GameObject targetAsteroid) Asteroid asteroidScript = targetAsteroid.GetComponent(); Vector2 targetVelocity = asteroidScript.asteroidRb.velocity; float a = (targetVelocity.x * targetVelocity.x) + (targetVelocity.y * targetVelocity.y) - (missileSpeed ​​* missileSpeed); float b = 2 * (targetVerocity.x * (targetAsteroid.gameObject.transform.position.x-turret.transform.position.x) + targetVelocity.y * (targetAsteroid.gameObject.transform.position.y.turret.transform.position) .y)); float c = ((targetAsteroid.gameObject.transform.position.x-turret.transform.position.x) * (targetAsteroid.gameObject.transform.position.x-turret.transform.position.x)) + ((targetAsteroid.gameObject) .transform.position.y-turret.transform.position.y) * (targetAsteroid.gameObject.transform.position.y-turret.transform.position.y)); Schwimmscheibe = b * b - (4 * a * c); if (disc<0) Debug.LogError("No possible hit!"); else float t1=(-1*b+Mathf.Sqrt(disc))/(2*a); float t2=(-1*b-Mathf.Sqrt(disc))/(2*a); float t= Mathf.Max(t1,t2);// let us take the larger time value float aimX=(targetVelocity.x*t)+targetAsteroid.gameObject.transform.position.x; float aimY=targetAsteroid.gameObject.transform.position.y+(targetVelocity.y*t); RotateAndFire(new Vector2(aimX,aimY));//now position the turret   public void RotateAndFire(Vector2 deployPos)//AI based turn & fire float turretAngle=Mathf.Atan2(deployPos.y-turret.transform.position.y,deployPos.x-turret.transform.position.x)*Mathf.Rad2Deg; turretAngle-=90;//art correction turret.transform.localRotation=Quaternion.Euler(0,0,turretAngle); FireMissile(deployPos, turretAngle);//launch missile  void FireMissile(Vector3 deployPos, float turretAngle) float deployDist= Vector3.Distance(deployPos,turret.transform.position);//how far is our target GameObject firedMissile=SimplePool.Spawn(missilePrefab,turret.transform.position,Quaternion.Euler(0,0,turretAngle)); Rigidbody2D missileRb=firedMissile.GetComponent(); Missile missileScript = firedMissile.GetComponent(); missileScript.LockOn (deployDist); missileRb.velocity = missileSpeed ​​* firedMissile.transform.up; // Rakete wird bereits in die erforderliche Richtung gedreht

Sobald wir den Aufschlagpunkt gefunden haben, können wir leicht die Entfernung berechnen, die der Flugkörper zurücklegen muss, um den Asteroiden zu treffen, der durch den Asteroiden passiert wird deployDist Variable auf die LockOn Methode der Rakete. Der Flugkörper verwendet diesen Wert, um zu seinem Objektpool zurückzukehren, sobald er diese Entfernung wie der Asteroid zurückgelegt hat. Bevor dies geschieht, hätte es definitiv den Asteroiden getroffen, und die Kollisionsereignisse wären ausgelöst worden.

Fazit

Sobald wir es implementiert haben, sieht das Ergebnis fast magisch aus. Durch die Reduzierung der aiPollTime Wert, wir können es zu einem unbesiegbaren KI-Turm machen, der jeden Asteroiden abschießen würde, es sei denn, die Asteroidengeschwindigkeit nähert sich unserer Raketengeschwindigkeit oder ist höher als diese. Die hergeleitete Ableitung kann verwendet werden, um eine Vielzahl ähnlicher Probleme zu lösen, die in Form einer quadratischen Gleichung dargestellt werden könnten. 

Ich möchte, dass Sie weiter experimentieren, indem Sie der Bewegung des Asteroiden und der Rakete die Wirkung der Schwerkraft hinzufügen. Dies würde die Bewegung in eine Projektilbewegung umwandeln und die entsprechenden Gleichungen würden sich ändern. Viel Glück.

Beachten Sie auch, dass die Einheit eine aktive Wirtschaft hat. Es gibt viele andere Produkte, die Ihnen beim Aufbau Ihres Projekts helfen. Die Art der Plattform macht sie auch zu einer großartigen Option, mit der Sie Ihre Fähigkeiten verbessern können. In jedem Fall können Sie sehen, was auf dem Envato Marketplace verfügbar ist.