Unity-oplossing voor het raken van bewegende doelen

Wat je gaat creëren

Bij het ontwikkelen van games waarbij een actie-element is betrokken, moeten we vaak een manier bedenken om in botsing te komen met een bewegend doelwit. Zulke scenario's kunnen meestal een 'probleem met een bewegend doelwit' worden genoemd. Dit is met name prominent in tower defense-games of raketcommando-achtige games. Misschien moeten we een AI of algoritme maken waarmee de beweging van de vijand kan worden vastgesteld en ernaar kan worden geschoten. 

Laten we eens kijken hoe we dit specifieke probleem kunnen oplossen, deze keer in Unity.

1. Het raketcommando

Voor deze specifieke tutorial zullen we een raketcommandogame overwegen. In het spel hebben we een torentje op de grond dat raketten afvuurt op een inkomende asteroïde. We moeten niet toestaan ​​dat de asteroïde de grond raakt. 

Het spel is gebaseerd op een tik, waarbij we moeten tikken om het torentje te richten. Met menselijke hulp zijn de spelmechanismen vrij eenvoudig, omdat het torentje alleen maar hoeft te richten en te schieten. Maar stel je voor dat het torentje automatisch moet vuren op binnenkomende asteroïden. 

De uitdagingen voor automatisch aansteken AI

Het torentje moet weten hoeveel asteroïden de grond naderen. Zodra het een verzameling van alle naderende asteroïden heeft, moet het een dreigingsanalyse uitvoeren om te bepalen welke te richten. Een langzaam bewegende asteroïde is een kleinere bedreiging dan een snelbewegende. Ook een asteroïde die dichter bij de grond staat, is ook een onmiddellijke dreiging. 

Deze problemen kunnen worden opgelost door de snelheid en de positie van de binnenkomende asteroïden te vergelijken. Zodra we hebben bepaald welke we moeten targeten, bereiken we het meest gecompliceerde probleem. Wanneer zou het torentje moeten vuren? Met welke hoek zou het moeten vuren? Wanneer moet de raket ontploffen na het schieten? De derde vraag wordt relevant omdat de raketexplosie ook de asteroïde kan vernietigen en ook een grotere straal van effect heeft.

Om het probleem te vereenvoudigen, kan het torentje besluiten om meteen te schieten. Dan moeten we alleen de hoek van vuren en afstand van detonatie achterhalen. Ook kan het voorkomen dat de asteroïde het gebied is gepasseerd waar hij geraakt zou kunnen worden, wat betekent dat er geen oplossing is!

Download de bron die bij deze zelfstudie is geleverd om de oplossing in actie te zien. We zullen zien hoe we die oplossing afleiden.

2. De oplossing

We gaan een beetje opfrissen van onze middelbare school wiskunde om de oplossing te vinden. Het is heel eenvoudig en omvat het oplossen van een kwadratische vergelijking. Een kwadratische vergelijking ziet eruit als ax2 + bx + c = 0, waar X is de variabele die te vinden is en het gebeurt met de hoogste macht van 2. 

Het probleem analyseren

Laten we proberen ons probleem schematisch weer te geven. 

De groene lijn geeft het voorspelde pad weer dat moet worden gevolgd door de asteroïde. Omdat we te maken hebben met uniforme beweging, beweegt de asteroïde met constante snelheid. Ons torentje moet draaien en de raket afvuren langs het blauwe pad zodat het in de toekomst in botsing komt met de asteroïde.

Voor uniforme beweging is de afstand die een object aflegt het product van de tijd en de snelheid van het object, d.w.z.. D = T x S, waar D staat voor de afstand, T is de tijd om te reizen D, en S is de snelheid van reizen. Ervan uitgaande dat onze asteroïde en de raketten zeker zouden botsen, kunnen we de afstand van de blauwe lijn gevolgd door de raket in termen van tijd vinden t. Tegelijkertijd t, onze asteroïde zal ook dezelfde positie bereiken. 

In wezen, in dezelfde tijd t, de asteroïde zal vanuit de huidige positie de aanvaringspositie bereiken en de raket zal ook dezelfde botspositie bereiken in dezelfde tijd t. Dus op tijd t, zowel de asteroïde als de raket zouden zich op dezelfde afstand van het torentje bevinden als ze tegen elkaar zouden botsen.

Voer wiskunde in

We kunnen de afstand van het torentje naar de asteroïde en de raket op dit toekomstige tijdstip gelijkstellen t om onze kwadratische vergelijking af te leiden van de variabele t. Beschouw twee punten op een tweedimensionaal vlak met coördinaten (X1, y1) en (X2, y2). De afstand D tussen hen kan worden berekend met behulp van de onderstaande vergelijking.

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

Als we de positie van het torentje aangeven als (Tx, Ty), de raketsnelheid als s en de onbekende aanvaringspositie als (X, Y), dan kan de bovenstaande vergelijking worden herschreven als:

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

waar t is de tijd die de raket nodig heeft om de afstand af te leggen D. Beide gelijkend, krijgen we onze eerste vergelijking voor onbekenden X en Y met nog een onbekend t.

s2 * t2 = (X-Tx) 2 + (Y-Ty) 2

We weten dat de asteroïde dezelfde botslocatie bereikt (X, Y) tegelijkertijd t, en we hebben de volgende vergelijkingen met behulp van de horizontale en verticale componenten van de snelheidsvector van de asteroïde. Als de snelheid van de asteroïde kan worden aangegeven met (Vx, Vy) en de huidige positie als (Ax, Ay), dan het onbekende X en Y kan worden gevonden zoals hieronder.

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

Het substitueren van deze in de eerdere vergelijking geeft ons een kwadratische vergelijking met een enkele onbekende t

s2 * t2 = ((t * Vx + Ax) -Tx) 2 + ((t * Vy + Ay) -Ty) 2;

Uitbreiden en combineren van vergelijkbare termen:

s2 * t2 = (t * Vx + Ax) 2 + Tx2 - 2 * Tx * (t * Vx + Ax) + (t * Vy + Ay) 2 + Ty2 - 2 * Ty * (t * Vy + Ay); s2 * t2 = t2 * Vx2 + Ax2 + 2 * t * Vx * Ax + Tx2 - 2 * Tx * (t * Vx + Ax) + t2 * Vy2 + Ay2 + 2 * t * Vy * Ay + Ty2 - 2 * Ty * (t * Vy + Ay); s2 * t2 = t2 * Vx2 + Ax2 + 2 * t * Vx * Ax + Tx2 - 2 * Tx * t * Vx - 2 * Tx * Ax + t2 * Vy2 + Ay2 + 2 * t * Vy * Ay + Ty2 - 2 * Ty * t * Vy - 2 * Ty * Ay; 0 = (Vx2 + Vy2 - s2) * t2 + 2 * (Vx * Ax - Tx * Vx + Vy * Ay - Ty * Vy) * t + Ay2 + Ty2 - 2 * Ty * Ay + Ax2 + Tx2 - 2 * Tx * Ax; (Vx2 + Vy2 - s2) * t2 + 2 * (Vx * (Ax - Tx) + Vy * (Ay - Ty)) * t + (Ay - Ty) 2 + (Ax - Tx) 2 = 0;

Vertegenwoordigt de kracht van twee als 2 en het vermenigvuldigingssymbool als * Misschien hebben het bovenstaande er als hiërogliefen uit gezien, maar het komt in feite neer op de laatste kwadratische vergelijking ax2 + bx + c = 0, waar X is de variabele t, een is Vx2 + Vy2 - s2, b is 2 * (Vx * (Ax - Tx) + Vy * (Ay - Ty)), en c is (Ay - Ty) 2 + (Ax - Tx) 2. We hebben de onderstaande vergelijkingen gebruikt in de afleiding.

(a + b) 2 = a2 + 2 * a * b + b2; (a-b) 2 = a2 - 2 * a * b + b2;

De kwadratische vergelijking oplossen

Om een ​​kwadratische vergelijking op te lossen, moeten we de discriminant berekenen D met behulp van de formule:

D = b2 - 4 * a * c;

Als de discriminant kleiner is dan 0 dan is er geen oplossing, als dat zo is 0 dan is er een enkele oplossing, en als het een positief getal is, dan zijn er twee oplossingen. Oplossingen worden berekend met behulp van de onderstaande formules.

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

Met behulp van deze formules kunnen we waarden voor de toekomst vinden t wanneer de botsing zal gebeuren. Een negatieve waarde voor t betekent dat we de kans om te vuren hebben gemist. De onbekenden X en Y kan worden gevonden door de waarde van te vervangen t in hun respectieve vergelijkingen.

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

Zodra we het botspunt kennen, kunnen we ons torentje draaien om de raket af te vuren, wat zeker de bewegende asteroïde zou raken. t secs.

3. Implementeren in Unity

Voor het voorbeeld-Unity-project heb ik de functie voor het maken van sprite van de nieuwste Unity-versie gebruikt om de benodigde tijdelijke activa te maken. Dit is toegankelijk met Maak> Sprites> zoals hieronder getoond.

We hebben een game script genaamd MissileCmdAI die aan de scènecamera is bevestigd. Het bevat de referentie naar de torentje sprite, raket prefab en asteroïde prefab. ik gebruik SimplePool door quill18 om de objectpools voor raketten en asteroïden te onderhouden. Het is te vinden op GitHub. Er zijn componentscripts voor raketten en asteroïden die aan hun prefabs zijn bevestigd en hun beweging afhandelen zodra ze zijn vrijgegeven.

De asteroïden

Asteroïden worden willekeurig uitgezet op vaste hoogte maar willekeurige horizontale positie en worden met een willekeurige snelheid op een willekeurige horizontale positie op de grond geslingerd. De frequentie van asteroïde spawning wordt geregeld met behulp van een AnimationCurve. De SpawnAsteroid methode in de MissileCmdAI script ziet er als volgt uit:

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

De Lancering methode in de Asteroïde klasse wordt hieronder getoond.

public void Launch () // plaats de asteroïde bovenaan met willekeurige x en start deze naar onder met willekeurig x bl = Camera.main.ScreenToWorldPoint (new Vector2 (10,0)); br = Camera.main.ScreenToWorldPoint (nieuwe Vector2 (Screen.width-20,0)); tl = Camera.main.ScreenToWorldPoint (nieuwe Vector2 (0, Schermhoogte)); tr = Camera.main.ScreenToWorldPoint (nieuwe 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); Vector2 velocity = asteroidSpeed ​​* ((destination-asteroidPos) .normalized); transform.position = asteroidPos; asteroidRb.velocity = velocity; // stel een velocity in op rigidbody om het in beweging te zetten deployDistance = Vector3.Distance (asteroidPos, destination); // na het reizen van deze afstand, keer terug naar pool void Update () if (Vector2. Afstand (transform.position, asteroidPos)> deployDistance) // zodra we de ingestelde afstand hebben afgelegd, keren we terug naar pool ReturnToPool ();  void OnTriggerEnter2D (Collider2D-projectiel) if (projectile.gameObject.CompareTag ("raket")) // controleer botsing met raket, ga terug naar pool ReturnToPool (); 

Zoals te zien in de Bijwerken methode, zodra de asteroïde de vooraf bepaalde afstand tot de grond heeft afgelegd, deployDistance, het zou terugkeren naar zijn object-pool. In wezen betekent dit dat het in botsing is gekomen met de grond. Het zou hetzelfde doen bij een botsing met de raket.

De targeting

Om ervoor te zorgen dat de automatische targeting werkt, moeten we vaak de bijbehorende methode bellen om de inkomende asteroïde te vinden en te targeten. Dit wordt gedaan in de MissileCmdAI script in zijn Begin methode.

InvokeRepeating ("FindTarget", 1, aiPollTime); // stel ai-code opvragen in

De FindTarget methode lus door alle asteroïden aanwezig in de scène om de dichtstbijzijnde en snelste asteroïden te vinden. Als het eenmaal is gevonden, roept het de AcquireTargetLock methode om onze berekeningen toe te passen.

void FindTarget () // vind snelste en dichtstbijzijnde asteroïde GameObject [] aArr = GameObject.FindGameObjectsWithTag ("asteroïde"); GameObject closestAsteroid = null; Asteroïde fastestAsteroid = null; Asteroïde asteroïde; foreach (GameObject go in aArr) if (go.transform.position.y(); if (fastestAsteroid == null) // vind snelst snelste asteroïde = asteroïde;  else if (asteroid.asteroidSpeed> fastestAsteroid.asteroidSpeed) fastestAsteroid = asteroid;  // als we een doel hebben dat het dichtst bij ligt, anders richten we ons het snelst als (het dichtstAsteroid! = null) AcquireTargetLock (closestAsteroid);  else if (fastestAsteroid! = null) AcquireTargetLock (fastestAsteroid.gameObject); 

AcquireTargetLock is waar de magie gebeurt als we onze kwadratische oplossingen voor vergelijkingsvaardigheden toepassen om het tijdstip van de botsing te vinden 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 * (targetVelocity.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)); vlotterschijf = 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; // raket wordt al in de gewenste richting gedraaid

Zodra we het punt van impact hebben gevonden, kunnen we eenvoudig de afstand berekenen die de raket moet afleggen om de asteroïde te raken, die wordt doorgegeven via de deployDist variabele op de Slot op methode van de raket. De raket gebruikt deze waarde om terug te keren naar zijn objectpool zodra hij deze afstand op dezelfde manier heeft afgelegd als de asteroïde. Voordat dit gebeurt, zou het zeker de asteroïde hebben geraakt en zouden de botsinggebeurtenissen zijn geactiveerd.

Conclusie

Zodra we het implementeren, ziet het resultaat er bijna magisch uit. Door het verminderen van aiPollTime waarde, we kunnen er een onoverwinnelijk AI-torentje van maken dat elke asteroïde zou neerschieten tenzij de asteroïdesnelheid dichtbij of hoger komt dan onze raketsnelheid. De afleiding die we hebben gevolgd, kan worden gebruikt om een ​​aantal vergelijkbare problemen op te lossen die kunnen worden weergegeven in de vorm van een kwadratische vergelijking. 

Ik zou graag willen dat je verder experimenteert door het effect van de zwaartekracht aan de beweging van de asteroïde en de raket toe te voegen. Dit zou de beweging in projectielbeweging veranderen en de overeenkomstige vergelijkingen zouden veranderen. Succes.

Merk ook op dat Unity een actieve economie heeft. Er zijn veel andere producten die u helpen uw project uit te bouwen. De aard van het platform maakt het ook een geweldige optie van waaruit je je vaardigheden kunt verbeteren. Hoe het ook zij, u kunt zien wat we beschikbaar hebben op de Envato Marketplace.