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.
override func didMoveToView(view: SKView) { backgroundColor = SKColor.blackColor() rightBounds = self.size.width - 30 setupInvaders() setupPlayer() }
Ensuite, implémentez le moveInvaders
méthode sous le setupPlayer
méthode que vous avez créée dans le didacticiel précédent.
func moveInvaders(){ var changeDirection = false enumerateChildNodesWithName("invader") { node, stop in let invader = node as! SKSpriteNode let invaderHalfWidth = invader.size.width/2 invader.position.x -= CGFloat(self.invaderSpeed) if(invader.position.x > self.rightBounds - invaderHalfWidth || invader.position.x < self.leftBounds + invaderHalfWidth){ changeDirection = true } } if(changeDirection == true){ self.invaderSpeed *= -1 self.enumerateChildNodesWithName("invader") { node, stop in let invader = node as! SKSpriteNode invader.position.y -= CGFloat(46) } changeDirection = false } }
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 invaderHalfWidth
et 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.
override func update(currentTime: CFTimeInterval) { moveInvaders() }
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.
func fireBullet(scene: SKScene){ let bullet = InvaderBullet(imageName: "laser",bulletSound: nil) bullet.position.x = self.position.x bullet.position.y = self.position.y - self.size.height/2 scene.addChild(bullet) let moveBulletAction = SKAction.moveTo(CGPoint(x:self.position.x,y: 0 - bullet.size.height), duration: 2.0) let removeBulletAction = SKAction.removeFromParent() bullet.runAction(SKAction.sequence([moveBulletAction,removeBulletAction])) }
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.
func invokeInvaderFire(){ let fireBullet = SKAction.runBlock(){ self.fireInvaderBullet() } let waitToFireInvaderBullet = SKAction.waitForDuration(1.5) let invaderFire = SKAction.sequence([fireBullet,waitToFireInvaderBullet]) let repeatForeverAction = SKAction.repeatActionForever(invaderFire) runAction(repeatForeverAction) }
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.
func fireInvaderBullet(){ let randomInvader = invadersWhoCanFire.randomElement() randomInvader.fireBullet(self) }
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.
import Foundation extension Array { func randomElement() -> T { let index = Int(arc4random_uniform(UInt32(self.count))) return self[index] } }
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.
override func didMoveToView(view: SKView) { ... setupPlayer() invokeInvaderFire() }
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.
class Player: SKSpriteNode { private var canFire = true
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.
func fireBullet(scene: SKScene){ if(!canFire){ return }else{ canFire = false let bullet = PlayerBullet(imageName: "laser",bulletSound: "laser.mp3") bullet.position.x = self.position.x bullet.position.y = self.position.y + self.size.height/2 scene.addChild(bullet) let moveBulletAction = SKAction.moveTo(CGPoint(x:self.position.x,y:scene.size.height + bullet.size.height), duration: 1.0) let removeBulletAction = SKAction.removeFromParent() bullet.runAction(SKAction.sequence([moveBulletAction,removeBulletAction])) let waitToEnableFire = SKAction.waitForDuration(0.5) runAction(waitToEnableFire,completion:{ self.canFire = true }) } }
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 Invader
de 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.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) { player.fireBullet(self) }
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.
struct CollisionCategories{ static let Invader : UInt32 = 0x1 << 0 static let Player: UInt32 = 0x1 << 1 static let InvaderBullet: UInt32 = 0x1 << 2 static let PlayerBullet: UInt32 = 0x1 << 3 }
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.
override init(imageName: String, bulletSound:String?){ super.init(imageName: imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody(texture: self.texture, size: self.size) self.physicsBody?.dynamic = true self.physicsBody?.usesPreciseCollisionDetection = true self.physicsBody?.categoryBitMask = CollisionCategories.InvaderBullet self.physicsBody?.contactTestBitMask = CollisionCategories.Player self.physicsBody?.collisionBitMask = 0x0 }
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.
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.
override init() { let texture = SKTexture(imageNamed: "player1") super.init(texture: texture, color: SKColor.clearColor(), size: texture.size()) self.physicsBody = SKPhysicsBody(texture: self.texture,size:self.size) self.physicsBody?.dynamic = true self.physicsBody?.usesPreciseCollisionDetection = false self.physicsBody?.categoryBitMask = CollisionCategories.Player self.physicsBody?.contactTestBitMask = CollisionCategories.InvaderBullet | CollisionCategories.Invader self.physicsBody?.collisionBitMask = 0x0 animate() }
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.
override init() { let texture = SKTexture(imageNamed: "invader1") super.init(texture: texture, color: SKColor.clearColor(), size: texture.size()) self.name = "invader" self.physicsBody = SKPhysicsBody(texture: self.texture, size: self.size) self.physicsBody?.dynamic = true self.physicsBody?.usesPreciseCollisionDetection = false self.physicsBody?.categoryBitMask = CollisionCategories.Invader self.physicsBody?.contactTestBitMask = CollisionCategories.PlayerBullet | CollisionCategories.Player self.physicsBody?.collisionBitMask = 0x0 }
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.
override init(imageName: String, bulletSound:String?){ super.init(imageName: imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody(texture: self.texture, size: self.size) self.physicsBody?.dynamic = true self.physicsBody?.usesPreciseCollisionDetection = true self.physicsBody?.categoryBitMask = CollisionCategories.PlayerBullet self.physicsBody?.contactTestBitMask = CollisionCategories.Invader self.physicsBody?.collisionBitMask = 0x0 }
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.
class GameScene: SKScene, SKPhysicsContactDelegate{
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.
override func didMoveToView(view: SKView) { self.physicsWorld.gravity = CGVectorMake(0, 0) self.physicsWorld.contactDelegate = self ... }
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.
func didBeginContact(contact: SKPhysicsContact) { var firstBody: SKPhysicsBody var secondBody: SKPhysicsBody if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask { firstBody = contact.bodyA secondBody = contact.bodyB } else { firstBody = contact.bodyB secondBody = contact.bodyA } if ((firstBody.categoryBitMask & CollisionCategories.Invader != 0) && (secondBody.categoryBitMask & CollisionCategories.PlayerBullet != 0)){ NSLog("Invader and Player Bullet Conatact") } if ((firstBody.categoryBitMask & CollisionCategories.Player != 0) && (secondBody.categoryBitMask & CollisionCategories.InvaderBullet != 0)) { NSLog("Player and Invader Bullet Contact") } if ((firstBody.categoryBitMask & CollisionCategories.Invader != 0) && (secondBody.categoryBitMask & CollisionCategories.Player != 0)) { NSLog("Invader and Player Collision Contact") } }
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é.
Laisser un commentaire