Maak een Neon Vector Shooter voor iOS The Warping Grid

In deze serie tutorials laat ik je zien hoe je een Geometry Wars-geïnspireerde twin-stick shooter maakt met neon graphics, gekke partikeleffecten en geweldige muziek, voor iOS met C ++ en OpenGL ES 2.0. In dit laatste deel voegen we het achtergrondraster toe dat verdraait op basis van de actie in de game.

Overzicht

In de serie tot nu toe hebben we de gameplay, virtuele gamepad en deeltjeseffecten gemaakt. In dit laatste deel maken we een dynamisch, kromtrekkend achtergrondraster.


Waarschuwing: Loud!

Zoals vermeld in het vorige deel, merk je een dramatische daling in framerate als je de code nog steeds uitvoert in de debug-modus. Zie die tutorial voor details over het overschakelen naar release-modus voor volledige compilatie-optimalisatie (en een snellere build).

The Warping Grid

Een van de coolste effecten in Geometry Wars is het kromme achtergrondraster. We zullen bekijken hoe je een vergelijkbaar effect kunt creëren in Shape Blaster. Het raster reageert op kogels, zwarte gaten en de speler die uit elkaar valt. Het is niet moeilijk om te maken en het ziet er geweldig uit.

We maken het rooster met behulp van een veersimulatie. Bij elke kruising van het raster plaatsen we een klein gewicht en bevestigen we aan weerszijden een veer. Deze veren zullen alleen trekken en nooit duwen, net als een rubberen band. Om het raster in positie te houden, worden de massa's aan de rand van het raster op hun plaats verankerd. Hieronder is een diagram van de lay-out.


We zullen een klasse aanmaken genaamd rooster om dit effect te creëren. Voordat we echter aan het raster zelf werken, moeten we twee helperklassen maken: De lente en PointMass.

De PointMass-klasse

De PointMass klasse vertegenwoordigt de massa's waaraan we de veren zullen bevestigen. Veren sluiten nooit rechtstreeks op andere veren aan; in plaats daarvan oefenen ze een kracht uit op de massa's die ze verbinden, die op hun beurt andere bronnen kunnen rekken.

 klasse PointMass beschermd: tVector3f mA-versnelling; zweven mDamping; public: tVector3f mPosition; tVector3f mVelocity; zweven mInverseMass; publiek: PointMass (); PointMass (const tVector3f & position, float invMass); void applyForce (const tVector3f & force); ongeldig verhogen Demping (zweeffactor); ongeldige update (); ; PointMass :: PointMass (): mAcceleration (0,0,0), mDamping (0.98f), mPosition (0), mVelocity (0,0.0), mInverseMass (0)  PointMass :: PointMass (const tVector3f & position , float invMass): mAcceleration (0,0,0), mDamping (0,98f), mPosition (positie), mVelocity (0,0,0), mInverseMass (invMass)  void PointMass :: applyForce (const tVector3f & force) mAcceleration + = force * mInverseMass;  void PointMass :: increaseDamping (float factor) mDamping * = factor;  void PointMass :: update () mVelocity + = mAcceleration; mPosition + = mVelocity; mAcceleration = tVector3f (0,0,0); if (mVelocity.lengthSquared () < 0.001f * 0.001f)  mVelocity = tVector3f(0,0,0);  mVelocity *= mDamping; mDamping = 0.98f; 

Er zijn een paar interessante punten over deze klasse. Merk allereerst op dat het de omgekeerde van de massa, 1 / massa. Dit is vaak een goed idee in natuurkundige simulaties omdat natuurkundige vergelijkingen de neiging hebben om vaker het omgekeerde van de massa te gebruiken, en omdat het ons een gemakkelijke manier geeft om oneindig zware, onverplaatsbare objecten weer te geven door de inverse massa op nul te zetten.

Ten tweede bevat de klasse ook een demping variabel. Dit wordt grofweg gebruikt als wrijving of luchtweerstand; het vertraagt ​​geleidelijk de massa. Dit zorgt ervoor dat het raster uiteindelijk tot rust komt en verhoogt ook de stabiliteit van de veersimulatie.

De PointMass :: update () methode verplaatst het werk van het verplaatsen van de punt elk frame. Het begint met symplectische Euler-integratie, wat betekent dat we de versnelling toevoegen aan de snelheid en vervolgens de bijgewerkte snelheid aan de positie toevoegen. Dit verschilt van de standaard Euler-integratie waarin we de snelheid zouden updaten na het updaten van de positie.

Tip: Symplectische Euler is beter voor de lentesimulaties omdat het energie bespaart. Als je gewone Euler-integratie gebruikt en veren zonder demping maakt, zullen ze de neiging hebben om verder te rekken en verder te stuiteren als ze energie krijgen, en uiteindelijk je simulatie te verbreken.

Na het bijwerken van de snelheid en de positie, controleren we of de snelheid erg klein is en als dat zo is, stellen we het op nul. Dit kan van belang zijn voor de prestaties vanwege de aard van gedenormaliseerde drijvende-kommagetallen.

(Wanneer drijvende-kommagetallen erg klein worden, gebruiken ze een speciale weergave genaamd a gedenormaliseerd nummer. Dit heeft het voordeel dat drijvers kleinere getallen voorstellen, maar dit heeft een prijs. De meeste chipsets kunnen hun standaard rekenkundige bewerkingen niet gebruiken op gedenormaliseerde getallen en moeten ze in plaats daarvan emuleren met behulp van een reeks stappen. Dit kan tientallen tot honderden keren langzamer zijn dan het uitvoeren van bewerkingen op genormaliseerde drijvende-kommagetallen. Omdat we onze snelheid vermenigvuldigen met onze dempingsfactor per frame, zal deze uiteindelijk erg klein worden. We geven niet zoveel om zulke kleine snelheden, dus we hebben het eenvoudigweg op nul gezet.)

De PointMass :: increaseDamping () methode wordt gebruikt om de hoeveelheid demping tijdelijk te verhogen. We zullen dit later gebruiken voor bepaalde effecten.

De Lenteklasse

Een veer verbindt twee puntmassa's en oefent een kracht uit die over de natuurlijke lengte heen reikt. Springs volgen een aangepaste versie van Hooke's Law met demping:

\ [f = -kx - bv \]

  • \ (f \) is de kracht geproduceerd door de veer.
  • \ (k \) is de veer constant, of de stijfheid van de veer.
  • \ (x \) is de afstand waarover de veer zich uitstrekt voorbij zijn natuurlijke lengte.
  • \ (b \) is de dempingsfactor.
  • \ (v \) is de snelheid.

De code voor de De lente klasse is als volgt:

 class Spring public: PointMass * mEnd1; PointMass * mEnd2; zwevende mTargetLength; drijfvermogen; zweven mDamping; publiek: lente (PointMass * end1, PointMass * end2, vlotterstijfheid, vlotterdemping); ongeldige update (); ; Veer: veer (PointMass * end1, PointMass * end2, vlotterstijfheid, vlotterdemping): mEnd1 (end1), mEnd2 (end2), mTargetLength (mEnd1-> mPosition.distance (mEnd2-> mPosition) * 0.95f), mStiffness (stijfheid), mDamping (demping)  void Lente :: update () tVector3f x = mEnd1-> mPosition - mEnd2-> mPosition; vlotterlengte = x.lengte (); if (length> mTargetLength) x = (x / length) * (length - mTargetLength); tVector3f dv = mEnd2-> mVelocity - mEnd1-> mVelocity; tVector3f force = mStiffness * x - dv * mDamping; mEnd1-> applyForce (force); mEnd2-> applyForce (kracht); 

Wanneer we een veer maken, stellen we de natuurlijke lengte van de veer in op iets minder dan de afstand tussen de twee eindpunten. Dit houdt het raster strak, zelfs in rust, en verbetert het uiterlijk enigszins.

De Spring :: update () methode controleert eerst of de veer uitgerekt is voorbij zijn natuurlijke lengte. Als het niet uitgerekt is, gebeurt er niets. Als dat zo is, gebruiken we de aangepaste wet van Hooke om de kracht van de veer te vinden en toe te passen op de twee verbonden massa's.

Het raster maken

Nu we de benodigde geneste klassen hebben, zijn we klaar om het raster te maken. We beginnen met creëren PointMass objecten op elk kruispunt op het raster. We creëren ook een onroerende anker PointMass objecten om het raster op zijn plaats te houden. Vervolgens verbinden we de massa's met bronnen.

 std :: vector mSprings; PointMass * mPoints; Grid :: Grid (const tRectf & rect, const tVector2f & spacing) mScreenSize = tVector2f (GameRoot :: getInstance () -> getViewportSize (). Width, GameRoot :: getInstance () -> getViewportSize (). Height); int numColumns = (int) ((float) rect.size.width / spacing.x) + 1; int numRows = (int) ((float) rect.size.height / spacing.y) + 1; mPoints = nieuwe PointMass [numColumns * numRows]; mCols = numColumns; mRows = numRows; PointMass * fixedPoints = nieuwe PointMass [numColumns * numRows]; int kolom = 0, rij = 0; for (float y = rect.location.y; y <= rect.location.y + rect.size.height; y += spacing.y)  for (float x = rect.location.x; x <= rect.location.x + rect.size.width; x += spacing.x)  SetPointMass(mPoints, column, row, PointMass(tVector3f(x, y, 0), 1)); SetPointMass(fixedPoints, column, row, PointMass(tVector3f(x, y, 0), 0)); column++;  row++; column = 0;  // link the point masses with springs for (int y = 0; y < numRows; y++)  for (int x = 0; x < numColumns; x++)  if (x == 0 || y == 0 || x == numColumns - 1 || y == numRows - 1)  mSprings.push_back(Spring(GetPointMass(fixedPoints, x, y), GetPointMass(mPoints, x, y), 0.1f, 0.1f));  else if (x % 3 == 0 && y % 3 == 0)  mSprings.push_back( Spring(GetPointMass(fixedPoints, x, y), GetPointMass(mPoints, x, y), 0.002f, 0.02f));  if (x > 0) mSprings.push_back (Spring (GetPointMass (mPoints, x - 1, y), GetPointMass (mPoints, x, y), 0.28f, 0.06f));  if (y> 0) mSprings.push_back (Spring (GetPointMass (mPoints, x, y - 1), GetPointMass (mPoints, x, y), 0.28f, 0.06f)); 

De eerste voor loop creëert zowel normale massa's als onroerende massa's op elk kruispunt van het raster. We zullen niet echt alle onwrikbare massa's gebruiken, en de ongebruikte massa's zullen gewoon wat vuilnis verzameld worden enige tijd nadat de aannemer eindigt. We zouden kunnen optimaliseren door onnodige objecten te creëren, maar omdat het raster meestal maar één keer wordt gemaakt, maakt het niet veel uit.

Naast het gebruik van ankerpuntmassa's rond de rand van het raster, gebruiken we ook enkele ankermassa's in het raster. Deze zullen worden gebruikt om heel voorzichtig te helpen het rooster terug te trekken naar zijn oorspronkelijke positie na te zijn vervormd.

Aangezien de ankerpunten nooit bewegen, hoeven ze niet elk frame te worden bijgewerkt; we kunnen ze gewoon aansluiten op de bronnen en ze vergeten. Daarom hebben we geen ledenvariabele in de rooster klasse voor deze massa's.

Er zijn een aantal waarden die u kunt aanpassen bij het maken van het raster. De belangrijkste zijn de stijfheid en demping van de veren. (De stijfheid en demping van de grensankers en binnenankers worden onafhankelijk van de hoofdveren bepaald.) Hogere stijfheidswaarden zullen de veren sneller laten oscilleren, en hogere dempingswaarden zullen ervoor zorgen dat de veren eerder vertragen.

Het raster manipuleren

Om het raster te laten bewegen, moeten we het elk frame bijwerken. Dit is heel eenvoudig omdat we al het harde werk in de PointMass en De lente klassen:

 void Grid :: update () for (size_t i = 0; i < mSprings.size(); i++)  mSprings[i].update();  for(int i = 0; i < mCols * mRows; i++)  mPoints[i].update();  

Nu zullen we enkele methoden toevoegen die het raster manipuleren. U kunt methoden toevoegen voor elke vorm van manipulatie die u maar kunt bedenken. We zullen hier drie soorten manipulaties uitvoeren: een deel van het raster in een bepaalde richting duwen, het raster vanaf een bepaald punt naar buiten duwen en het raster naar een bepaald punt trekken. Alle drie hebben invloed op het raster binnen een bepaalde straal vanaf een bepaald doelpunt. Hieronder staan ​​enkele afbeeldingen van deze manipulaties in actie:


Kogels die het raster naar buiten stoten.


Het rooster naar binnen zuigen.

Golf gemaakt door het raster langs de z-as te duwen.
 void Grid :: applyDirectedForce (const tVector3f & force, const tVector3f & position, float radius) for (int i = 0; i < mCols * mRows; i++)  if (position.distanceSquared(mPoints[i].mPosition) < radius * radius)  mPoints[i].applyForce(10.0f * force / (10 + position.distance(mPoints[i].mPosition)));    void Grid::applyImplosiveForce(float force, const tVector3f& position, float radius)  for (int i = 0; i < mCols * mRows; i++)  float dist2 = position.distanceSquared(mPoints[i].mPosition); if (dist2 < radius * radius)  mPoints[i].applyForce(10.0f * force * (position - mPoints[i].mPosition) / (100 + dist2)); mPoints[i].increaseDamping(0.6f);    void Grid::applyExplosiveForce(float force, const tVector3f& position, float radius)  for (int i = 0; i < mCols * mRows; i++)  float dist2 = position.distanceSquared(mPoints[i].mPosition); if (dist2 < radius * radius)  mPoints[i].applyForce(100 * force * (mPoints[i].mPosition - position) / (10000 + dist2)); mPoints[i].increaseDamping(0.6f);   

We zullen alle drie deze methoden in Shape Blaster gebruiken voor verschillende effecten.

Het raster weergeven

We tekenen het raster door lijnsegmenten tussen elk aangrenzend paar punten te tekenen. Eerst voegen we een uitbreidingsmethode toe met een tSpriteBatch aanwijzer als een parameter waarmee we lijnsegmenten kunnen tekenen door een textuur van een enkele pixel te nemen en deze uit te lijnen in een lijn.

Open de Kunst class en declareer een textuur voor de pixel:

 klasse Art: public tSingleton; beschermd: tTexture * mPixel; ... public: tTexture * getPixel () const; ...;

Je kunt de pixeltextuur op dezelfde manier instellen als de andere afbeeldingen, dus we zullen toevoegen pixel.png (een 1x1px-afbeelding met de enige pixel ingesteld op wit) voor het project en laad het in de tTexture:

 mPixel = nieuwe tTexture (tSurface ("pixel.png"));

Laten we nu de volgende methode toevoegen aan de uitbreidingen klasse:

 void Extensions :: drawLine (tSpriteBatch * spriteBatch, const tVector2f & start, const tVector2f & end, const tColor4f & color, float thickness) tVector2f delta = einde - start; spriteBatch-> draw (0, Art :: getInstance () -> getPixel (), tPoint2f ((int32_t) start.x, (int32_t) start.y), tOptional(), color, to Angle (delta), tPoint2f (0, 0), tVector2f (delta.length (), thickness)); 

Deze methode rekt, roteert en tinten de pixeltextuur om de gewenste lijn te produceren.

Vervolgens hebben we een methode nodig om de 3D-rasterpunten op ons 2D-scherm te projecteren. Normaal gesproken kan dit worden gedaan met behulp van matrices, maar hier zullen we de coördinaten in plaats daarvan handmatig transformeren.

Voeg het volgende toe aan de rooster klasse:

 tVector2f Raster :: toVec2 (const tVector3f & v) float factor = (v.z + 2000.0f) * 0.0005f; return (tVector2f (v.x, v.y) - mScreenSize * 0.5f) * factor + mScreenSize * 0.5f; 

Deze transformatie geeft het raster een perspectiefbeeld waarbij ver weg gelegen punten dichter bij elkaar op het scherm verschijnen. Nu kunnen we het raster tekenen door de rijen en kolommen te doorlopen en er lijnen tussen te tekenen:

 void Grid :: draw (tSpriteBatch * spriteBatch) int width = mCols; int height = mRows; tColor4f-kleur (0,12f, 0,12f, 0,55f, 0,33f); voor (int y = 1; y < height; y++)  for (int x = 1; x < width; x++)  tVector2f left, up; tVector2f p = toVec2(GetPointMass(mPoints, x, y)->mPosition); if (x> 1) left = toVec2 (GetPointMass (mPoints, x - 1, y) -> mPosition); vlotterdikte = (y% 3 == 1)? 3.0f: 1.0f; Uitbreidingen :: drawLine (spriteBatch, links, p, kleur, dikte);  if (y> 1) up = toVec2 (GetPointMass (mPoints, x, y - 1) -> mPosition); vlotterdikte = (x% 3 == 1)? 3.0f: 1.0f; Uitbreidingen :: drawLine (spriteBatch, up, p, kleur, dikte); 

In de bovenstaande code, p is ons huidige punt op het raster, links is het punt direct links ervan en omhoog is het punt er direct boven. We tekenen elke derde lijn zowel horizontaal als verticaal voor een visueel effect.

interpolatie

We kunnen het raster optimaliseren door de beeldkwaliteit voor een bepaald aantal veren te verbeteren zonder de prestatiekosten aanzienlijk te verhogen. We gaan twee van dergelijke optimalisaties doen.

We zullen het raster dichter maken door lijnsegmenten toe te voegen in de bestaande rastercellen. We doen dit door lijnen te tekenen vanaf het middelpunt van één kant van de cel naar het middelpunt van de andere kant. De afbeelding hieronder toont de nieuwe geïnterpoleerde lijnen in rood.

Het tekenen van de geïnterpoleerde lijnen is eenvoudig. Als je twee punten hebt, een en b, hun middelpunt is (a + b) / 2. Dus, om de geïnterpoleerde lijnen te tekenen, voegen we de volgende code toe binnen de voor loops van onze Grid :: draw () methode:

 if (x> 1 && y> 1) tVector2f upLeft = toVec2 (GetPointMass (mPoints, x - 1, y - 1) -> mPosition); Extensies :: drawLine (spriteBatch, 0.5f * (upLeft + up), 0.5f * (links + p), kleur, 1.0f); // verticale lijn Extensies :: drawLine (spriteBatch, 0.5f * (upLeft + left), 0.5f * (up + p), color, 1.0f); // horizontale lijn 

De tweede verbetering is om interpolatie uit te voeren op onze rechte lijnsegmenten om ze in vloeiendere bochten te maken. In de oorspronkelijke XNA-versie van deze game, vertrouwde de code op XNA's Vector2.CatmullRom () methode die Catmull-Rom-interpolatie uitvoert. Je geeft de methode vier opeenvolgende punten op een gebogen lijn door, en het geeft punten terug langs een vloeiende curve tussen de tweede en derde punten die je hebt opgegeven.

Aangezien dit algoritme niet bestaat in de standaardbibliotheek van C of C ++, moeten we het zelf implementeren. Gelukkig is er een referentie-implementatie beschikbaar om te gebruiken. Ik heb een MathUtil :: catmullRom () methode gebaseerd op deze referentie-implementatie:

 float MathUtil :: catmullRom (const float value1, const float value2, const float value3, const float value4, float amount) // Met formule van http://www.mvps.org/directx/articles/catmull/ float amountSquared = hoeveelheid * bedrag; float amountCubed = amountSquared * amount; return (float) (0.5f * (2.0f * waarde2 + (waarde3 - waarde1) * aantal + (2.0f * waarde1 - 5.0f * waarde2 + 4.0f * waarde3 - waarde4) * amountSquared + (3.0f * waarde2 - waarde1 - 3.0f * waarde3 + waarde4) * amountCubed));  tVector2f MathUtil :: catmullRom (const tVector2f & value1, const tVector2f & value2, const tVector2f & value3, const tVector2f & value4, float amount) return tVector2f (MathUtil :: catmullRom (value1.x, value2.x, value3.x, value4.x , aantal), MathUtil :: catmullRom (value1.y, value2.y, value3.y, value4.y, amount)); 

Het vijfde argument voor MathUtil :: catmullRom () is een wegingsfactor die bepaalt welk punt op de geïnterpoleerde curve wordt geretourneerd. Een weegfactor van 0 of 1 zullen respectievelijk het door u opgegeven tweede of derde punt en een wegingsfactor van 0.5 zal het punt op de geïnterpoleerde curve halverwege tussen de twee punten retourneren. Door de wegingsfactor geleidelijk van nul naar één te verplaatsen en lijnen tussen de geretourneerde punten te tekenen, kunnen we een perfect vloeiende curve produceren. Om de prestatiekosten laag te houden, zullen we echter slechts één enkel geïnterpoleerd punt in overweging nemen, met een wegingsfactor van 0.5. Vervolgens vervangen we de oorspronkelijke rechte lijn in het raster met twee lijnen die elkaar raken op het geïnterpoleerde punt.

Het onderstaande diagram toont het effect van deze interpolatie:


Omdat de lijnsegmenten in het raster al klein zijn, maakt het gebruik van meer dan één geïnterpoleerd punt over het algemeen geen merkbaar verschil.

Vaak zijn de lijnen in ons raster erg recht en hoeven ze niet te worden gladgemaakt. We kunnen dit controleren en voorkomen dat we twee lijnen in plaats van één moeten tekenen: we controleren of de afstand tussen het geïnterpoleerde punt en het middelpunt van de rechte lijn groter is dan één pixel; als dat zo is, nemen we aan dat de lijn gebogen is en we trekken twee lijnsegmenten.

De wijziging aan onze Grid :: draw () methode voor het toevoegen van Catmull-Rom-interpolatie voor de horizontale lijnen wordt hieronder weergegeven.

 links = toVec2 (GetPointMass (mPoints, x - 1, y) -> mPosition); vlotterdikte = (y% 3 == 1)? 3.0f: 1.0f; int clampedX = (int) tMath :: min (x + 1, width - 1); tVector2f mid = MathUtil :: catmullRom (toVec2 (GetPointMass (mPoints, x - 2, y) -> mPosition), links, p, toVec2 (GetPointMass (mPoints, clampedX, y) -> mPosition), 0,5f); if (mid.distanceSquared ((left + p) / 2)> 1) Extensions :: drawLine (spriteBatch, links, midden, kleur, dikte); Uitbreidingen :: drawLine (spriteBatch, mid, p, kleur, dikte);  else Extensies :: drawLine (spriteBatch, links, p, kleur, dikte); 

De afbeelding hieronder toont de effecten van de afvlakking. Op elk geïnterpoleerd punt wordt een groene stip getekend om beter te illustreren waar de lijnen worden afgevlakt.

De Grid in Shape Blaster gebruiken

Nu is het tijd om het raster in onze game te gebruiken. We beginnen met het verklaren van een openbare, statische rooster variabele in GameRoot en het maken van het raster in de GameRoot :: onInitView. We maken een raster met ongeveer 600 punten zoals dat.

 const int maxGridPoints = 600; tVector2f gridSpacing = tVector2f ((float) sqrtf (mViewportSize.width * mViewportSize.height / maxGridPoints)); mGrid = new Grid (tRectf (0,0, mViewportSize), gridSpacing);

Hoewel de originele XNA-versie van het spel 1.600 punten gebruikt (in plaats van 600), wordt dit veel te veel om te verwerken, zelfs voor de krachtige hardware op de iPhone. Gelukkig heeft de oorspronkelijke code het aantal punten aangepast en bij ongeveer 600 rasterpunten kunnen we ze nog steeds renderen en toch een optimale framesnelheid behouden.

Dan bellen we Grid :: update () en Grid :: draw () van de GameRoot :: onRedrawView () methode in GameRoot. Hierdoor kunnen we het raster zien wanneer we het spel uitvoeren. We moeten echter nog steeds verschillende game-objecten laten communiceren met het raster.

Kogels stoten het raster af. We hebben al een methode gemaakt om dit te doen genaamd Grid :: applyExplosiveForce (). Voeg de volgende regel toe aan de Bullet :: update () methode.

 GameRoot :: getInstance () -> getGrid () -> applyExplosiveForce (0.5f * mVelocity.length (), mPosition, 80);

Dit zorgt ervoor dat kogels het raster proportioneel afstoten ten opzichte van hun snelheid. Dat was vrij eenvoudig.

Laten we nu werken aan zwarte gaten. Voeg deze regel toe aan BlackHole :: update ():

 GameRoot :: getInstance () -> getGrid () -> applyImplosiveForce ((float) sinf (mSprayAngle / 2.0f) * 10 + 20, mPosition, 200);

Dit zorgt ervoor dat het zwarte gat in het rooster zuigt met een variërende hoeveelheid kracht. We hebben het mSprayAngle variabele, die ervoor zorgt dat de kracht op het rooster synchroon pulseert met de hoek waarin het deeltjes sproeit (hoewel met de helft van de frequentie als gevolg van de deling door twee). De doorgevoerde kracht zal sinusoïdaal variëren tussen 10 en 30.

Ten slotte zullen we een schokgolf in het net creëren wanneer het schip van de speler na de dood weer verschijnt. We doen dit door het rooster langs de z-as te trekken en vervolgens de kracht te laten propageren en door de veren te laten stuiteren. Nogmaals, dit vereist slechts een kleine aanpassing aan PlayerShip :: update ().

 if (getIsDead ()) mFramesUntilRespawn--; if (mFramesUntilRespawn == 0) GameRoot :: getInstance () -> getGrid () -> applyDirectedForce (tVector3f (0, 0, 5000), tVector3f (mPosition.x, mPosition.y, 0), 50); 

Wat is het volgende?

We hebben de basis gameplay en effecten geïmplementeerd. Het is aan jou om er een compleet en gepolijst spel van te maken met je eigen smaak. Probeer wat interessante nieuwe mechanica, enkele coole nieuwe effecten of een uniek verhaal toe te voegen. Als u niet zeker weet waar u moet beginnen, volgt hier een paar suggesties:

  • Tweak en pas de touch-bediening aan uw persoonlijke voorkeuren aan.
  • Ondersteuning toevoegen voor hardware-gamecontrollers in iOS 7 via het GameController Framework.
  • Profileer en optimaliseer alle langzame delen van de code met behulp van de ingebouwde instrumenten van Xcode.
  • Probeer post-processing effecten toe te voegen, zoals de Bloom Shader die aanwezig is in de originele XNA-versie.
  • Maak nieuwe vijandtypen zoals slangen of exploderende vijanden.
  • Maak nieuwe wapensoorten zoals het zoeken van raketten of een bliksemschiet.
  • Voeg een titelscherm en hoofdmenu toe en een hoge scoretabel.
  • Voeg wat power-ups toe, zoals een schild of bommen.
  • Experimenteer om power-ups te vinden die leuk zijn en je spel helpen opvallen.
  • Maak meerdere niveaus. Hardere niveaus kunnen hardere vijanden en geavanceerdere wapens en power-ups introduceren.
  • Voeg milieurisico's toe zoals lasers.

Alles is mogelijk!