Erstellen Sie einen Neon-Vektor-Shooter für iOS Partikeleffekte

In dieser Serie von Tutorials zeige ich Ihnen, wie Sie einen von Geometry Wars inspirierten Doppelstock-Shooter mit Neon-Grafik, verrückten Partikeleffekten und beeindruckender Musik für iOS mit C ++ und OpenGL ES 2.0 erstellen. In diesem Teil fügen wir Explosionen und visuelles Flair hinzu.

Überblick

In der bisherigen Serie haben wir das Gameplay eingerichtet und virtuelle Gamepad-Steuerelemente hinzugefügt. Als nächstes fügen wir Partikeleffekte hinzu.


Warnung: Laut!

Partikeleffekte werden durch das Herstellen einer großen Anzahl kleiner Partikel erzeugt. Sie sind sehr vielseitig und können verwendet werden, um fast jedem Spiel mehr Flair zu verleihen. In Shape Blaster werden wir mit Partikeleffekten Explosionen machen. Wir werden auch Partikeleffekte verwenden, um ein Auspufffeuer für das Schiff des Spielers zu erzeugen und den Schwarzen Löchern ein optisches Flair zu verleihen. Außerdem werden wir untersuchen, wie Partikel aus den schwarzen Löchern mit der Schwerkraft interagieren können.

Wechseln Sie zu Release Builds für Geschwindigkeitssteigerungen

Bisher haben Sie Shape Blaster wahrscheinlich mit allen Standardeinstellungen erstellt und ausgeführt debuggen Bau des Projekts. Dies ist zwar in Ordnung und großartig, wenn Sie Ihren Code debuggen. Das Debuggen deaktiviert jedoch die meisten Geschwindigkeits- und Mathematikoptimierungen, die ausgeführt werden können, sowie das Aktivieren aller Assertionen im Code.

Wenn Sie den Code von nun an im Debug-Modus ausführen, werden Sie feststellen, dass die Framerate drastisch sinkt. Dies liegt daran, dass wir auf ein Gerät abzielen, das im Vergleich zu einem Desktop-Computer oder sogar einem Laptop weniger RAM, weniger CPU-Takt und kleinere 3D-Hardware aufweist.

An dieser Stelle können Sie das Debugging optional deaktivieren und den Freigabemodus aktivieren. Der Release-Modus ermöglicht uns eine vollständige Compiler- und Mathematikoptimierung sowie das Entfernen von nicht verwendetem Debugging-Code und Assertions.

Wenn Sie das Projekt öffnen, wählen Sie die Option Produkt Speisekarte, Planen, dann Schema bearbeiten… .


Das folgende Dialogfenster wird geöffnet. Wählen Lauf auf der linken Seite des Dialogs und von Konfiguration erstellen, Ändern Sie das Popup-Element von debuggen zu Veröffentlichung.


Sie werden die Geschwindigkeitsgewinne sofort bemerken. Der Vorgang kann leicht rückgängig gemacht werden, wenn Sie das Programm erneut debuggen müssen: Wählen Sie einfach debuggen anstatt Veröffentlichung und du bist fertig.

Spitze: Beachten Sie jedoch, dass eine solche Schemaänderung eine vollständige Neukompilierung des Programms erfordert.

Die ParticleManager-Klasse

Wir beginnen mit der Erstellung einer ParticleManager Klasse, die alle Partikel speichert, aktualisiert und zeichnet. Wir machen diese Klasse so allgemein, dass sie problemlos in anderen Projekten wiederverwendet werden kann, aber dennoch eine Anpassung von Projekt zu Projekt erfordert. Um das zu behalten ParticleManager so allgemein wie möglich ist er nicht dafür verantwortlich, wie die Partikel aussehen oder sich bewegen; Wir werden das anderswo erledigen.

Partikel neigen dazu, schnell und in großer Zahl erzeugt und zerstört zu werden. Wir werden einen Objektpool verwenden, um zu vermeiden, dass große Müllmengen entstehen. Das bedeutet, dass wir eine große Anzahl von Partikeln im Voraus zuweisen und diese Partikel dann wiederverwenden werden.

Wir werden auch machen ParticleManager haben eine feste Kapazität. Dies vereinfacht es und hilft sicherzustellen, dass wir unsere Leistungs- oder Speicherbeschränkungen nicht überschreiten, indem zu viele Partikel erzeugt werden. Wenn die maximale Anzahl an Partikeln überschritten wird, werden wir die ältesten durch neue ersetzen. Wir machen das ParticleManager eine generische Klasse. Dies ermöglicht es uns, benutzerdefinierte Zustandsinformationen für die Partikel zu speichern, ohne sie fest in die Partikel einzugeben
ParticleManager selbst.

Wir erstellen auch eine Partikel Klasse:

 Klasse Partikel public: ParticleState mState; tColor4f mColor; tVector2f mPosition; tVector2f mScale; tTexture * mTexture; Float-Orientierung; Float mDuration; Float mPercentLife; public: Partikel (): mScale (1,1), mPercentLife (1.0f) ;

Das Partikel Die Klasse enthält alle Informationen, die zur Anzeige eines Partikels und zum Verwalten seiner Lebensdauer erforderlich sind. ParticleState Gibt es zusätzliche Daten, die wir für unsere Partikel benötigen? Welche Daten benötigt werden, hängt von den gewünschten Partikeleffekten ab. Es kann verwendet werden, um Geschwindigkeit, Beschleunigung, Rotationsgeschwindigkeit oder anderes zu speichern.

Um die Partikel besser verwalten zu können, benötigen wir eine Klasse, die als kreisförmiges Array fungiert. Das bedeutet, dass Indizes, die normalerweise außerhalb der Grenzen liegen, sich an den Anfang des Arrays bewegen. Dies macht es leicht, die ältesten Partikel zuerst zu ersetzen, wenn der Platz für neue Partikel in unserem Array zur Neige geht. Dazu fügen wir als verschachtelte Klasse Folgendes hinzu ParticleManager:

 Klasse CircularParticleArray protected: std :: vector mlist; size_t mStart; size_t mCount; public: CircularParticleArray (int Kapazität) mList.resize ((size_t) Kapazität);  size_t getStart () return mStart;  void setStart (Wert für size_t) mStart = value% mList.size ();  size_t getCount () return mCount;  void setCount (Wert für size_t) mCount = value;  size_t getCapacity () return mList.size ();  Particle & operator [] (const size_t i) Rückgabe der mList [(mStart + i)% mList.size ()];  const Particle & operator [] (const size_t i) const return mList [(mStart + i)% mList.size ()]; ;

Wir können das einstellen mStart Mitglied, wo Index Null in unserem anpassen CircularParticleArray entspricht dem zugrunde liegenden Array und mCount wird verwendet, um zu ermitteln, wie viele aktive Partikel in der Liste enthalten sind. Wir sorgen dafür, dass das Teilchen am Index Null immer das älteste Teilchen ist. Wenn wir das älteste Teilchen durch ein neues ersetzen, erhöhen wir es einfach mStart, was im Wesentlichen die kreisförmige Anordnung dreht.

Nun, da wir unsere Helfer-Klassen haben, können wir damit beginnen, die ParticleManager Klasse. Wir benötigen eine neue Membervariable und einen Konstruktor.

 CircularParticleArray mParticleList; ParticleManager :: ParticleManager (int. Kapazität): mParticleList (Kapazität) 

Wir erstellen mParticleList und füllen Sie es mit leeren Partikeln. Der Konstruktor ist der einzige Ort, an dem der ParticleManager weist Speicher zu.

Als nächstes fügen wir die createParticle () Diese Methode erstellt ein neues Partikel, indem das nächste nicht verwendete Partikel im Pool verwendet wird, oder das älteste Partikel, wenn keine nicht verwendeten Partikel vorhanden sind.

 void ParticleManager :: createParticle (tTexture * texture, const tVector2f & position, const tColor4f & tint, Floatdauer, const tVector2f & scale, const ParticleState & state, float theta) size_t index; if (mParticleList.getCount () == mParticleList.getCapacity ()) index = 0; mParticleList.setStart (mParticleList.getStart () + 1);  else index = mParticleList.getCount (); mParticleList.setCount (mParticleList.getCount () + 1);  Particle & ref = mParticleList [Index]; ref.mTexture = Textur; Ref.mPosition = Position; Referenzfarbe = Tönung; ref.mDuration = Dauer; ref.mPercentLife = 1.0f; ref.mScale = Skala; ref.mOrientation = Theta; ref.mState = state; 

Partikel können jederzeit zerstört werden. Wir müssen diese Partikel entfernen und dabei sicherstellen, dass die anderen Partikel in derselben Reihenfolge bleiben. Wir können dies tun, indem wir die Liste der Partikel durchlaufen und dabei nachverfolgen, wie viele davon zerstört wurden. Auf dem Weg bewegen wir jedes aktive Teilchen vor den zerstörten Teilchen, indem wir es mit dem ersten zerstörten Teilchen tauschen. Sobald alle zerstörten Partikel am Ende der Liste sind, deaktivieren wir sie, indem Sie die Liste setzen mCount variabel auf die Anzahl der aktiven Teilchen. Zerstörte Partikel verbleiben im darunter liegenden Array, werden jedoch nicht aktualisiert oder gezeichnet.

ParticleManager :: update () übernimmt die Aktualisierung der einzelnen Partikel und das Entfernen der zerstörten Partikel aus der Liste:

 void ParticleManager :: update () size_t removeCount = 0; für (size_ti = 0; i < mParticleList.getCount(); i++)  Particle& ref = mParticleList[i]; ref.mState.updateParticle(ref); ref.mPercentLife -= 1.0f / ref.mDuration; Swap(mParticleList, i - removalCount, i); if (ref.mPercentLife < 0)  removalCount++;   mParticleList.setCount(mParticleList.getCount() - removalCount);  void ParticleManager::Swap(typename ParticleManager::CircularParticleArray& list, size_t index1, size_t index2) const  Particle temp = list[index1]; list[index1] = list[index2]; list[index2] = temp; 

Die letzte Sache, die in implementiert werden soll ParticleManager zieht die Partikel:

 void ParticleManager :: draw (tSpriteBatch * spriteBatch) für (size_t i = 0; i < mParticleList.getCount(); i++)  Particle particle = mParticleList[(size_t)i]; tPoint2f origin = particle.mTexture->getSurfaceSize () / 2; spriteBatch-> draw (2, particle.mTexture, tPoint2f ((int) particle.mPosition.x, (int) particle.mPosition.y), tOptional(), Partikel.mColor, Partikel.mOrientation, Ursprung, Partikel.mScale); 

Die ParticleState-Klasse

Als Nächstes erstellen Sie eine benutzerdefinierte Klasse oder Struktur, um das Aussehen der Partikel in Shape Blaster anzupassen. Es gibt verschiedene Arten von Partikeln in Shape Blaster, die sich ein wenig anders verhalten, also erstellen wir zunächst eine enum für den Partikeltyp. Wir benötigen auch Variablen für die Geschwindigkeit und die Anfangslänge des Partikels.

 Klasse ParticleState public: enum ParticleType kNone = 0, kEnemy, kBullet, kIgnoreGravity; public: tVector2f mVelocity; ParticleType mType; float mLengthMultiplier; public: ParticleState (); ParticleState (const tVector2f & Velocity, ParticleType-Typ, Float-LängeMultiplier = 1.0f); ParticleState getRandom (float minVel, float maxVel); void updateParticle (Partikel & Partikel); ;

Jetzt können wir die Partikel schreiben aktualisieren() Methode. Es ist eine gute Idee, diese Methode schnell zu machen, da sie möglicherweise für eine große Anzahl von Partikeln aufgerufen werden muss.

Wir fangen einfach an. Fügen wir folgende Methode hinzu ParticleState:

 void ParticleState :: updateParticle (Partikel & Partikel) tVector2f vel = Partikel.mState.mVelocity; partikel.mPosition + = vel; particle.mOrientation = Erweiterungen :: toAngle (vel); // denormalisierte Floats verursachen erhebliche Leistungsprobleme, wenn (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Wir werden in Kürze zurückkommen und diese Methode verbessern. Lassen Sie uns zunächst einige Partikeleffekte erstellen, um unsere Änderungen tatsächlich testen zu können.

Feindliche Explosionen

Im GameRoot, ein neues deklarieren ParticleManager und nenne es aktualisieren() und zeichnen() Methoden:

 // in GameRoot geschützt: ParticleManager mParticleManager; public: ParticleManager * getParticleManager () return & mParticleManager;  // in GameRoots Konstruktor GameRoot :: GameRoot (): mParticleManager (1024 * 20), mViewportSize (800, 600), mSpriteBatch (NULL)  // in GameRoot :: onRedrawView () mParticleManager.update (); mParticleManager.draw (mSpriteBatch);

Außerdem deklarieren wir eine neue Instanz von tTextur Klasse in der Kunst Klasse aufgerufen mLineParticle für die Textur des Teilchens. Wir laden es so, wie wir die Sprites des anderen Spiels machen:

 // In Art's Konstruktor mLineParticle = new tTexture (tSurface ("laser.png"));

Lassen Sie uns Feinde explodieren lassen. Wir werden das ändern Feind :: wasShot () Methode wie folgt:

 void Feind :: wasShot () mIsExpired = true; für (int i = 0; i < 120; i++)  float speed = 18.0f * (1.0f - 1 / Extensions::nextFloat(1, 10)); ParticleState state(Extensions::nextVector2(speed, speed), ParticleState::kEnemy, 1); tColor4f color(0.56f, 0.93f, 0.56f, 1.0f); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, color, 190, 1.5f, state); 

Dadurch entstehen 120 Partikel, die mit unterschiedlichen Geschwindigkeiten in alle Richtungen nach außen schießen. Die Zufallsgeschwindigkeit wird so gewichtet, dass sich die Partikel wahrscheinlicher in der Nähe der Höchstgeschwindigkeit bewegen. Dadurch werden sich mehr Partikel am Rand der Explosion befinden, wenn sie sich ausdehnt. Die Partikel halten 190 Frames oder etwas mehr als drei Sekunden.

Sie können jetzt das Spiel ausführen und zusehen, wie Gegner explodieren. Bei den Partikeleffekten müssen jedoch noch einige Verbesserungen vorgenommen werden.

Das erste Problem ist, dass die Partikel abrupt verschwinden, wenn ihre Dauer abgelaufen ist. Es wäre schöner, wenn sie problemlos ausgeblendet werden könnten, aber gehen wir noch ein bisschen weiter und lassen die Partikel heller leuchten, wenn sie sich schnell bewegen. Es sieht auch gut aus, wenn wir schnell bewegte Teilchen verlängern und langsam bewegende Teilchen verkürzen.

Modifiziere den ParticleState.UpdateParticle () Methode wie folgt (Änderungen werden hervorgehoben).

 void ParticleState :: updateParticle (Partikel & Partikel) tVector2f vel = Partikel.mState.mVelocity; partikel.mPosition + = vel; particle.mOrientation = Erweiterungen :: toAngle (vel); Schwimmgeschwindigkeit = Vel.Länge (); Float alpha = tMath :: min (1,0f, tMath :: min (partikel.mPercentLife * 2, Geschwindigkeit * 1,0f)); alpha * = alpha; partikel.mColor.a = alpha; particle.mScale.x = Partikel.mState.mLengthMultiplier * tMath :: min (tMath :: min (1.0f, 0.2f * Geschwindigkeit + 0.1f), alpha); // denormalisierte Floats verursachen erhebliche Leistungsprobleme, wenn (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Die Explosionen sehen jetzt viel besser aus, aber sie haben alle dieselbe Farbe.

Monochromatische Explosionen sind ein guter Anfang, aber können wir es besser machen?

Wir können ihnen mehr Auswahl geben, indem Sie zufällige Farben wählen. Eine Möglichkeit, zufällige Farben zu erzeugen, ist die zufällige Auswahl der roten, blauen und grünen Komponenten. Dies führt jedoch zu vielen stumpfen Farben, und wir möchten, dass unsere Partikel ein Neonlicht-Erscheinungsbild haben. Wir können mehr Kontrolle über unsere Farben haben, indem wir sie im HSV-Farbraum angeben. HSV steht für Farbton, Sättigung und Wert. Wir möchten Farben mit einem zufälligen Farbton auswählen, aber mit einer festen Sättigung und einem festen Wert. Wir brauchen eine Hilfsfunktion, die aus HSV-Werten eine Farbe erzeugen kann.

 tColor4f ColorUtil :: HSVToColor (Float h, Float s, Float v) if (h == 0 &&s == 0) Rückgabe tColor4f (v, v, v, 1.0f);  float c = s * v; float x = c * (1 - abs (int32_t (h)% 2 - 1)); Float m = v - c; wenn (h < 1) return tColor4f(c + m, x + m, m, 1.0f); else if (h < 2) return tColor4f(x + m, c + m, m, 1.0f); else if (h < 3) return tColor4f(m, c + m, x + m, 1.0f); else if (h < 4) return tColor4f(m, x + m, c + m, 1.0f); else if (h < 5) return tColor4f(x + m, m, c + m, 1.0f); else return tColor4f(c + m, m, x + m, 1.0f); 

Jetzt können wir modifizieren Feind :: wasShot () zufällige Farben verwenden. Um die Explosionsfarbe weniger monoton zu gestalten, wählen wir für jede Explosion zwei nahegelegene Schlüsselfarben aus und interpolieren sie linear um einen zufälligen Betrag für jedes Partikel:

 void Feind :: wasShot () mIsExpired = true; float hue1 = Erweiterungen :: nextFloat (0, 6); float hue2 = fmodf (hue1 + Erweiterungen :: nextFloat (0, 2), 6.0f); tColor4f color1 = ColorUtil :: HSVToColor (Farbton1, 0,5f, 1); tColor4f color2 = ColorUtil :: HSVToColor (Farbton2, 0,5f, 1); für (int i = 0; i < 120; i++)  float speed = 18.0f * (1.0f - 1 / Extensions::nextFloat(1, 10)); ParticleState state(Extensions::nextVector2(speed, speed), ParticleState::kEnemy, 1); tColor4f color = Extensions::colorLerp(color1, color2, Extensions::nextFloat(0, 1)); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, color, 190, 1.5f, state); 

Die Explosionen sollten wie die Animation unten aussehen:


Sie können mit der Farbgenerierung nach Ihren Wünschen herumspielen. Eine alternative Technik, die gut funktioniert, ist die manuelle Auswahl einer Reihe von Farbmustern für Explosionen und die zufällige Auswahl der von Ihnen gewählten Farbschemata.

Bullet Explosionen

Wir können die Kugeln auch explodieren lassen, wenn sie den Bildschirmrand erreichen. Wir werden im Wesentlichen dasselbe tun, wie wir es für feindliche Explosionen gemacht haben.

Lass uns modifizieren Bullet :: Update () wie folgt:

 if (! tRectf (0, 0, GameRoot :: getInstance () -> getViewportSize ()) enthält. (tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y))) mIsExpired = true; für (int i = 0; i < 30; i++)  GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, tColor4f (0.67f, 0.85f, 0.90f, 1), 50, 1, ParticleState (Erweiterungen :: nextVector2 (0, 9) , ParticleState :: kBullet, 1)); 

Sie stellen möglicherweise fest, dass das Verschieben der Partikel in eine zufällige Richtung verschwenderisch ist, da mindestens die Hälfte der Partikel sofort vom Bildschirm abspringt (mehr, wenn die Kugel in einer Ecke explodiert). Wir könnten zusätzliche Arbeit verrichten, um sicherzustellen, dass den Partikeln nur Geschwindigkeiten gegeben werden, die der Wand gegenüberliegen. Stattdessen nehmen wir ein Stichwort von Geometry Wars und lassen alle Partikel von den Wänden abprallen, so dass alle Partikel, die sich vom Bildschirm abwenden, zurückgeworfen werden.

Fügen Sie folgende Zeilen hinzu ParticleState.UpdateParticle () irgendwo zwischen der ersten und letzten Zeile:

 tVector2f pos = Partikel.mPosition; int width = (int) GameRoot :: getInstance () -> getViewportSize (). width; int height = (int) GameRoot :: getInstance () -> getViewportSize (). height; // mit den Rändern des Bildschirms kollidieren, wenn (pos.x < 0)  vel.x = (float)fabs(vel.x);  else if (pos.x > width) vel.x = (float) -fabs (vel.x);  if (pos.y < 0)  vel.y = (float)fabs(vel.y);  else if (pos.y > height) vel.y = (Float) -fabs (vel.y); 

Spieler Schiff Explosion

Wir werden eine große Explosion machen, wenn der Spieler getötet wird. Ändern PlayerShip :: kill () wie so:

 void PlayerShip :: kill () PlayerStatus :: getInstance () -> removeLife (); mFramesUntilRespawn = PlayerStatus :: getInstance () -> getIsGameOver ()? 300: 120; tColor4f ExplosionColor = tColor4f (0,8f, 0,8f, 0,4f, 1,0f); für (int i = 0; i < 1200; i++)  float speed = 18.0f * (1.0f - 1 / Extensions::nextFloat(1.0f, 10.0f)); tColor4f color = Extensions::colorLerp(tColor4f(1,1,1,1), explosionColor, Extensions::nextFloat(0, 1)); ParticleState state(Extensions::nextVector2(speed, speed), ParticleState::kNone, 1); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, color, 190, 1.5f, state); 

Dies ist ähnlich wie bei den feindlichen Explosionen, aber wir verwenden mehr Partikel und verwenden immer das gleiche Farbschema. Der Partikeltyp ist ebenfalls auf eingestellt ParticleState :: kNone.

In der Demo verlangsamen sich Partikel aus feindlichen Explosionen schneller als Partikel aus dem Schiff des Spielers, das explodiert. Dadurch dauert die Explosion des Spielers etwas länger und wirkt etwas epischer.

Schwarze Löcher nochmals besucht

Jetzt, da wir Partikeleffekte haben, lassen Sie uns die Schwarzen Löcher noch einmal besuchen und sie mit Partikeln interagieren lassen.

Wirkung auf die Partikel

Schwarze Löcher sollten Partikel zusätzlich zu anderen Entitäten beeinflussen, daher müssen wir sie ändern ParticleState :: updateParticle (). Fügen wir die folgenden Zeilen hinzu:

 if (particle.mState.mType! = kIgnoreGravity) für (std :: list:: iterator j = EntityManager :: getInstance () -> mBlackHoles.begin (); j! = EntityManager :: getInstance () -> mBlackHoles.end (); j ++) tVector2f dPos = (* j) -> getPosition () - pos; Floatdistanz = dPos.length (); tVector2fn = dPos / Abstand; vel + = 10000.0f * n / (Abstand * Abstand + 10000.0f); // füge tangentiale Beschleunigung für in der Nähe befindliche Partikel hinzu (Abstand < 400)  vel += 45.0f * tVector2f(n.y, -n.x) / (distance + 100.0f);   

Hier, n ist der Einheitsvektor, der auf das Schwarze Loch zeigt. Die Anziehungskraft ist eine modifizierte Version der inversen Quadratfunktion:

  • Die erste Modifikation ist, dass der Nenner ist Abstand ^ 2 + 10.000; Dies führt dazu, dass sich die Anziehungskraft einem maximalen Wert nähert, anstatt sich gegen unendlich zu neigen, da der Abstand sehr klein wird.
    • Wenn der Abstand viel größer als 100 Pixel ist, Abstand ^ 2 wird viel größer als 10.000. Daher 10.000 zu hinzufügen Abstand ^ 2 hat einen sehr kleinen Effekt und die Funktion nähert sich einer normalen inversen Quadratfunktion an.
    • Wenn der Abstand jedoch viel kleiner als 100 Pixel ist, hat der Abstand einen geringen Einfluss auf den Wert des Nenners, und die Gleichung wird ungefähr gleich: vel + = n
  • Die zweite Modifikation besteht darin, der Geschwindigkeit eine Seitenkomponente hinzuzufügen, wenn sich die Partikel dem Schwarzen Loch nahe genug nähern. Dies dient zwei Zwecken:
    1. Dadurch werden die Partikel im Uhrzeigersinn in Richtung des Schwarzen Lochs spiralförmig.
    2. Wenn sich die Teilchen nahe genug befinden, erreichen sie ein Gleichgewicht und bilden einen leuchtenden Kreis um das Schwarze Loch.

Spitze: Einen Vektor drehen, V, 90 ° im Uhrzeigersinn nehmen (V. Y, -V.X). Um ihn um 90 ° gegen den Uhrzeigersinn zu drehen, nehmen Sie (-V.Y, V.X).

Partikel produzieren

Ein schwarzes Loch erzeugt zwei Arten von Partikeln. Erstens spritzt es periodisch Partikel aus, die sich um ihn herum bewegen. Zweitens, wenn ein schwarzes Loch geschossen wird, werden spezielle Partikel gesprüht, die nicht von der Schwerkraft beeinflusst werden.

Fügen Sie dem folgenden Code hinzu BlackHole :: WasShot () Methode:

 Float-Farbton = fmodf (3.0f / 1000.0f * tTimer :: getTimeMS (), 6); tColor4f color = ColorUtil :: HSVToColor (Farbton, 0,25f, 1); const int numParticles = 150; float startOffset = Erweiterungen :: nextFloat (0, tMath :: PI * 2.0f / numParticles); für (int i = 0; i < numParticles; i++)  tVector2f sprayVel = MathUtil::fromPolar(tMath::PI * 2.0f * i / numParticles + startOffset, Extensions::nextFloat(8, 16)); tVector2f pos = mPosition + 2.0f * sprayVel; ParticleState state(sprayVel, ParticleState::kIgnoreGravity, 1.0f); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, color, 90, 1.5f, state); 

Dies funktioniert im Wesentlichen genauso wie bei den anderen Partikelexplosionen. Ein Unterschied besteht darin, dass wir den Farbton der Farbe basierend auf der gesamten verstrichenen Spielzeit auswählen. Wenn Sie das Schwarze Loch mehrmals in schneller Folge abschießen, wird der Farbton der Explosionen allmählich rotieren. Dies wirkt weniger unordentlich als die Verwendung von zufälligen Farben, lässt aber dennoch Variationen zu.

Für das umlaufende Partikelspray müssen wir eine Variable hinzufügen Schwarzes Loch Klasse, um die Richtung zu verfolgen, in die wir aktuell Partikel sprühen:

 protected: int mHitPoints; float mSprayAngle; BlackHole :: BlackHole (const tVector2f & position): mSprayAngle (0) …

Nun fügen wir der folgenden Liste Folgendes hinzu: BlackHole :: Update () Methode.

 // Die schwarzen Löcher sprühen umlaufende Partikel. Das Spray schaltet sich jede Viertelsekunde ein und aus. if ((tTimer :: getTimeMS () / 250)% 2 == 0) tVector2f sprayVel = MathUtil :: fromPolar (mSprayAngle, Extensions :: nextFloat (12, 15)); tColor4f color = ColorUtil :: HSVToColor (5, 0.5f, 0.8f); tVector2f pos = mPosition + 2.0f * tVector2f (sprayVel.y, -sprayVel.x) + Erweiterungen :: nextVector2 (4, 8); ParticleState-Status (sprayVel, ParticleState :: kEnemy, 1.0f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, color, 190, 1.5f, state);  // Drehe die Sprührichtung mSprayAngle - = tMath :: PI * 2.0f / 50.0f;

Dies bewirkt, dass die Schwarzen Löcher violette Partikel sprühen, die einen Ring bilden, der sich um das Schwarze Loch kreist:

Schiff Auspuff Feuer

Wie von den Gesetzen der geometrischen Neonphysik vorgegeben, treibt das Schiff des Spielers sich selbst an, indem es einen Strom feuriger Teilchen aus seinem Auspuff rauswirft. Mit unserer Particle Engine ist dieser Effekt einfach zu erstellen und verleiht der Schiffsbewegung ein optisches Flair.

Während sich das Schiff bewegt, erzeugen wir drei Teilchenströme: einen Mittelstrom, der direkt aus dem Schiff herausschießt, und zwei Seitenströme, deren Winkel relativ zum Schiff hin und her schwenken. Die beiden Seitenströme schwenken in entgegengesetzte Richtungen, um ein Kreuzmuster zu bilden. Die Seitenströme haben eine rötere Farbe, während der mittlere Strom eine heißere gelb-weiße Farbe hat. Die Animation unten zeigt den Effekt:


Damit das Feuer heller leuchtet, muss das Schiff zusätzliche Partikel aussenden, die folgendermaßen aussehen:


Diese Partikel werden gefärbt und mit den regulären Partikeln gemischt. Der Code für den gesamten Effekt wird unten gezeigt:

 void PlayerShip :: MakeExhaustFire () if (mVelocity.lengthSquared ()> 0.1f) mOrientation = Erweiterungen :: toAngle (mVelocity); float cosA = cosf (mOrientation); float sinA = sinf (mOrientation); tMatrix2x2f rot (tVector2f (cosA, sinA), tVector2f (-sinA, cosA)); float t = tTimer :: getTimeMS () / 1000.0f; tVector2f baseVel = Erweiterungen :: scaleTo (mVelocity, -3); tVector2f perpVel = tVector2f (baseVel.y, -baseVel.x) * (0,6f * (Float) sinf (t * 10.0f)); tColor4f-Seitenfarbe (0,78f, 0,15f, 0,04f, 1); tColor4f midColor (1,0f, 0,73f, 0,12f, 1); tVector2f pos = mPosition + rot * tVector2f (-25, 0); // Position des Auspuffrohrs des Schiffes. const float alpha = 0.7f; // mittlerer Teilchenstrom tVector2f velMid = baseVel + Extensions :: nextVector2 (0, 1); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (velMid, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, midColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (velMid, ParticleState: : kEnemy)); // seitliche Partikelströme tVector2f vel1 = baseVel + perpVel + Extensions :: nextVector2 (0, 0.3f); tVector2f vel2 = baseVel - perpVel + Erweiterungen :: nextVector2 (0, 0.3f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel1, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel2, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel1, ParticleState:) : kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel2, ParticleState:) : kEnemy)); 

In diesem Code läuft nichts Schleichendes. Wir verwenden eine Sinusfunktion, um den Schwenkeffekt in den Seitenströmen zu erzeugen, indem sie ihre Seitengeschwindigkeit über die Zeit variieren. Für jeden Stream erstellen wir zwei überlappende Partikel pro Frame: ein halbtransparentes, weißes LineParticle, und ein farbiges Glimmpartikel dahinter. Anruf
MakeExhaustFire () am Ende von PlayerShip.Update (), unmittelbar vor dem Setzen der Schiffsgeschwindigkeit auf Null.

Fazit

Mit all diesen Partikeleffekten sieht Shape Blaster ziemlich cool aus. Im letzten Teil dieser Serie werden wir einen weiteren Effekt hinzufügen: das Warping-Hintergrundraster.