Dans le tutoriel précédent, nous avons jeté les bases de notre jeu Missile Command en créant le projet, en configurant la scène solo et en ajoutant une interaction utilisateur. Dans ce didacticiel, vous allez élargir votre expérience de jeu en ajoutant un mode multi-joueurs ainsi que de la physique, des collisions et des explosions..
Jetez un coup d'œil à la capture d'écran suivante pour avoir une idée de ce que nous visons.
Si vous ne l’avez pas déjà fait, nous vous conseillons vivement de suivre le didacticiel précédent pour pouvoir vous appuyer sur les bases que nous avons posées dans le premier. Dans ce tutoriel, nous abordons un certain nombre de sujets, tels que la physique, les collisions, les explosions et l’ajout d’un mode multi-joueurs..
La structure du kit Sprite comprend un moteur physique qui simule des objets physiques. Le moteur physique du framework Sprite Kit fonctionne via le SKPhysicsContactDelegate
protocole. Pour activer le moteur physique dans notre jeu, nous devons modifier la Ma scène
classe. Commencez par mettre à jour le fichier d’en-tête comme indiqué ci-dessous pour indiquer au compilateur le SKScene
classe conforme à la SKPhysicsContactDelegate
protocole.
#importation@interface MyScene: SKScene @fin
le SKPhysicsContactDelegate
Le protocole nous permet de détecter si deux objets sont entrés en collision. le Ma scène
l'instance doit mettre en œuvre le SKPhysicsContactDelegate
protocole s'il veut être averti des collisions entre objets. Un objet implémentant le protocole est notifié chaque fois qu'une collision commence et se termine.
Puisque nous allons traiter avec des explosions, des missiles et des monstres, nous allons définir une catégorie pour chaque type d'objet physique. Ajoutez l'extrait de code suivant au fichier d'en-tête du fichier Ma scène
classe.
#importationtypedef enum: NSUInteger ExplosionCategory = (1 << 0), MissileCategory = (1 << 1), MonsterCategory = (1 << 2) NodeCategory; @interface MyScene : SKScene @fin
Avant de pouvoir explorer le moteur physique du framework Sprite Kit, nous devons définir le la gravité
propriété du monde de la physique, ainsi que son contactDélégué
. Mettre à jour le initWithSize:
méthode comme indiqué ci-dessous.
- (id) initWithSize: (CGSize) taille if (self = [super initWithSize: size]) self.backgroundColor = [SKColor couleurWithRed: (198.0 / 255.0) vert: (220.0 / 255.0) bleu: (54.0 / 255.0) alpha : 1,0]; //… // // Configurer Physics World self.physicsWorld.gravity = CGVectorMake (0, 0); self.physicsWorld.contactDelegate = self; retourner soi-même;
Dans notre jeu, le moteur physique est utilisé pour créer trois types de corps physique, des balles, des missiles et des monstres. Lorsque vous travaillez avec la structure Sprite Kit, vous utilisez des volumes dynamiques et statiques pour simuler des objets physiques. Un volume pour un groupe d'objets est un volume contenant chaque objet du groupe. Les volumes dynamiques et statiques sont un élément important pour améliorer les performances du moteur physique, en particulier lorsque vous travaillez avec des objets complexes. Dans notre jeu, nous définirons deux types de volumes, les cercles à rayon fixe et les objets personnalisés..
Bien que les cercles soient disponibles via le SKPhysicsBody
classe, objet personnalisé nécessite un peu de travail supplémentaire de notre part. Parce que le corps d'un monstre n'est pas circulaire, nous devons lui créer un volume personnalisé. Pour rendre cette tâche un peu plus facile, nous utiliserons un générateur de trajectoire physique. L'outil est simple à utiliser. Importez les images-objets de votre projet et définissez le chemin d'accès englobant chaque image-objet. Le code Objective-C permettant de recréer le chemin est affiché sous l’image-objet. Par exemple, regardez le sprite suivant.
La capture d'écran suivante montre le même sprite avec une superposition du chemin généré par le générateur de chemin physique.
Si un objet touche ou chevauche la frontière physique d'un objet, nous sommes informés de cet événement. Dans notre jeu, les objets qui peuvent toucher les monstres sont les missiles entrants. Commençons par utiliser les chemins générés pour les monstres.
Pour créer un corps de physique, nous devons utiliser un CGMutablePathRef
structure, qui représente un chemin mutable. Nous l'utilisons pour définir les contours des monstres du jeu..
Revisiter addMonstersBetweenSpace:
et créez un chemin mutable pour chaque type de monstre, comme indiqué ci-dessous. Rappelez-vous qu'il existe deux types de monstres dans notre jeu..
- (void) addMonstersBetweenSpace: (int) spaceOrder pour (int i = 0; i< 3; i++) int giveDistanceToMonsters = 60 * i -60; int randomMonster = [self getRandomNumberBetween:0 to:1]; SKSpriteNode *monster; CGMutablePathRef path = CGPathCreateMutable(); if (randomMonster == 0) monster = [SKSpriteNode spriteNodeWithImageNamed:@"protectCreature4"]; CGFloat offsetX = monster.frame.size.width * monster.anchorPoint.x; CGFloat offsetY = monster.frame.size.height * monster.anchorPoint.y; CGPathMoveToPoint(path, NULL, 10 - offsetX, 1 - offsetY); CGPathAddLineToPoint(path, NULL, 42 - offsetX, 0 - offsetY); CGPathAddLineToPoint(path, NULL, 49 - offsetX, 13 - offsetY); CGPathAddLineToPoint(path, NULL, 51 - offsetX, 29 - offsetY); CGPathAddLineToPoint(path, NULL, 50 - offsetX, 42 - offsetY); CGPathAddLineToPoint(path, NULL, 42 - offsetX, 59 - offsetY); CGPathAddLineToPoint(path, NULL, 29 - offsetX, 67 - offsetY); CGPathAddLineToPoint(path, NULL, 19 - offsetX, 67 - offsetY); CGPathAddLineToPoint(path, NULL, 5 - offsetX, 53 - offsetY); CGPathAddLineToPoint(path, NULL, 0 - offsetX, 34 - offsetY); CGPathAddLineToPoint(path, NULL, 1 - offsetX, 15 - offsetY); CGPathCloseSubpath(path); else monster = [SKSpriteNode spriteNodeWithImageNamed:@"protectCreature2"]; CGFloat offsetX = monster.frame.size.width * monster.anchorPoint.x; CGFloat offsetY = monster.frame.size.height * monster.anchorPoint.y; CGPathMoveToPoint(path, NULL, 0 - offsetX, 1 - offsetY); CGPathAddLineToPoint(path, NULL, 47 - offsetX, 1 - offsetY); CGPathAddLineToPoint(path, NULL, 47 - offsetX, 24 - offsetY); CGPathAddLineToPoint(path, NULL, 40 - offsetX, 43 - offsetY); CGPathAddLineToPoint(path, NULL, 28 - offsetX, 53 - offsetY); CGPathAddLineToPoint(path, NULL, 19 - offsetX, 53 - offsetY); CGPathAddLineToPoint(path, NULL, 8 - offsetX, 44 - offsetY); CGPathAddLineToPoint(path, NULL, 1 - offsetX, 26 - offsetY); CGPathCloseSubpath(path); monster.zPosition = 2; monster.position = CGPointMake(position * spaceOrder - giveDistanceToMonsters, monster.size.height / 2); [self addChild:monster];
Avec le chemin prêt à utiliser, nous devons mettre à jour le monstre corps physique
propriété ainsi qu'un certain nombre d'autres propriétés. Jetez un coup d'œil à l'extrait de code suivant pour plus de précisions..
- (void) addMonstersBetweenSpace: (int) spaceOrder pour (int i = 0; i< 3; i++) //… // monster.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:path]; monster.physicsBody.dynamic = YES; monster.physicsBody.categoryBitMask = MonsterCategory; monster.physicsBody.contactTestBitMask = MissileCategory; monster.physicsBody.collisionBitMask = 1; monster.zPosition = 2; monster.position = CGPointMake(position * spaceOrder - giveDistanceToMonsters, monster.size.height / 2); [self addChild:monster];
le catégorieBitMask
et contactTestBitMask
propriétés du corps physique
Les objets sont une partie essentielle et peuvent nécessiter une explication. le catégorieBitMask
propriété du corps physique
objet définit les catégories auxquelles appartient le nœud. le contactTestBitMask
Cette propriété définit les catégories de corps à l'origine des notifications d'intersection avec le nœud. En d’autres termes, ces propriétés définissent quels objets peuvent entrer en collision avec quels objets.
Parce que nous configurons les nœuds de monstres, nous définissons la catégorieBitMask
à MonsterCategory
et contactTestBitMask
à Catégorie missile
. Cela signifie que les monstres peuvent entrer en collision avec des missiles et cela nous permet de détecter quand un monstre est touché par un missile.
Nous devons également mettre à jour notre mise en œuvre de addMissilesFromSky:
. Définir le corps physique des missiles est beaucoup plus facile, car chaque missile est circulaire. Regardez l'implémentation mise à jour ci-dessous.
- (void) addMissilesFromSky: (CGSize) taille int numberMissiles = [self getRandomNumberBetween: 0 to: 3]; pour (int i = 0; i < numberMissiles; i++) SKSpriteNode *missile; missile = [SKSpriteNode spriteNodeWithImageNamed:@"enemyMissile"]; missile.scale = 0.6; missile.zPosition = 1; int startPoint = [self getRandomNumberBetween:0 to:size.width]; missile.position = CGPointMake(startPoint, size.height); missile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:missile.size.height/2]; missile.physicsBody.dynamic = NO; missile.physicsBody.categoryBitMask = MissileCategory; missile.physicsBody.contactTestBitMask = ExplosionCategory | MonsterCategory; missile.physicsBody.collisionBitMask = 1; int endPoint = [self getRandomNumberBetween:0 to:size.width]; SKAction *move =[SKAction moveTo:CGPointMake(endPoint, 0) duration:15]; SKAction *remove = [SKAction removeFromParent]; [missile runAction:[SKAction sequence:@[move,remove]]]; [self addChild:missile];
À ce stade, les monstres et les missiles de notre jeu devraient avoir un corps physique qui nous permettra de détecter toute collision entre eux..
Défi: Les défis pour cette section sont les suivants.
SKPhysicsBody
classe.Les collisions et les explosions sont deux éléments étroitement associés. Chaque fois qu'une balle tirée par une fleur atteint sa destination, au toucher de l'utilisateur, elle explose. Cette explosion peut provoquer une collision entre l'explosion et les missiles situés à proximité.
Pour créer l'explosion quand une balle atteint sa cible, nous avons besoin d'un autre SKAction
exemple. Cette SKAction
instance est en charge de deux aspects du jeu, définit les propriétés de l'explosion et le corps physique de l'explosion.
Pour définir une explosion, nous devons nous concentrer sur l'explosion SKSpriteNode
, ses zPosition
, échelle
, et position
. le position
est l'emplacement du contact de l'utilisateur.
Pour créer le corps physique de l'explosion, nous devons définir le noeud corps physique
propriété comme nous l'avons fait plus tôt. N'oubliez pas de régler correctement le catégorieBitMask
et contactTestBitMask
propriétés du corps physique. Nous créons l'explosion dans toucheBegan:
comme indiqué ci-dessous.
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) événement pour (UITouch * touches tactiles) //… // SKSpriteNode * bullet = [SKSpriteNode spriteNodeWithImageNamed: @ "flowerBullet"]; bullet.zPosition = 1; bullet.scale = 0.6; bullet.position = CGPointMake (bulletBeginning, 110); bullet.color = [SKColor redColor]; bullet.colorBlendFactor = 0,5; durée de flottement = (2 * location.y) /sizeGlobal.width; SKAction * move = [SKAction moveTo: CGPointMake (location.x, location.y) durée: durée]; SKAction * remove = [SKAction removeFromParent]; // Explosion SKAction * callExplosion = [SKAction runBlock: ^ SKSpriteNode * explosion = [SKSpriteNode> spriteNodeWithImageNamed: @ "explosion"]; explosion.zPosition = 3; échelle d'explosion = 0,1; explosion.position = CGPointMake (location.x, location.y); explosion.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: explosion.size.height / 2]; explosion.physicsBody.dynamic = YES; explosion.physicsBody.categoryBitMask = ExplosionCategory; explosion.physicsBody.contactTestBitMask = MissileCategory; explosion.physicsBody.collisionBitMask = 1; SKAction * explosionAction = [SKAction scaleTo: 0.8 duration: 1.5]; [explosion runAction: [séquence SKAction: @ [explosionAction, remove]]]; [auto addChild: explosion]; ]; [bullet runAction: [séquence SKAction: @ [move, callExplosion, remove]]]; [auto addChild: bullet];
Dans toucheBegan:
, nous avons mis à jour le balle
l'action de. La nouvelle action doit appeler le callExplosion
action avant qu'il ne soit retiré de la scène. Pour ce faire, nous avons mis à jour la ligne de code suivante dans toucheBegan:
.
[bullet runAction: [séquence SKAction: @ [déplacer, supprimer]]];
La séquence de l'action contient maintenant callExplosion
comme indiqué ci-dessous.
[bullet runAction: [séquence SKAction: @ [move, callExplosion, remove]]];
Générez le projet et exécutez l'application pour voir le résultat de notre travail. Comme vous pouvez le constater, nous devons encore détecter les collisions entre les explosions et les missiles à venir. C'est là que le SKPhysicsContactDelegate
le protocole entre en jeu.
Il y a une méthode de délégué qui nous intéresse particulièrement, la didBeginContact:
méthode. Cette méthode nous dira quand une collision entre une explosion et un missile a lieu. le didBeginContact:
méthode prend un argument, une instance de la SKPhysicsContact
classe, qui nous dit tout ce que nous devons savoir sur la collision. Laissez-moi vous expliquer comment cela fonctionne.
Un SKPhysicsContact
l'instance a un corpsA
et un bodyB
propriété. Chaque corps pointe vers un corps physique impliqué dans la collision. Quand didBeginContact:
est appelé, nous devons détecter le type de collision auquel nous faisons face. Cela peut être (1) une collision entre une explosion et un missile ou (2) une collision entre un missile et un monstre. Nous détectons le type de collision en inspectant le catégorieBitmask
propriété des corps physiques de la SKPhysicsContact
exemple.
Trouver le type de collision auquel nous sommes confrontés est assez facile grâce à la catégorieBitmask
propriété. Si corpsA
ou bodyB
a un catégorieBitmask
de type Catégorie d'explosion
, alors nous savons que c'est une collision entre une explosion et un missile. Consultez l'extrait de code ci-dessous pour plus de précisions..
- (void) didBeginContact: (SKPhysicsContact *) contact if ((contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) NSLog (@ "EXPLOSION HIT"); else NSLog (@ "MONSTER HIT");
Si nous rencontrons une collision entre une explosion et un missile, nous saisissons le nœud associé au corps physique du missile. Nous devons également affecter une action au nœud, qui sera exécutée lorsque la balle touchera le missile. La tâche de l'action consiste à retirer le missile de la scène. Notez que nous ne retirons pas immédiatement l'explosion de la scène car elle pourrait détruire d'autres missiles à proximité..
Lorsqu'un missile est détruit, nous incrémentons la missile Explodé
variable d'instance et mettez à jour l'étiquette indiquant le nombre de missiles que le joueur a détruits jusqu'à présent. Si le joueur a détruit vingt missiles, il remporte la partie..
- (void) didBeginContact: (SKPhysicsContact *) contact if ((contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) // Collision entre Explosion et Missile SKNode * missile = (contact.bodyA.categoryBitMask & ExplosionCategory)? contact.bodyB.node: contact.bodyA.node; [missile runAction: [SKAction removeFromParent]]; // l'explosion continue, car peut tuer plus d'un missile NSLog (@ "Missile détruit"); // Update Missile Exploded missileExploded ++; [labelMissilesExploded setText: [NSString stringWithFormat: @ "Missiles Exploded:% d", missileExploded]]; if (missileExploded == 20) SKLabelNode * ganhou = [SKLabelNode labelNodeWithFontNamed: @ "Hiragino-Kaku-Gothic-ProN"]; ganhou.text = @ "Vous gagnez!"; ganhou.fontSize = 60; ganhou.position = CGPointMake (sizeGlobal.width / 2, sizeGlobal.height / 2); ganhou.zPosition = 3; [auto addChild: ganhou]; else // Collision entre Missile et Monster
S'il s'agit d'une collision entre un missile et un monstre, nous supprimons le nœud de missile et le monstre de la scène en ajoutant une action. [SKAction removeFromParent]
à la liste des actions exécutées par le noeud. Nous incrémentons également le monstres morts
variable d'instance et vérifiez si elle est égale à 6
. Si c'est le cas, le joueur a perdu la partie et nous affichons un message l'informant que la partie est terminée..
- (void) didBeginContact: (SKPhysicsContact *) contact if ((contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) // Collision entre Explosion et Missile // … // else // Collision entre missile et monstre SKNode * monster = (contact.bodyA.categoryBitMask & MonsterCategory)? contact.bodyA.node: contact.bodyB.node; SKNode * missile = (contact.bodyA.categoryBitMask & MonsterCategory)? contact.bodyB.node: contact.bodyA.node; [missile runAction: [SKAction removeFromParent]]; [monster runAction: [SKAction removeFromParent]]; NSLog (@ "Monstre tué"); monstersDead ++; if (monstersDead == 6) SKLabelNode * perdeu = [SKLabelNode labelNodeWithFontNamed: @ "Hiragino-Kaku-Gothic-ProN"]; perdeu.text = @ "Vous perdez!"; perdeu.fontSize = 60; perdeu.position = CGPointMake (sizeGlobal.width / 2, sizeGlobal.height / 2); perdeu.zPosition = 3; [auto addChild: perdeu]; [auto moveToMenu];
Avant de lancer le jeu sur votre iPad, nous devons implémenter la moveToMenu
méthode. Cette méthode est invoquée lorsque le joueur perd la partie. Dans moveToMenu
, le jeu revient à la scène du menu afin que le joueur puisse commencer une nouvelle partie. N'oubliez pas d'ajouter une déclaration d'importation pour le MenuScene
classe.
- (void) moveToMenu SKTransition * transition = [SKTransition fadeWithDuration: 2]; MenuScene * myscene = [[MenuScene alloc] initWithSize: CGSizeMake (CGRectGetMaxX (self.frame), CGRectGetMaxY (self.frame)))]; [self.scene.view presentScene: transition myscene: transition];
#import "MyScene.h" #import "MenuScene.h" @interface MyScene () //… // @end
Il est temps de construire le projet et de lancer le jeu pour voir le résultat final.
Défi: Les défis pour cette section sont les suivants.
Dans le mode multijoueur du jeu, deux joueurs peuvent s'affronter via un mode d'écran partagé. Le mode multi-joueurs ne change pas le jeu lui-même. Les principales différences entre les modes solo et multijoueur sont énumérées ci-dessous..
En mode multi-joueurs, le jeu devrait ressembler à la capture d'écran ci-dessous.
C'est le dernier défi de ce tutoriel. Ce n'est pas aussi compliqué que cela puisse paraître. Le défi consiste à recréer Missile Command en activant le mode multi-joueurs. Les fichiers source de ce didacticiel contiennent deux projets Xcode, dont l’un (Missile Command Multi-Player) contient une implémentation incomplète du mode multijoueur pour vous aider à commencer ce défi. Notez que le MultiScene
La classe est incomplète et il est de votre devoir de terminer sa mise en œuvre pour réussir le défi. Vous trouverez des astuces et des commentaires (/ * Travailler ICI - CODE MANQUANT * /
) pour vous aider à relever ce défi.
La capture d'écran suivante vous montre l'état actuel du mode multi-joueurs.
Nous avons couvert beaucoup de terrain dans cette courte série sur Sprite Kit. Vous devriez maintenant pouvoir créer des jeux similaires à Missile Command en utilisant le framework Sprite Kit. Si vous avez des questions ou des commentaires, n'hésitez pas à nous laisser un mot dans les commentaires..