Construire un jeu Caterpillar avec Cocos2D détection de collision

C’est le sixième volet de notre série de didacticiels Cocos2D sur le clonage de Centipede pour iOS. Assurez-vous d'avoir complété les parties précédentes avant de commencer.


Dernière fois…

Dans le dernier tutoriel, je vous ai montré comment créer un tableau d'objets missiles et en tirer un flux constant. Vous avez également appris l’interaction de base entre les joueurs dans Cocos2D afin de déplacer le lecteur..

Dans le tutoriel d'aujourd'hui, nous allons explorer comment configurer de base détection de collision dans Cocos2D. Bien que cette solution ne soit pas toujours optimale, elle est certainement la plus rapide et la plus facile à mettre en œuvre..


Étape 1: Collision missile / germination

La collision de missiles avec les germes ressemble beaucoup à la collision entre le joueur et les germes. Nous vérifions simplement les limites de chaque missile en jeu par rapport aux limites de chaque pousse en jeu et déterminons s’il ya collision. Quand une pousse a été atteinte, on décrémente sa vie, on modifie son opacité et on l'enlève si sa vie atteint 0.

Ouvrez Missile.m, importez Sprout.h et ajoutez le code suivant au bas de la méthode de mise à jour:

 CGRect missileRect = [self getBounds]; [self.gameLayer.sprouts enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Sprout * sprout = (Sprout *) obj; CGRect sproutRect = [sprout getBounds]; if (CGRectIntersectsRect (missileRect, sproutRect)) self.dirty = YES; sprout.lives--; ];

Au fur et à mesure que chaque missile se met à jour, il énumère tous les germes en jeu, en vérifiant si leurs droits de croisement se croisent. En cas de collision, nous réglons le missile sur "sale" et décrémentons la vie de la pousse en conséquence. Nous avons déjà écrit le code pour modifier l'opacité des germes en fonction de leur durée de vie. Ainsi, si vous lancez le jeu à ce stade, vous devriez voir les germes changer lorsqu'ils sont touchés. Le problème est qu'ils sont toujours en jeu. Nous devons éliminer les germes avec 0 vies.

Cela peut être fait à l'intérieur du mettre à jour: méthode dans GameLayer.m. Ouvrez GameLayer.m et ajoutez le code suivant au bas de la mettre à jour: méthode:

 // 1 __block Sprout * deadSprout = nil; [self.sprouts enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * arrêt) Sprout * sprout = (Sprout *) obj; if (sprout.lives == 0) deadSprout = sprout; * stop = YES; ]; // 2 if (deadSprout) [self.spritesBatchNode removeChild: deadSprout.sprite cleanup: YES]; [self.sprouts removeObject: deadSprout]; 
  1. Nous énumérons chacune des pousses à la recherche d'une morte. Pour simplifier les choses, nous effaçons une seule pousse par itération.
  2. Si un germe mort existe, nous retirons son sprite du noeud de lot et le germe de notre tableau de choux..

Maintenant, lancez le jeu et vous verrez les pousses se faner une fois touchées et éventuellement disparaître.


Étape 2: Collision Caterpillar

Nous devons maintenant ajouter la détection de collision entre le missile et la chenille. Cette interaction est ce qui fait de votre application un jeu. Une fois touchée, la chenille doit se fendre au segment touché et chaque nouvelle "chenille" doit voyager dans des directions distinctes. Le segment qui a été touché est ensuite transformé en une pousse.

Commencez par ouvrir Missile.m, importez Caterpillar.h et Segment.h, puis ajoutez le code suivant au bas de la fenêtre. mettre à jour: méthode:

 __block Caterpillar * hitCaterpillar = nil; __block Segment * hitSegment = nil; // 1 [self.gameLayer.caterpillars enumerateObjectsUsingBsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Caterpillar * chenille = (Caterpillar *) obj; [caterpillar.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Segment * segment = (Segment *) obj; CGRect segmentRect = [segment getBounds]; // 2 if (CGRectIntersectsRect (missileRect, segmentRect)) self.dirty = YES; hitCaterpillar = [chenille conservée]; hitSegment = [segment retenue]; * stop = YES; ]; ]; // 3 if (hitCaterpillar && hitSegment) [self.gameLayer splitCaterpillar: hitCaterpillar atSegment: hitSegment]; [hitSegment release]; [hitCaterpillar release]; 
  1. Énumérer toutes les chenilles en jeu et énumérer chacun de leurs segments.
  2. Recherchez une collision et rappelez-vous quel segment de la chenille a été touché.
  3. Si nous avons un succès, nous divisons la chenille au segment actuel. Ne vous inquiétez pas, nous allons implémenter cette méthode sous peu.

Maintenant que le missile entre en collision avec la chenille, nous devons faire certaines choses. La première consiste à écrire la logique permettant de scinder la chenille sur un segment donné. Cela sera fait dans GameLayer.m. Tout d’abord, ouvrez GameLayer.h et ajoutez les déclarations de classe suivantes:.

 @class Caterpillar; @class Segment;

Ensuite, déclarez la méthode suivante:

 - (vide) splitCaterpillar: (Caterpillar *) chenille atSegment: (segment *) segment;

Avant de commencer l'implémentation, nous devons ajouter deux fichiers au projet. Téléchargez NSArray + Reverse, décompressez-le et faites glisser les deux fichiers dans votre projet. C'est simplement une catégorie sur NSMutableArray qui nous donne une méthode inverse. Maintenant, ouvrez GameLayer.m, importez Segment.h et NSArray + Reverse.h et implémentez la méthode suivante:

 - (void) splitCaterpillar: (Caterpillar *) caterpillar atSegment: (segment *) segment // 1 if ([caterpillar.segments count]] == 1) [self.spritesBatchNode removeChild: segment.sprite cleanup: NO]; [self.caterpillars removeObject: caterpillar]; [self createSproutAtPostion: segment.position]; revenir;  // 2 [self.spritesBatchNode removeChild: segment.sprite cleanup: NO]; // 3 [self createSproutAtPostion: segment.position]; // 4 NSInteger indexOfSegement = [caterpillar.segments indexOfObject: segment]; NSMutableArray * headSegments = [tableau NSMutableArray]; NSMutableArray * tailsSegments = [tableau NSMutableArray]; // 5 [caterpillar.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) if (idx < indexOfSegement)  [headSegments addObject:obj];  else if(idx > indexOfSegement) [tailsSegments addObject: obj]; ]; // 6 if ([tailsSegments count]> 0) // 7 [tailsSegments reverse]; // 8 Caterpillar * newCaterpillar = [[[Allocation Caterpillar] initWithGameLayer: segments auto: tailsSegments niveau: self.level] autorelease]; newCaterpillar.position = [[tailsSegments objectAtIndex: 0] position]; // 9 if (caterpillar.currentState == CSRight || caterpillar.previousState == CSRight) // se dirigeait bien si (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // se dirige vers newCaterpillar .previousState = CSUpRight;  else // se dirige vers newCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSLeft;  else // était à gauche si (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // est en train de descendre newCaterpillar.previousState = CSUpRight;  else // se dirige vers newCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSRight;  [self.caterpillars addObject: newCaterpillar];  // 10 if ([headSegments count]> 0) caterpillar.segments = headSegments;  else [self.caterpillars removeObject: caterpillar]; 
  1. Si nous frappons une chenille à un seul segment (juste une tête), nous retirons cette chenille du jeu et la convertissons en une pousse..
  2. Supprimer l'image-objet du segment touché du noeud de traitement par lots.
  3. Convertissez le segment touché en pousse (nous allons implémenter cette méthode dans un instant).
  4. Nous devons diviser la chenille en deux rangées, la partie principale et la partie arrière..
  5. Déterminez où tombent les autres segments (section de la tête ou de la queue) et ajoutez-les aux tableaux appropriés.
  6. Vérifiez s'il y a des segments de queue.
  7. Inverser le tableau des segments de queue. Il s'agit d'une catégorie sur NSMutableArray mentionnée ci-dessus. Nous devons inverser les segments afin de déplacer la chenille dans la direction opposée.
  8. Créez un nouvel objet chenille en utilisant la section de queue. Nous allons mettre en œuvre cette méthode momentanément.
  9. C'est le courage de cette méthode. Nous déterminons ici la direction actuelle (gauche ou droite) et la direction générale actuelle (haut ou bas) de la chenille qui a été touchée afin d'envoyer la nouvelle chenille dans la direction opposée..
  10. S'il reste encore une tête, nous plaçons simplement la chenille qui a été touchée sur les segments restants de la tête..

Wow, c'était beaucoup à prendre. Tu es toujours avec moi? Quelques méthodes que nous avons utilisées ici et dans le code précédent doivent encore être implémentées. La première méthode à implémenter est createSproutAtPosition:. Ajoutez la déclaration de méthode suivante à votre interface privée en haut de GameLayer.m:

 - (void) createSproutAtPostion: (CGPoint) position;

Maintenant, implémentez la méthode suivante:

 - (void) createSproutAtPostion: (CGPoint) position // 1 int x = (position.x - kGameAreaStartX) / kGridCellSize; int y = (kGameAreaStartY - kGridCellSize + kGameAreaHeight + kGridCellSize / 2 - position.y) / kGridCellSize; // 2 Sprout * sprout = [[Sprout alloc]] initWithGameLayer: self]; sprout.position = position; [self.sprouts addObject: sprout]; _locations [x] [y] = OUI; 
  1. Cela traduit notre position sur les coordonnées de l'écran en coordonnées de grille. En utilisant nos compétences en algèbre de 8e année, nous tirons cela du code de positionnement écrit dans la partie 2.
  2. Créer une nouvelle pousse et l'ajouter au jeu.

La dernière méthode que nous devons mettre en œuvre pour que tout cela fonctionne est initWithGameLayer: segments: niveau: dans la classe chenille. Cette méthode sera chargée de construire une nouvelle chenille à partir des segments transférés. Ouvrez Caterpillar.h et ajoutez la déclaration de méthode suivante:

 - (id) initWithGameLayer: segments de couche (GameLayer *): niveau de segments (NSMutableArray *): niveau (NSInteger);

Maintenant, ouvrez Caterpillar.m et implémentez la méthode suivante:

 - (id) initWithGameLayer: (GameLayer *) segments de couche: (NSMutableArray *) niveau de segments: (NSInteger) niveau if (self = [super initWithGameLayer: layer]) self.segments = segments; self.level = level; self.currentState = CSRight; self.previousState = CSDownLeft; // définit la position du reste des segments __block int x = 0; __block Segment * parentSegment = [self.segments objectAtIndex: 0]; parentSegment.parent = nil; [self.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Segment * segment = (Segment *) obj; if (x ++> 0) if (! [segment isEqual: parentSegment]) segment.parent = parentSegment;  parentSegment = segment; ];  retourner soi-même; 

Cette méthode est presque identique à notre précédente initWithGameLayer: niveau: position: méthode. La seule différence est que, plutôt que d'allouer un tableau de segments, il définit le tableau de segments sur les segments entrants transmis..

Allez-y et lancez le jeu à ce stade. Vous devriez être capable de tuer complètement la chenille en jeu..


Étape 3: Collision du joueur Caterpillar

La dernière chose dont nous avons besoin pour compléter le cercle de détection de collision de la vie est de mettre en œuvre les collisions entre la chenille et le joueur. Si une partie de la chenille frappe le joueur, nous voulons réduire le nombre de vies du joueur. En plus de cela, le joueur deviendra invincible pendant un bref moment, de sorte que la chenille ne flambe pas juste à travers lui.

Commencez par ouvrir GameConfig.h et en ajoutant l'option suivante:

 #define kPlayerInvincibleTime 15

Maintenant, ouvrez Caterpillar.m, importez Player.h et ajoutez le code suivant au bas de la mettre à jour: méthode juste avant de définir la position de la chenille:

 static int playerInvincibleCount = kPlayerInvincibleTime; statique BOOL playerHit; CGRect playerRect = [self.gameLayer.player getBounds]; // 1 [self.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Segment * segment = (Segment *) obj; CGRect segmentRect = [segment getBounds]; if (CGRectIntersectsRect (segmentRect, playerRect) && playerInvincibleCount == kPlayerInvincibleTime) * stop = YES; playerHit = YES; // 2 self.gameLayer.player.lives--; [[NSNotificationCenter defaultCenter] postNotificationName: kNotificationPlayerLives, objet: nil]; ]; // 3 if (playerHit) if (playerInvincibleCount> 0) playerInvincibleCount--;  else playerHit = NO; playerInvincibleCount = kPlayerInvincibleTime; 
  1. Énumérer chacun des segments pour déterminer s'ils entrent en collision avec le joueur..
  2. Si le joueur est touché, décrémentez sa vie et envoyez la notification. Ce devrait automatiquement reflété dans l'interface utilisateur en fonction du code que vous avez écrit dans la partie 3.
  3. Si le joueur a été touché, vérifiez s'il était toujours dans la période invincible. Si ce n'est pas le cas, réinitialisez le compteur invincible pour qu'ils puissent se faire frapper.

Maintenant, lancez le jeu et laissez la chenille vous frapper. Il devrait supprimer une de vos vies du haut de l'écran.


Conclusion

La détection de collision n'est jamais une tâche facile et nous n'avons fait qu'effleurer la surface. Si vous souhaitez approfondir la détection des collisions, jetez un coup d'œil à l'utilisation de Box2D avec Cocos2D..


La prochaine fois

Dans le prochain et dernier tutoriel de la série, nous discuterons du peaufinement du jeu. Cela inclura les conditions gagnantes, le score, les sons, le jeu fini et le meilleur score.

Code heureux!