Créez un jeu de tir vectoriel néon pour iOS manettes de jeu virtuelles et trous noirs

Dans cette série de didacticiels, je vais vous montrer comment créer un jeu de tir double-stick inspiré de Geometry Wars, avec des graphismes au néon, des effets de particules incroyables et une musique géniale, pour iOS avec C ++ et OpenGL ES 2.0. Dans cette partie, nous allons ajouter les contrôles de la manette de jeu virtuelle et les ennemis du "trou noir".

Vue d'ensemble

Jusqu'à présent, dans la série, nous avons mis en place le gameplay de base de notre jeu de tir double stick néon, Shape Blaster. Ensuite, nous ajouterons deux "manettes de jeu virtuelles" à l'écran pour contrôler le navire avec.


La saisie est un must pour tous les jeux vidéo et iOS nous offre un défi intéressant et ambigu avec la saisie multi-touch. Je vais vous montrer une approche, basée sur le concept de manettes de jeu virtuelles, où nous allons simuler des manettes de jeu en utilisant uniquement le toucher et un peu de logique complexe pour comprendre les choses. Après avoir ajouté les manettes de jeu virtuelles pour la saisie multi-touch, nous allons également ajouter des trous noirs au jeu..

Manettes de jeu virtuelles

Les commandes tactiles à l'écran constituent le principal moyen de saisie pour la majorité des applications et des jeux pour iPhone et iPad. En fait, iOS permet l'utilisation d'une interface multi-touch, ce qui signifie lire plusieurs points de contact en même temps. La beauté des interfaces tactiles réside dans le fait que vous pouvez définir l’interface comme vous le souhaitez, qu’il s’agisse d’un seul bouton, d’un contrôle virtuel ou d’un contrôle coulissant. Ce que nous allons implémenter, c'est une interface tactile que j'appellerai "manettes de jeu virtuelles".

UNE manette de jeu décrit généralement un contrôle physique standard en forme de plus similaire à l'interface plus sur un système Game Boy ou un contrôleur PlayStation (également appelé pavé directionnel ou D-pad). Une manette de jeu permet un mouvement dans les axes haut et bas, ainsi que dans les axes gauche et droit. Le résultat est que vous êtes en mesure de signaler huit directions distinctes, avec l’ajout de «aucune direction». Dans Shape Blaster, notre interface de manette de jeu ne sera pas physique, mais à l'écran, d'où une virtuel manette de jeu.


Une manette de jeu physique typique; le pavé directionnel dans ce cas est en forme de plus.

Bien qu'il n'y ait que quatre entrées, il existe huit directions (plus neutre) disponibles.

Pour avoir une manette de jeu virtuelle dans notre jeu, nous devons reconnaître la saisie tactile lorsque cela se produit et la convertir en une forme que le jeu comprend déjà..

Le gamepad virtuel implémenté ici fonctionne en trois étapes:

  1. Déterminer le type de contact.
  2. Déterminer si c'est dans la zone d'une manette de jeu à l'écran.
  3. Emule le toucher en appuyant sur une touche ou en déplaçant la souris.

À chaque étape, nous nous concentrerons uniquement sur le contact que nous avons et garderons trace du dernier événement de contact que nous avons dû comparer. Nous allons également garder une trace de la ID tactile, qui détermine quel doigt touche quel gamepad.

La capture d'écran ci-dessous montre comment les gamepads apparaîtront à l'écran:

Capture d'écran des manettes finales en position.

Ajout de multi-touch à Shape Blaster

dans le Utilitaire bibliothèque, regardons la classe d'événements nous allons principalement utiliser. tTouchEvent encapsule tout ce dont nous avons besoin pour gérer les événements tactiles à un niveau de base.

 class tTouchEvent public: enum EventType kTouchBegin, kTouchEnd, kTouchMove,; public: EventType mEvent; tPoint2f mLocation; uint8_t mID; public: tTouchEvent (const EventType & newEvent, const tPoint2f & newLocation, const uint8_t & newID): mEvent (newEvent), mLocation (newLocation), mID (newID) ;

le Type d'événement nous permet de définir le type d'événements que nous allons autoriser sans être trop compliqué. mLocation sera le point de contact réel, et milieu sera l’ID du doigt, qui commence à zéro et en ajoute un pour chaque doigt touché à l’écran. Si nous définissons le constructeur pour ne prendre que const références, nous pourrons instancier des classes d’événements sans avoir à créer explicitement des variables nommées pour elles.

Nous allons utiliser tTouchEvent exclusivement pour envoyer des événements tactiles de l'OS à notre Contribution classe. Nous l'utiliserons aussi plus tard pour mettre à jour la représentation graphique des manettes de jeu dans le VirtualGamepad classe.

La classe d'entrée

La version originale XNA et C # de la Contribution La classe peut gérer les entrées de la souris, du clavier et de la manette de jeu physique. La souris est utilisée pour tirer sur un point quelconque de l’écran à partir de n’importe quelle position; le clavier peut être utilisé pour déplacer et tirer dans des directions données. Puisque nous avons choisi d'émuler l'entrée d'origine (pour rester fidèle à un "port direct"), nous conservons la plupart du code original, en utilisant les noms. clavier et Souris, même si nous n'avons ni sur les appareils iOS.

Voici comment notre Contribution la classe va regarder. Pour chaque périphérique, nous devrons conserver un "instantané actuel" et un "instantané précédent" afin de pouvoir savoir ce qui a changé entre le dernier événement d'entrée et l'événement d'entrée actuel. Dans notre cas, État de la souris et mKeyboardState sont "l'instantané actuel", et mLastMouseState et mLastKeyboardState représente "l'instantané précédent".

 classe Input: tSingleton public protected: tPoint2f mMouseState; tPoint2f mLastMouseState; tPoint2f mFreshMouseState; std :: vector mKeyboardState; std :: vector mLastKeyboardState; std :: vector mFreshKeyboardState; bool mIsAimingWithMouse; uint8_t mLeftEngaged; uint8_t mRightEngaged; public: enum KeyType kUp = 0, kLeft, kDown, kRight, kW, kA, kS, kD,; protected: tVector2f GetMouseAimDirection () const; protégé: entrée (); public: tPoint2f getMousePosition () const; void update (); // Vérifie si une touche vient d'être pressée bool wasKeyPressed (KeyType) const; tVector2f getMovementDirection () const; tVector2f getAimDirection () const; void onTouch (const tTouchEvent & msg); classe d'amis tSingleton; classe d'amis VirtualGamepad; ; Input :: Input (): mMouseState (-1, -1), mLastMouseState (-1, -1), mIsAimingWithMouse (false), mLeftEngaged (255), mRightEngaged (255) mKeyboardState.resize (8); mLastKeyboardState.resize (8); mFreshKeyboardState.resize (8); pour (size_t i = 0; i < 8; i++)  mKeyboardState[i] = false; mLastKeyboardState[i] = false; mFreshKeyboardState[i] = false;   tPoint2f Input::getMousePosition() const  return mMouseState; 

Mise à jour de l'entrée

Sur un PC, tout événement que nous obtenons est "distinct", ce qui signifie qu'un mouvement de la souris est différent du fait de pousser la lettre UNE, et même la lettre UNE est assez différent de la lettre S que nous pouvons dire que ce n'est pas exactement le même événement.

Avec iOS, nous seulement jamais obtenez des événements de saisie tactile, et une touche n'est pas assez distincte d'une autre pour que nous puissions dire s'il s'agit d'un mouvement de souris ou d'une pression sur une touche, ou même de quelle touche il s'agit. De notre point de vue, tous les événements se ressemblent.

Pour aider à comprendre cette ambiguïté, nous allons introduire deux nouveaux membres, mFreshMouseState et mFreshKeyboardState. Leur but est d'agréger ou de "capturer tout" les événements d'une trame particulière, sans modifier les autres variables d'état autrement. Une fois que nous sommes convaincus qu'un cadre est passé, nous pouvons mettre à jour l'état actuel avec les "nouveaux" membres en appelant Entrée :: mise à jour. Entrée :: mise à jour indique également notre état d'entrée pour avancer.

 void Input :: update () mLastKeyboardState = mKeyboardState; mLastMouseState = mMouseState; mKeyboardState = mFreshKeyboardState; mMouseState = mFreshMouseState; if (mKeyboardState [kLeft] || mKeyboardState [kRight] || mKeyboardState [kUp] || mKeyboardState [kDown]) mIsAimingWithMouse = false;  else if (mMouseState! = mLastMouseState) mIsAimingWithMouse = true; 

Puisque nous le ferons une fois par image, nous appellerons Entrée :: update () de l'Intérieur GameRoot :: onRedrawView ():

 // Dans GameRoot :: onRedrawView () Input :: getInstance () -> update ();

Voyons maintenant comment nous transformons la saisie tactile en souris ou en clavier simulés. Premièrement, nous prévoyons d’avoir deux zones rectangulaires différentes qui représentent les manettes de jeu virtuelles. Tout ce qui se trouve en dehors de ces zones est considéré comme «un événement de souris». rien à l'intérieur, nous allons considérer "certainement un événement de clavier."

Dans les cases rouges, nous allons mapper notre saisie au clavier simulé; tout ce que nous traitons comme une entrée de souris.

Regardons Entrée :: onTouch (), qui obtient tous les événements tactiles. Nous allons prendre une vue d'ensemble de la méthode et noter les zones FAIRE où code plus spécifique devrait être:

 void Input :: onTouch (const tTouchEvent & msg) tPoint2f leftPoint = VirtualGamepad :: getInstance () -> mLeftPoint - tPoint2f (18, 18); tPoint2f rightPoint = VirtualGamepad :: getInstance () -> mRightPoint - tPoint2f (18, 18); tPoint2f intPoint ((int) msg.mLocation.x, (int) msg.mLocation.y); bool mouseDown = (msg.mEvent == tTouchEvent :: kTouchBegin) || (msg.mEvent == tTouchEvent :: kTouchMove); if (! mouseDown) if (msg.mID == mLeftEngaged) // TODO: définit toutes les touches de déplacement comme "touche active" else if (msg.mID == mRightEngaged) // TODO: définit toutes les touches de déclenchement comme "touche haut" if (mouseDown && tRectf (leftPoint, 164, 164) .contains (intPoint)) mLeftEngaged = msg.mID; // TODO: Définir toutes les clés de mouvement comme "clé vers le haut" // TODO: Déterminer les clés de mouvement à définir if (mouseDown && tRectf (rightPoint, 164, 164) .contains (intPoint)) mRightEngaged = msg.mID; // TODO: Définir toutes les touches de déclenchement comme "clé vers le haut" // TODO: Déterminer les touches de déclenchement à définir if (! TRectf (leftPoint, 164, 164) .contains (intPoint) &&! TRectf (rightPoint, 164, 164) .contains (intPoint)) // Si nous le faisons ici, touch était certainement un "événement de souris" mFreshMouseState = tPoint2f ((int32_t) msg.mLocation.x, (int32_t) msg.mLocation.y); 

Le code est assez simple, mais il y a une logique puissante que je vais souligner:

  1. Nous déterminons où les manettes gauche et droite seront affichées à l'écran, afin de voir si nous y sommes lorsque nous touchons ou lâchons prise. Ceux-ci sont stockés dans le point gauche et point droit variables locales.
  2. Nous déterminons le souris vers le bas Etat: si nous "pressons" avec un doigt, nous devons savoir si c'est dans les point gaucheest rect ou point droitest correct, et si oui, prenez des mesures pour mettre à jour le Frais état pour le clavier. Si ce n'est pas rectiligne, nous supposerons qu'il s'agit d'un événement de souris et mettre à jour le Frais Etat pour le Souris.
  3. Enfin, nous gardons une trace des ID de contact (ou ID de doigt) au fur et à mesure qu’ils sont pressés; si nous détectons un doigt qui s’élève de la surface et qui est associé à une manette de jeu active, nous réinitialisons le clavier simulé pour cette manette de jeu en conséquence..

Maintenant que nous voyons la grande image, approfondissons un peu plus loin.

Combler les lacunes

Lorsque vous soulevez un doigt de la surface de l'iPhone ou de l'iPad, nous vérifions s'il s'agit d'un doigt dont nous savons qu'il se trouve sur une manette de jeu. Si tel est le cas, nous réinitialisons toutes les "clés simulées" de cette manette de jeu:

 if (! mouseDown) if (msg.mID == mLeftEngaged) mFreshKeyboardState [kA] = false; mFreshKeyboardState [kD] = false; mFreshKeyboardState [kW] = false; mFreshKeyboardState [kS] = false;  else if (msg.mID == mRightEngaged) mFreshKeyboardState [kUp] = false; mFreshKeyboardState [kDown] = false; mFreshKeyboardState [kLeft] = false; mFreshKeyboardState [kRight] = false; 

La situation est quelque peu différente quand il y a une touche qui commence à la surface ou qui bouge; nous vérifions si le contact est dans l'un ou l'autre des manettes. Comme le code des deux manettes est similaire, nous ne regarderons que la manette gauche (qui traite du mouvement).

Chaque fois que nous obtenons un événement tactile, nous effacerons complètement l'état du clavier pour cette manette de jeu en particulier et vérifierons dans notre zone de droite pour déterminer la ou les touches sur lesquelles appuyer. Donc, bien que nous ayons un total de huit directions (plus la position neutre), nous ne vérifierons jamais que quatre rectangles: un pour le haut, un pour le bas, un pour le gauche et un pour le droite..

Les neuf domaines d’intérêt de notre manette de jeu.
 if (mouseDown && tRectf (leftPoint, 164, 164) .contains (intPoint)) mLeftEngaged = msg.mID; mFreshKeyboardState [kA] = false; mFreshKeyboardState [kD] = false; mFreshKeyboardState [kW] = false; mFreshKeyboardState [kS] = false; if (tRectf (leftPoint, 72, 164) .contains (intPoint)) mFreshKeyboardState [kA] = true; mFreshKeyboardState [kD] = false;  else if (tRectf (leftPoint + tPoint2f (128, 0), 72, 164) .contains (intPoint)) mFreshKeyboardState [kA] = false; mFreshKeyboardState [kD] = true;  if (tRectf (leftPoint, 164, 72) .contains (intPoint)) mFreshKeyboardState [kW] = true; mFreshKeyboardState [kS] = false;  else if (tRectf (leftPoint + tPoint2f (0, 128), 164, 72) .contains (intPoint)) mFreshKeyboardState [kW] = false; mFreshKeyboardState [kS] = true; 

Affichage de graphiques pour la manette de jeu virtuelle

Si vous exécutez le jeu maintenant, vous aurez le support d'une manette de jeu virtuelle, mais vous ne pourrez pas réellement voir où commencent et se terminent les manettes de jeu virtuelles..

C'est là que le VirtualGamepad la classe entre en jeu. le VirtualGamepadL'objectif principal de est de dessiner les manettes de jeu à l'écran. La manière dont nous allons afficher la manette de jeu correspond à la façon dont les autres jeux ont tendance à le faire s’ils ont des manettes de jeu: un cercle "de base" plus grand et un cercle de "manche de contrôle" plus petit que nous pouvons déplacer. Cela ressemble à un joystick d'arcade de haut en bas, et plus facile à dessiner que d'autres alternatives.

Tout d'abord, notez que les fichiers image vpad_top.png et vpad_bot.png ont été ajoutés au projet. Modifions le Art classe pour les charger:

 classe Art: tSingleton public protected:… tTexture * mVPadBottom; tTexture * mVPadTop;… public:… tTexture * getVPadBottom () const; tTexture * getVPadTop () const;… classe d'amis tSingleton; ; Art :: Art () … mVPadTop = new tTexture (tSurface ("vpad_top.png")); mVPadBottom = new tTexture (tSurface ("vpad_bot.png"));  tTexture * Art :: getVPadBottom () const return mVPadBottom;  tTexture * Art :: getVPadTop () const return mVPadTop; 

le VirtualGamepad classe dessine les deux manettes de jeu à l’écran et conserve Etat informations dans les membres mLeftStick et mRightStick sur où dessiner les "manettes" des manettes de jeu.

J'ai choisi des positions légèrement arbitraires pour les manettes de jeu, qui sont initialisées dans le mLeftPoint et mRightPoint membres - les calculs les placent à environ 3,75% du bord gauche ou droit de l'écran et environ 13% du bas de l'écran. J'ai basé ces mesures sur un jeu commercial avec des gamepads virtuels similaires mais un gameplay différent.

 classe VirtualGamepad: tSingleton public public: enum State kCenter = 0x00, kTop = 0x01, kBottom = 0x02, kLeft = 0x04, kRight = 0x08, kTopLeft = 0x05, kTopRight = 0x09, kBottomLeft = 0x06, kBottomRef = 0x06, kBottomRight = 0x0a,; protected: tPoint2f mLeftPoint; tPoint2f mRightPoint; int mLeftStick; int mRightStick; protected: VirtualGamepad (); void DrawStickAtPoint (tSpriteBatch * spriteBatch, const tPoint2f & point, State state); void UpdateBasedOnKeys (); public: void draw (tSpriteBatch * spriteBatch); void update (const tTouchEvent & msg); classe d'amis tSingleton; entrée de classe ami; ; VirtualGamepad :: VirtualGamepad (): mLeftStick (kCenter), mRightStick (kCenter) mLeftPoint = tPoint2f (int (3.0f / 80.0f * 800.0f), 600 - int (21.0f / 160.0f * 600.0f) - 128); mRightPoint = tPoint2f (800 - int (3.0f / 80.0f * 800.0f) - 128, 600 - int (21.0f / 160.0f * 600.0f) - 128); 

Comme mentionné précédemment, mLeftStick et mRightStick sont des bitmasks, et leur utilisation est de déterminer où dessiner le cercle intérieur du gamepad. Nous allons calculer le bitmask dans la méthode VirtualGamepad :: UpdateBasedOnKeys ().

Cette méthode est appelée immédiatement après Entrée :: onTouch, afin que nous puissions lire les états "frais" et savoir qu'ils sont à jour:

 void VirtualGamepad :: UpdateBasedOnKeys () Entrée * inp = Entrée :: getInstance (); mLeftStick = kCenter; if (inp-> mFreshKeyboardState [Input :: kA]) mLeftStick | = kLeft;  else if (inp-> mFreshKeyboardState [Input :: kD]) mLeftStick | = kRight;  if (inp-> mFreshKeyboardState [Entrée :: kW]) mLeftStick | = kTop;  else if (inp-> mFreshKeyboardState [Input :: kS]) mLeftStick | = kBottom;  mRightStick = kCenter; if (inp-> mFreshKeyboardState [Input :: kLeft]) mRightStick | = kLeft;  else if (inp-> mFreshKeyboardState [Input :: kRight]) mRightStick | = kRight;  if (inp-> mFreshKeyboardState [Input :: kUp]) mRightStick | = kTop;  else if (inp-> mFreshKeyboardState [Input :: kDown]) mRightStick | = kBottom; 

Pour dessiner un gamepad individuel, nous appelons VirtualGamepad :: DrawStickAtPoint (); cette méthode ne sait ni quel jeu est le gamepad que vous dessinez, elle sait seulement où vous voulez le dessiner et dans quel état le dessiner. Parce que nous avons utilisé des masques de bits et que nous avons calculé à l'avance, notre méthode devient plus petite et plus facile à utiliser. lis:

 void VirtualGamepad :: DrawStickAtPoint (tSpriteBatch * spriteBatch, const tPoint2f & point, état d'état) tPoint2f offset = tPoint2f (18, 18); spriteBatch-> draw (10, Art :: getInstance () -> getVPadBottom (), point, tOptional()); commutateur (état) case kCenter: offset + = tPoint2f (0, 0); Pause; case kTopLeft: offset + = tPoint2f (-13, -13); Pause; cas kTop: offset + = tPoint2f (0, -18); Pause; case kTopRight: offset + = tPoint2f (13, -13); Pause; case kDroite: offset + = tPoint2f (18, 0); Pause; case kBottomRight: offset + = tPoint2f (13, 13); Pause; case kBottom: offset + = tPoint2f (0, 18); Pause; case kBottomLeft: offset + = tPoint2f (-13, 13); Pause; case kLeft: offset + = tPoint2f (-18, 0); Pause;  spriteBatch-> draw (11, Art :: getInstance () -> getVPadTop (), point + offset, tOptional()); 

Dessiner deux manettes devient beaucoup plus facile car il suffit d'appeler deux fois la méthode ci-dessus. Regardons VirtualGamepad :: draw ():

 void VirtualGamepad :: draw (tSpriteBatch * spriteBatch) DrawStickAtPoint (spriteBatch, mLeftPoint, (State) mLeftStick); DrawStickAtPoint (spriteBatch, mRightPoint, (État) mRightStick); 

Enfin, nous devons dessiner le gamepad virtuel, donc dans GameRoot :: onRedrawView (), ajoutez la ligne suivante:

 VirtualGamepad :: getInstance () -> draw (mSpriteBatch);

C'est tout. Si vous lancez le jeu maintenant, vous devriez voir les manettes de jeu virtuelles produire leurs effets. Lorsque vous touchez à l'intérieur du gamepad de gauche, vous devez vous déplacer. Lorsque vous touchez le bon gamepad, la direction de tir doit changer. En fait, vous pouvez utiliser les deux manettes en même temps, et même vous déplacer en utilisant la manette gauche et toucher en dehors de la manette droite pour faire bouger la souris. Et quand vous lâchez le ballon, vous arrêtez de bouger et (éventuellement) vous arrêtez de tirer..

Résumé de la technique du gamepad virtuel

Nous avons entièrement implémenté la prise en charge du gamepad virtuel, et cela fonctionne, mais vous le trouverez peut-être un peu maladroit ou difficile à utiliser. Pourquoi est-ce le cas? C’est là que le véritable défi des commandes tactiles sur iOS intervient avec les jeux traditionnels qui n’avaient pas été conçus initialement pour eux..

Vous n'êtes pas seul, cependant. Beaucoup de jeux souffrent de ces problèmes et les ont surmontés.

Voici quelques choses que j'ai observées avec la saisie à l'écran tactile. vous pourriez avoir des observations similaires vous-même:

Premièrement, les contrôleurs de jeu ont une sensation différente de celle d’un écran tactile plat. vous savez où se trouve votre doigt sur une vraie manette de jeu et comment empêcher vos doigts de glisser. Cependant, sur un écran tactile, vos doigts risquent de dériver légèrement trop loin de la zone tactile. Par conséquent, votre saisie risque de ne pas être correctement reconnue et vous risquez de ne pas vous en rendre compte tant qu'il ne sera pas trop tard..

Deuxièmement, vous avez peut-être aussi remarqué, lorsque vous jouez avec les commandes tactiles, que votre main obscurcit votre vision, de sorte que votre navire peut être heurté par un ennemi situé sous votre main que vous n'aviez pas vu au début.!

Enfin, vous constaterez peut-être que les zones tactiles sont plus faciles à utiliser sur un iPad que sur un iPhone ou inversement. Nous avons donc des problèmes avec une taille d'écran différente qui affecte notre "taille de zone de saisie", ce qui est certainement quelque chose que nous n'éprouvons pas autant sur un ordinateur de bureau. (La plupart des claviers et souris ont la même taille et agissent de la même manière, ou peuvent être ajustés.)

Voici quelques modifications que vous pouvez apporter au système de saisie décrit dans cet article:

  • Dessinez l'emplacement central de votre manette de jeu où commence votre contact. Cela permet à la main du joueur de bouger très légèrement sans impact, et signifie qu'ils peuvent toucher n'importe où sur l'écran.
  • Rendez votre "zone jouable" plus petite et déplacez complètement la manette de jeu de la zone jouable. Maintenant, vos doigts ne gêneront pas votre vue.
  • Créez des interfaces utilisateur distinctes pour iPhone et iPad. Cela vous permettra d’ajuster la conception en fonction du type d’appareil, mais vous devrez également tester différents appareils..
  • Rendre les ennemis ou le joueur navire légèrement plus lent. Cela permet potentiellement à l'utilisateur de jouer au jeu plus facilement, mais cela rend également votre jeu plus facile à gagner..
  • Éliminez complètement les manettes de jeu virtuelles et utilisez un autre schéma. Vous êtes responsable, après tout!

Encore une fois, c'est à vous de décider ce que vous voulez faire et comment vous voulez le faire. Sur le plan positif, il existe de nombreuses façons de faire de la saisie tactile. La partie difficile consiste à bien faire les choses et à rendre vos joueurs heureux.

Trous noirs

L’un des ennemis les plus intéressants de Geometry Wars est le trou noir. Examinons comment nous pouvons créer quelque chose de similaire dans Shape Blaster. Nous allons créer la fonctionnalité de base maintenant, et nous reverrons l'ennemi dans le prochain tutoriel pour ajouter des effets et des interactions de particules..

Un trou noir avec des particules en orbite

Fonctionnalité de base

Les trous noirs attireront le vaisseau du joueur, les ennemis proches et (après le prochain tutoriel) des particules, mais repousseront les balles..

Il existe de nombreuses fonctions que nous pouvons utiliser pour attirer ou repousser. Le plus simple consiste à utiliser une force constante afin que le trou noir tire avec la même force, quelle que soit la distance de l'objet. Une autre option consiste à augmenter la force de manière linéaire, de zéro à une certaine distance maximale, à la force maximale pour les objets directement au-dessus du trou noir. Si nous souhaitons modéliser la gravité de manière plus réaliste, nous pouvons utiliser le carré inverse de la distance, ce qui signifie que la force de gravité est proportionnelle à 1 / (distance ^ 2).

Nous utiliserons chacune de ces trois fonctions pour gérer différents objets. Les balles seront repoussées avec une force constante; les ennemis et le navire du joueur seront attirés avec une force linéaire; et les particules utiliseront une fonction carrée inverse.

Nous allons faire une nouvelle classe pour les trous noirs. Commençons par les fonctionnalités de base:

 class BlackHole: public Entity protected: int mHitPoints; public: BlackHole (const tVector2f & position); void update (); vide draw (tSpriteBatch * spriteBatch); vide wasShot (); kill kill (); ; BlackHole :: BlackHole (const tVector2f & position): mHitPoints (10) mImage = Art :: getInstance () -> getBlackHole (); mPosition = position; mRadius = mImage-> getSurfaceSize (). width / 2.0f; mKind = kBlackHole;  void BlackHole :: wasShot () mHitPoints--; si (mHitPoints <= 0)  mIsExpired = true;   void BlackHole::kill()  mHitPoints = 0; wasShot();  void BlackHole::draw(tSpriteBatch* spriteBatch)  // make the size of the black hole pulsate float scale = 1.0f + 0.1f * sinf(tTimer::getTimeMS() * 10.0f / 1000.0f); spriteBatch->draw (1, mImage, tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y), tOptional(), mColor, mOrientation, getSize () / 2.0f, tVector2f (scale)); 

Les trous noirs prennent dix coups à tuer. Nous ajustons légèrement l'échelle de l'image-objet pour la faire vibrer. Si vous décidez que la destruction de trous noirs doit également vous rapporter des points, vous devez procéder aux mêmes ajustements. Trou noir classe comme nous l'avons fait avec le Ennemi classe.

Ensuite, nous allons faire en sorte que les trous noirs appliquent une force sur d’autres entités. Nous aurons besoin d'une petite méthode d'assistance de notre EntityManager:

 std :: list EntityManager :: getNearbyEntities (const tPoint2f & pos, rayon de flottant) std :: list résultat; pour (std :: list:: iterator iter = mEntities.begin (); iter! = mEntities.end (); iter ++) if (* iter) if (pos.distanceSquared ((* iter) -> getPosition ()) < radius * radius)  result.push_back(*iter);    return result; 

Cette méthode pourrait être rendue plus efficace en utilisant un schéma de partitionnement spatial plus compliqué, mais pour le nombre d'entités que nous aurons, c'est bien tel quel..

Maintenant, nous pouvons faire en sorte que les trous noirs appliquent une force BlackHole :: update () méthode:

 void BlackHole :: update () std :: list entités = EntityManager :: getInstance () -> getNearbyEntities (mPosition, 250); pour (std :: list:: iterator iter = entités.begin (); iter! = entités.end (); iter ++) if ((* iter) -> getKind () == kEnemy &&! ((Enemy *) (* iter)) -> getIsActive ()) // Ne fait rien sinon if ((* iter) -> getKind () == kBullet) tVector2f temp = ((* iter) - - getPosition () - mPosition); (* iter) -> setVelocity ((* iter) -> getVelocity () + temp.normalize () * 0.3f);  else tVector2f dPos = mPosition - (* iter) -> getPosition (); longueur de float = dPos.length (); (* iter) -> setVelocity ((* iter) -> getVelocity () + dPos.normalize () * tMath :: mix (2.0f, 0.0f, longueur / 250.0f)); 

Les trous noirs n'affectent que les entités situées dans un rayon choisi (250 pixels). Les balles situées dans ce rayon ont une force de répulsion constante, tandis que tout le reste a une force d'attraction linéaire..

Nous devrons ajouter la gestion des collisions pour les trous noirs à la EntityManager. Ajouter un std :: list pour les trous noirs comme nous l'avons fait pour les autres types d'entités, et ajoutez le code suivant dans EntityManager :: handleCollisions ():

 // gérer les collisions avec des trous noirs pour (std :: list:: itérateur i = mBlackHoles.begin (); i! = mBlackHoles.end (); i ++) pour (std :: list:: itérateur j = mEnemies.begin (); j! = mEnemies.end (); j ++) if ((* j) -> getIsActive () && isColliding (* i, * j)) (* j) -> wasShot ();  pour (std :: list:: itérateur j = mBullets.begin (); j! = mBullets.end (); j ++) if (isColliding (* i, * j)) (* j) -> setExpired (); (* i) -> wasShot ();  if (isColliding (PlayerShip :: getInstance (), * i)) KillPlayer (); Pause; 

Enfin, ouvrez le EnemySpawner classe et qu’il crée des trous noirs. J'ai limité le nombre maximum de trous noirs à deux et j'ai donné une chance sur 600 qu'un trou noir apparaisse à chaque image.

 if (EntityManager :: getInstance () -> getBlackHoleCount () < 2 && int32_t(tMath::random() * mInverseBlackHoleChance) == 0)  EntityManager::getInstance()->add (new BlackHole (GetSpawnPosition ())); 

Conclusion

Nous avons discuté et ajouté des manettes de jeu virtuelles, ainsi que des trous noirs en utilisant diverses formules de force. Shape Blaster commence à bien paraître. Dans la prochaine partie, nous ajouterons des effets de particules fous et superbes.

Références

  • Crédit photo: manette Wii de kazuma jp.