Créer des Space Invaders avec Swift et Sprite Kit: implémentation du gameplay

Dans la partie précédente de cette série, nous avons implémenté les stubs pour les classes principales du jeu. Dans ce didacticiel, nous allons faire bouger les envahisseurs, tirer des balles pour les envahisseurs et le joueur, et mettre en œuvre la détection de collision. Commençons.

1. Déplacer les envahisseurs

Nous utiliserons la scène update méthode pour déplacer les envahisseurs. Chaque fois que vous souhaitez déplacer quelque chose manuellement, le update est généralement l’endroit où vous souhaitez faire cela.

Avant de faire cela, nous devons mettre à jour le rightBounds propriété. Il était initialement fixé à , parce que nous devons utiliser la scène size pour définir la variable. Nous n’avons pas pu faire cela en dehors des méthodes de la classe, nous mettrons donc à jour cette propriété dans le didMoveToView(_:) méthode.

Ensuite, implémentez le moveInvaders méthode sous le setupPlayer méthode que vous avez créée dans le didacticiel précédent.

Nous déclarons une variable, changeDirection, pour suivre le moment où les envahisseurs doivent changer de direction, se déplacer à gauche ou à droite. Nous utilisons ensuite le enumerateChildNodesWithName(usingBlock:) méthode, qui recherche les enfants d’un nœud et appelle la fermeture une fois pour chaque nœud correspondant qu’il trouve avec le nom correspondant “envahisseur”. La fermeture accepte deux paramètres, node est le nœud qui correspond au name et stop est un pointeur vers une variable booléenne pour terminer l’énumération. Nous n’utiliserons pas stop ici, mais il est bon de savoir à quoi il sert.

Nous jetons node à un SKSpriteNode instance qui invader est une sous-classe de, obtient la moitié de sa largeur invaderHalfWidthet mettez à jour sa position. Nous vérifions ensuite si son position est dans les limites, leftBounds et rightBounds, et sinon, nous définissons changeDirection à true.

Si changeDirection est true, nous nions invaderSpeed, ce qui changera la direction dans laquelle l’envahisseur se déplace. Nous énumérons ensuite les envahisseurs et mettons à jour leur position y. Enfin, nous définissons changeDirection retour à false.

le moveInvaders est appelée dans le update(_:) méthode.

Si vous testez l’application maintenant, vous devriez voir les envahisseurs se déplacer vers la gauche, la droite, puis vers le bas s’ils atteignent les limites que nous avons fixées de chaque côté.

2. Tirer des balles d’envahisseur

Étape 1: fireBullet

De temps en temps, nous voulons que l’un des envahisseurs tire une balle. Dans l’état actuel des choses, les envahisseurs de la rangée du bas sont prêts à tirer une balle, car ils sont dans le invadersWhoCanFire tableau.

Lorsqu’un envahisseur est touché par une balle de joueur, l’envahisseur une ligne plus haut et dans la même colonne sera ajouté au invadersWhoCanFire tableau, tandis que l’envahisseur qui a été touché sera supprimé. De cette façon, seul l’envahisseur le plus bas de chaque colonne peut tirer des balles.

Ajouter le fireBullet méthode à la InvaderBullet classe dans InvaderBullet.swift.

dans le fireBullet méthode, nous instancions un InvaderBullet instance, en passant “laser” pour imageName, et parce qu’on ne veut pas qu’un son joue on passe nil pour bulletSound. Nous définissons son position être le même que celui de l’envahisseur, avec un léger décalage sur la position y, et l’ajouter à la scène.

Nous créons deux SKAction instances, moveBulletAction et removeBulletAction. le moveBulletAction action déplace la puce à un certain point sur une certaine durée tandis que le removeBulletAction l’action le supprime de la scène. En invoquant le sequence(_:) méthode sur ces actions, elles s’exécuteront de manière séquentielle. C’est pourquoi j’ai mentionné le waitForDuration méthode lors de la lecture d’un son dans la partie précédente de cette série. Si vous créez un SKAction objet en invoquant playSoundFileNamed(_:waitForCompletion:) Et mettre waitForCompletion à true, alors la durée de cette action serait aussi longtemps que le son est joué, sinon elle passerait immédiatement à l’action suivante de la séquence.

Étape 2: invokeInvaderFire

Ajouter le invokeInvaderFire méthode ci-dessous les autres méthodes que vous avez créées dans GameScence.swift.

le runBlock(_:) méthode de la SKAction classe crée un SKAction instance et appelle immédiatement la fermeture transmise au runBlock(_:) méthode. Dans la clôture, nous invoquons le fireInvaderBullet méthode. Parce que nous invoquons cette méthode dans une fermeture, nous devons utiliser self pour l’appeler.

Nous créons ensuite un SKAction instance nommée waitToFireInvaderBullet en invoquant waitForDuration(_:), en passant le nombre de secondes à attendre avant de continuer. Ensuite, nous créons un SKAction exemple, invaderFire, en invoquant le sequence(_:) méthode. Cette méthode accepte une collection d’actions qui sont appelées par le invaderFire action. Nous voulons que cette séquence se répète pour toujours, nous créons une action nommée repeatForeverAction, passez dans le SKAction objets à répéter et invoquer runAction, passant dans le repeatForeverAction action. La méthode runAction est déclarée dans le SKNode classe.

Étape 3: fireInvaderBullet

Ajouter le fireInvaderBullet méthode sous le invokeInvaderFire méthode que vous avez entrée à l’étape précédente.

En relation :  Google appelle le Pixel 5 Body Gap issue de la conception du téléphone

Dans cette méthode, nous appelons ce qui semble être une méthode nommée randomElement qui renverrait un élément aléatoire hors du invadersWhoCanFire array, puis appelez son fireBullet méthode. Il n’y a malheureusement pas de randomElement méthode sur le Array structure. Cependant, nous pouvons créer un Array extension pour fournir cette fonctionnalité.

Étape 4: mise en œuvre randomElement

Aller à Fichier > Nouveau > Fichier… et choisissez Fichier Swift. Nous faisons quelque chose de différent qu’avant, alors assurez-vous de choisir Fichier Swift et pas Classe Touch Cocoa. presse Prochain et nommez le fichier Utilitaires. Ajoutez ce qui suit à Utilities.swift.

Nous étendons le Array structure pour avoir une méthode nommée randomElement. le arc4random_uniform La fonction renvoie un nombre entre 0 et tout ce que vous transmettez. Puisque Swift ne convertit pas implicitement les types numériques, nous devons effectuer la conversion nous-mêmes. Enfin, nous retournons l’élément du tableau à l’index index.

Cet exemple illustre à quel point il est facile d’ajouter des fonctionnalités à la structure et aux classes. Vous pouvez en savoir plus sur la création d’extensions dans le Le langage de programmation Swift.

Étape 5: Tirer la balle

Avec tout cela à l’écart, nous pouvons maintenant tirer les balles. Ajoutez ce qui suit au didMoveToView(_:) méthode.

Si vous testez l’application maintenant, toutes les secondes environ, vous devriez voir l’un des envahisseurs de la rangée du bas tirer une balle.

3. Tirer des balles de joueur

Étape 1: fireBullet(scene:)

Ajoutez la propriété suivante au Player classe dans Player.swift.

Nous voulons limiter la fréquence à laquelle le joueur peut tirer une balle. le canFire la propriété sera utilisée pour réglementer cela. Ensuite, ajoutez ce qui suit au fireBullet(scene:) méthode dans le Player classe.

Nous nous assurons d’abord que le joueur est capable de tirer en vérifiant si canFire est réglé sur true. Si ce n’est pas le cas, nous revenons immédiatement de la méthode.

Si le joueur peut tirer, nous mettons canFire à false ils ne peuvent donc pas immédiatement tirer une autre balle. Nous instancions ensuite un PlayerBullet instance, en passant “laser” pour le imageNamed paramètre. Parce qu’on veut qu’un son joue quand le joueur tire une balle, on passe “laser.mp3” pour le bulletSound paramètre.

Nous définissons ensuite la position de la balle et l’ajoutons à l’écran. Les quelques lignes suivantes sont les mêmes que les Invaderde fireBullet méthode en ce que nous déplaçons la balle et la supprimons de la scène. Ensuite, nous créons un SKAction exemple, waitToEnableFire, en invoquant le waitForDuration(_:) méthode de classe. Enfin, nous invoquons runAction, en passant waitToEnableFire, et à la fin du jeu canFire retour à true.

Étape 2: tirer la balle du joueur

Chaque fois que l’utilisateur touche l’écran, nous voulons tirer une balle. C’est aussi simple que d’appeler fireBullet sur le player objet dans le touchesBegan(_:withEvent:) méthode de la GameScene classe.

Si vous testez l’application maintenant, vous devriez pouvoir tirer une balle lorsque vous appuyez sur l’écran. De plus, vous devriez entendre le son du laser chaque fois qu’une balle est tirée.

4. Catégories de collision

Pour détecter lorsque des nœuds entrent en collision ou se contactent, nous utiliserons le moteur physique intégré de Sprite Kit. Cependant, le comportement par défaut du moteur physique est que tout entre en collision avec tout quand un corps physique leur est ajouté. Nous avons besoin d’un moyen de séparer ce que nous voulons interagir les uns avec les autres et nous pouvons le faire en créant des catégories auxquelles appartiennent des corps physiques spécifiques.

Vous définissez ces catégories à l’aide d’un masque de bits qui utilise un entier 32 bits avec 32 indicateurs individuels qui peuvent être activés ou désactivés. Cela signifie également que vous ne pouvez avoir qu’un maximum de 32 catégories pour votre jeu. Cela ne devrait pas poser de problème pour la plupart des jeux, mais c’est quelque chose à garder à l’esprit.

Ajoutez la définition de structure suivante au GameScene classe, sous le invaderNum déclaration en GameScene.swift.

Nous utilisons une structure, CollsionCategories, pour créer des catégories pour Invader, Player, InvaderBullet, et PlayerBullet Des classes. Nous utilisons le décalage de bits pour activer les bits.

5. Player et InvaderBullet Collision

Étape 1: configuration InvaderBullet pour Collision

Ajoutez le bloc de code suivant au init(imageName:bulletSound:) méthode dans InvaderBullet.swift.

Il existe plusieurs façons de créer un corps physique. Dans cet exemple, nous utilisons le init(texture:size:) initialiseur, qui fera en sorte que la détection de collision utilise la forme de la texture que nous transmettons. Il existe plusieurs autres initialiseurs disponibles, que vous pouvez voir dans le Référence de classe SKPhysicsBody.

Nous aurions pu facilement utiliser le init(rectangleOfSize:) initialiseur, car les puces sont de forme rectangulaire. Dans un jeu aussi petit, peu importe. Cependant, sachez que l’utilisation du init(texture:size:) La méthode peut être coûteuse en calcul car elle doit calculer la forme exacte de la texture. Si vous avez des objets de forme rectangulaire ou circulaire, vous devez utiliser ces types d’initialiseurs si les performances du jeu deviennent un problème.

En relation :  Comment améliorer la concentration avec un cocktail audio

Pour que la détection de collision fonctionne, au moins un des corps que vous testez doit être marqué comme dynamique. En réglant le usesPreciseCollisionDetection propriété à true, Sprite Kit utilise une détection de collision plus précise. Définissez cette propriété sur true sur de petits corps en mouvement rapide comme nos balles.

Chaque corps appartiendra à une catégorie et vous la définissez en définissant son categoryBitMask. Puisque c’est le InvaderBullet classe, nous le réglons sur CollisionCategories.InvaderBullet.

Pour savoir quand ce corps est entré en contact avec un autre corps qui vous intéresse, vous définissez le contactBitMask. Ici, nous voulons savoir quand le InvaderBullet a pris contact avec le joueur, nous utilisons CollisionCategories.Player. Parce qu’une collision ne devrait déclencher aucune force physique, nous définissons collisionBitMask à 0x0.

Étape 2: configuration Player pour Collsion

Ajoutez ce qui suit au init méthode dans Player.swift.

Une grande partie de cela devrait être familière à l’étape précédente, je ne vais donc pas la répéter ici. Il y a cependant deux différences à remarquer. L’un est que usesPreciseCollsionDetection a été réglé sur false, qui est la valeur par défaut. Il est important de réaliser que seul l’un des organismes en contact a besoin que cette propriété soit définie sur true (qui était la balle). L’autre différence est que nous voulons également savoir quand le joueur contacte un envahisseur. Vous pouvez en avoir plus d’un contactBitMask catégorie en les séparant par le bit ou (|) opérateur. En dehors de cela, vous devriez remarquer que c’est juste à l’opposé du InvaderBullet.

6. Invader et PlayerBullet Collision

Étape 1: configuration Invader pour Collision

Ajoutez ce qui suit au init méthode dans Invader.swift.

Tout cela devrait avoir du sens si vous avez suivi. Nous avons mis en place le physicsBody, categoryBitMask, et contactBitMask.

Étape 2: configuration PlayerBullet pour Collision

Ajoutez ce qui suit au init(imageName:bulletSound:) dans PlayerBullet.swift. Encore une fois, la mise en œuvre devrait être familière maintenant.

7. Configuration de la physique pour GameScene

Étape 1: Configurer Physics World

Nous devons mettre en place le GameScene classe pour implémenter le SKPhysicsContactDelegate afin que nous puissions réagir lorsque deux corps entrent en collision. Ajoutez ce qui suit pour que le GameScene classe conforme à la SKPhysicsContactDelegate protocole.

Ensuite, nous devons configurer certaines propriétés sur la scène physicsWorld. Entrez ce qui suit en haut de la didMoveToView(_:) méthode dans GameScene.swift.

Nous définissons le gravity propriété de physicsWorld à de sorte qu’aucun des corps physiques de la scène ne soit affecté par la gravité. Vous pouvez également le faire corps par corps au lieu de faire en sorte que le monde entier n’ait aucune gravité en réglant le affectedByGravity propriété. Nous définissons également le contactDelegate propriété du monde de la physique self, la GameScene exemple.

Étape 2: mise en œuvre SKPhysicsContactDelegate Protocole

Pour conformer le GameScene classe à SKPhysicsContactDelegate protocole, nous devons implémenter le didBeginContact(_:) méthode. Cette méthode est appelée lorsque deux corps entrent en contact. La mise en œuvre du didBeginContact(_:) La méthode ressemble à ceci.

Nous déclarons d’abord deux variables firstBody et secondBody. Lorsque deux objets entrent en contact, nous ne savons pas quel corps est lequel. Cela signifie que nous devons d’abord effectuer des vérifications pour nous assurer firstBody est celui avec le plus bas categoryBitMask.

Ensuite, nous passons en revue chaque scénario possible en utilisant le bitwise & opérateur et les catégories de collision que nous avons définies précédemment pour vérifier ce qui établit le contact. Nous enregistrons le résultat sur la console pour nous assurer que tout fonctionne comme il se doit. Si vous testez l’application, tous les contacts devraient fonctionner correctement.

Conclusion

C’était un didacticiel assez long, mais nous avons maintenant les envahisseurs en mouvement, les balles tirées à la fois par le joueur et les envahisseurs et la détection de contact fonctionnant à l’aide de masques de bits de contact. Nous sommes sur la dernière ligne droite pour le dernier match. Dans la prochaine et dernière partie de cette série, nous aurons un jeu terminé.

Moyens Staff
Moyens I/O Staff vous a motivé, donner des conseils sur la technologie, le développement personnel, le style de vie et des stratégies qui vous aider.