sprite-kit
SKNode Collision
Recherche…
Remarques
Les déterminants de la collision Sprite Kit et du traitement des événements de contact sont les paramètres de relation que vous avez créés, categoryBitMask
, collisionBitMask
et contactTestBitMask
pour chacun de vos types d'objet en interaction. En les définissant rationnellement en fonction des résultats escomptés des contacts et des collisions, vous déterminez quels types peuvent entrer en collision et informer des contacts avec d’autres, et éviter les collisions, les contacts et le traitement physique indésirables.
Pour chaque type d'entité, vous pouvez définir tous les trois:
-
categoryBitMask
: une catégorie spécifique à ce type de noeud -
collisionBitMask
: un différenciateur de collision, peut être différent du précédent -
contactTestBitMask
: un différenciateur de contact, peut être différent des deuxcontactTestBitMask
Les étapes générales pour implémenter des collisions et des contacts sont les suivantes:
- définir la taille physique du corps, la forme et (parfois) la masse
- ajouter les BitMasks nécessaires pour votre type de noeud à partir de la catégorie, de la collision et du contact ci-dessus
- mettre en scène en tant que délégué de contact lui permettant de vérifier et d'informer des collisions et des contacts
- mettre en œuvre des gestionnaires de contacts et toute autre logique pertinente pour les événements de physique
Activer le monde de la physique
// World physics
self.physicsWorld.gravity = CGVectorMake(0, -9.8);
Activer le nœud pour entrer en collision
Tout d'abord, nous définissons la catégorie de nœud
let groundBody: UInt32 = 0x1 << 0
let boxBody: UInt32 = 0x1 << 1
Ajoutez ensuite le nœud du type de sol et le nœud du type de boîte.
let ground = SKSpriteNode(color: UIColor.cyanColor(), size: CGSizeMake(self.frame.width, 50))
ground.position = CGPointMake(CGRectGetMidX(self.frame), 100)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.size)
ground.physicsBody?.dynamic = false
ground.physicsBody?.categoryBitMask = groundBody
ground.physicsBody?.collisionBitMask = boxBody
ground.physicsBody?.contactTestBitMask = boxBody
addChild(ground)
// Add box type node
let box = SKSpriteNode(color: UIColor.yellowColor(), size: CGSizeMake(20, 20))
box.position = location
box.physicsBody = SKPhysicsBody(rectangleOfSize: box.size)
box.physicsBody?.dynamic = true
box.physicsBody?.categoryBitMask = boxBody
box.physicsBody?.collisionBitMask = groundBody | boxBody
box.physicsBody?.contactTestBitMask = boxBody
box.name = boxId
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
box.runAction(SKAction.repeatActionForever(action))
self.addChild(box)
Gérer les contacts
Définir la scène en tant que délégué
//set your scene as SKPhysicsContactDelegate
class yourScene: SKScene, SKPhysicsContactDelegate
self.physicsWorld.contactDelegate = self;
Ensuite, vous devez implémenter l'une ou l'autre des fonctions de contact: option func didBegin (contact :) et / ou facultatif fund didEnd (contact :) méthode pour remplir votre logique de contact, par exemple comme
//order
let bodies = (contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask) ? (A:contact.bodyA,B:contact.bodyB) : (A:contact.bodyB,B:contact.bodyA)
//real handler
if ((bodies.B.categoryBitMask & boxBody) == boxBody){
if ((bodies.A.categoryBitMask & groundBody) == groundBody) {
let vector = bodies.B.velocity
bodies.B.velocity = CGVectorMake(vector.dx, vector.dy * 4)
}else{
let vector = bodies.A.velocity
bodies.A.velocity = CGVectorMake(vector.dx, vector.dy * 10)
}
}
Alternative aBeginContact
Si vous utilisez des catégories simples, avec chaque corps physique appartenant à une seule catégorie, alors cette forme alternative de didBeginContact peut être plus lisible:
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case categoryBitMask.player | categoryBitMask.enemy:
print("Collision between player and enemy")
let enemyNode = contact.bodyA.categoryBitMask == categoryBitMask.enemy ? contact.bodyA.node! : contact.bodyB.node!
enemyNode.explode()
score += 10
case categoryBitMask.enemy | categoryBitMask.enemy:
print("Collision between enemy and enemy")
contact.bodyA.node.explode()
contact.bodyB.node.explode()
default :
//Some other contact has occurred
print("Some other contact")
}
}
Projet Simple Sprite Kit montrant des collisions, des contacts et des événements tactiles.
Voici un simple Sprite-Kit GameScene.swift. Créez un nouveau projet SpriteKit vide et remplacez-le par GameScene.swift. Ensuite, construisez et exécutez.
Cliquez sur l'un des objets à l'écran pour les faire bouger. Vérifiez les journaux et les commentaires pour voir ceux qui entrent en collision et ceux qui entrent en contact.
//
// GameScene.swift
// bounceTest
//
// Created by Stephen Ives on 05/04/2016.
// Copyright (c) 2016 Stephen Ives. All rights reserved.
//
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let objectSize = 150
let initialImpulse: UInt32 = 300 // Needs to be proportional to objectSize
//Physics categories
let purpleSquareCategory: UInt32 = 1 << 0
let redCircleCategory: UInt32 = 1 << 1
let blueSquareCategory: UInt32 = 1 << 2
let edgeCategory: UInt32 = 1 << 31
let purpleSquare = SKSpriteNode()
let blueSquare = SKSpriteNode()
let redCircle = SKSpriteNode()
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
//Create an boundary else everything will fly off-screen
let edge = frame.insetBy(dx: 0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: edge)
physicsBody?.isDynamic = false //This won't move
name = "Screen_edge"
scene?.backgroundColor = SKColor.black
// Give our 3 objects their attributes
blueSquare.color = SKColor.blue
blueSquare.size = CGSize(width: objectSize, height: objectSize)
blueSquare.name = "shape_blueSquare"
blueSquare.position = CGPoint(x: size.width * -0.25, y: size.height * 0.2)
let circleShape = SKShapeNode(circleOfRadius: CGFloat(objectSize))
circleShape.fillColor = SKColor.red
redCircle.texture = view.texture(from: circleShape)
redCircle.size = CGSize(width: objectSize, height: objectSize)
redCircle.name = "shape_redCircle"
redCircle.position = CGPoint(x: size.width * 0.4, y: size.height * -0.4)
purpleSquare.color = SKColor.purple
purpleSquare.size = CGSize(width: objectSize, height: objectSize)
purpleSquare.name = "shape_purpleSquare"
purpleSquare.position = CGPoint(x: size.width * -0.35, y: size.height * 0.4)
addChild(blueSquare)
addChild(redCircle)
addChild(purpleSquare)
redCircle.physicsBody = SKPhysicsBody(circleOfRadius: redCircle.size.width/2)
blueSquare.physicsBody = SKPhysicsBody(rectangleOf: blueSquare.frame.size)
purpleSquare.physicsBody = SKPhysicsBody(rectangleOf: purpleSquare.frame.size)
setUpCollisions()
checkPhysics()
}
func setUpCollisions() {
//Assign our category bit masks to our physics bodies
purpleSquare.physicsBody?.categoryBitMask = purpleSquareCategory
redCircle.physicsBody?.categoryBitMask = redCircleCategory
blueSquare.physicsBody?.categoryBitMask = blueSquareCategory
physicsBody?.categoryBitMask = edgeCategory // This is the edge for the scene itself
// Set up the collisions. By default, everything collides with everything.
redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory // Circle doesn't collide with purple square
purpleSquare.physicsBody?.collisionBitMask = 0 // purpleSquare collides with nothing
// purpleSquare.physicsBody?.collisionBitMask |= (redCircleCategory | blueSquareCategory) // Add collisions with red circle and blue square
purpleSquare.physicsBody?.collisionBitMask = (redCircleCategory) // Add collisions with red circle
blueSquare.physicsBody?.collisionBitMask = (redCircleCategory) // Add collisions with red circle
// Set up the contact notifications. By default, nothing contacts anything.
redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory // Notify when red circle and purple square contact
blueSquare.physicsBody?.contactTestBitMask |= redCircleCategory // Notify when blue square and red circle contact
// Make sure everything collides with the screen edge and make everything really 'bouncy'
enumerateChildNodes(withName: "//shape*") { node, _ in
node.physicsBody?.collisionBitMask |= self.edgeCategory //Add edgeCategory to the collision bit mask
node.physicsBody?.restitution = 0.9 // Nice and bouncy...
node.physicsBody?.linearDamping = 0.1 // Nice and bouncy...
}
//Lastly, set ourselves as the contact delegate
physicsWorld.contactDelegate = self
}
func didBegin(_ contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case purpleSquareCategory | blueSquareCategory:
print("Purple square and Blue square have touched")
case redCircleCategory | blueSquareCategory:
print("Red circle and Blue square have touched")
case redCircleCategory | purpleSquareCategory:
print("Red circle and purple Square have touched")
default: print("Unknown contact detected")
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let touchedNode = selectNodeForTouch(touch.location(in: self))
if let node = touchedNode {
node.physicsBody?.applyImpulse(CGVector(dx: CGFloat(arc4random_uniform(initialImpulse)) - CGFloat(initialImpulse/2), dy: CGFloat(arc4random_uniform(initialImpulse)) - CGFloat(initialImpulse/2)))
node.physicsBody?.applyTorque(CGFloat(arc4random_uniform(20)) - CGFloat(10))
}
}
}
// Return the sprite where the user touched the screen
func selectNodeForTouch(_ touchLocation: CGPoint) -> SKSpriteNode? {
let touchedNode = self.atPoint(touchLocation)
print("Touched node is \(touchedNode.name)")
// let touchedColor = getPixelColorAtPoint(touchLocation)
// print("Touched colour is \(touchedColor)")
if touchedNode is SKSpriteNode {
return (touchedNode as! SKSpriteNode)
} else {
return nil
}
}
//MARK: - Analyse the collision/contact set up.
func checkPhysics() {
// Create an array of all the nodes with physicsBodies
var physicsNodes = [SKNode]()
//Get all physics bodies
enumerateChildNodes(withName: "//.") { node, _ in
if let _ = node.physicsBody {
physicsNodes.append(node)
} else {
print("\(node.name) does not have a physics body so cannot collide or be involved in contacts.")
}
}
//For each node, check it's category against every other node's collion and contctTest bit mask
for node in physicsNodes {
let category = node.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let name = node.name != nil ? node.name! : "Category \(category)"
let collisionMask = node.physicsBody!.collisionBitMask
let contactMask = node.physicsBody!.contactTestBitMask
// If all bits of the collisonmask set, just say it collides with everything.
if collisionMask == UInt32.max {
print("\(name) collides with everything")
}
for otherNode in physicsNodes {
if (node.physicsBody?.dynamic == false) {
print("This node \(name) is not dynamic")
}
if (node != otherNode) && (node.physicsBody?.isDynamic == true) {
let otherCategory = otherNode.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let otherName = otherNode.name != nil ? otherNode.name! : "Category \(otherCategory)"
// If the collisonmask and category match, they will collide
if ((collisionMask & otherCategory) != 0) && (collisionMask != UInt32.max) {
print("\(name) collides with \(otherName)")
}
// If the contactMAsk and category match, they will contact
if (contactMask & otherCategory) != 0 {print("\(name) notifies when contacting \(otherName)")}
}
}
}
}
}
Alternative au traitement des contacts avec les sprites multi-catégories
let bodies = (contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask) ? (A:contact.bodyA,B:contact.bodyB) : (A:contact.bodyB,B:contact.bodyA)
switch (bodies.A.categoryBitMask,bodies.B.categoryBitMask)
{
case let (a, _) where (a && superPower): //All we care about is if category a has a super power
//do super power effect
fallthrough //continue onto check if we hit anything else
case let (_, b) where (b && superPower): //All we care about is if category b has a super power
//do super power effect
fallthrough //continue onto check if we hit anything else
case let (a, b) where (a && groundBody) && (b && boxBody): //Check if box hit ground
//boxBody hit ground
case let (b, _) where (b && boxBody): //Check if box hit anything else
//box body hit anything else
default:()
}
Différence entre contacts et collisions
Dans Sprite-Kit, il y a le concept de collision qui fait référence au moteur physique SK qui interagit avec la façon dont les objets physiques interagissent lorsqu'ils entrent en collision, par exemple ceux qui rebondissent sur les autres.
Il a également le concept de contacts , qui est le mécanisme par lequel votre programme est informé lorsque 2 objets physiques se croisent.
Les objets peuvent entrer en collision mais ne pas générer de contacts, générer des contacts sans entrer en collision, ou entrer en collision et générer un contact (ou ne pas interagir ni interagir du tout)
Les collisions peuvent également être unilatérales, c'est-à-dire que l'objet A peut entrer en collision (rebondir) avec l'objet B, tandis que l'objet B continue comme si rien ne s'était passé. Si vous voulez que deux objets se renversent, ils doivent tous les deux être contraints d'entrer en collision avec l'autre.
Les contacts ne sont toutefois pas à sens unique; si vous voulez savoir quand l'objet A a touché (contacté) l'objet B, il suffit de configurer la détection de contact sur l'objet A par rapport à l'objet B. Il n'est pas nécessaire de configurer la détection de contact sur l'objet B pour l'objet A.
Manipulation des masques de bit contactTest et collison pour activer / désactiver le contact et les collisions spécifiques.
Pour cet exemple, nous utiliserons 4 corps et ne montrerons que les 8 derniers bits des masques de bits pour plus de simplicité. Les 4 corps sont 3 SKSpriteNodes, chacun avec un corps physique et une limite:
let edge = frame.insetBy(dx: 0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: edge)
Notez que le corps physique "bord" est le corps physique de la scène, pas un nœud.
Nous définissons 4 catégories uniques
let purpleSquareCategory: UInt32 = 1 << 0 // bitmask is ...00000001
let redCircleCategory: UInt32 = 1 << 1 // bitmask is ...00000010
let blueSquareCategory: UInt32 = 1 << 2 // bitmask is ...00000100
let edgeCategory: UInt32 = 1 << 31 // bitmask is 10000...00000000
Chaque corps de physique se voit attribuer les catégories auxquelles il appartient:
//Assign our category bit masks to our physics bodies
purpleSquare.physicsBody?.categoryBitMask = purpleSquareCategory
redCircle.physicsBody?.categoryBitMask = redCircleCategory
blueSquare.physicsBody?.categoryBitMask = blueSquareCategory
physicsBody?.categoryBitMask = edgeCategory // This is the edge for the scene itself
Si un bit de la propriété collisionBitMask d'un corps est défini sur 1, alors il se heurte (rebondit) à tout corps ayant un "1" à la même position dans son objet categoryBitMask. De même pour contactTestBitMask.
À moins d'indication contraire, tout se heurte à tout le reste et aucun contact n'est généré (votre code ne sera pas notifié lorsque quelque chose entrera en contact avec quelque chose d'autre):
purpleSquare.physicsBody.collisonBitMask = 11111111111111111111111111111111 // 32 '1's.
Chaque bit dans chaque position est '1', donc quand on le compare à n'importe quel autre categoryBitMask, Sprite Kit trouvera un '1' et une collision se produira. Si vous ne voulez pas que ce corps entre en collision avec une certaine catégorie, vous devrez définir le bon bit dans le collisonBitMask sur '0'
et son contactTestbitMask
est défini sur tous les 0
s:
redCircle.physicsBody.contactTestBitMask = 00000000000000000000000000000000 // 32 '0's
Identique à collisionBitMask, sauf inversé.
Les contacts ou les collisions entre les corps peuvent être désactivés (laissant le contact ou la collision existants inchangés) en utilisant:
nodeA.physicsBody?.collisionBitMask &= ~nodeB.category
Nous avons logiquement le masque de bit de collision de ET nodeA avec l'inverse (l'opérateur logique, l'opérateur ~) du masque de catégorie de nodeB pour "désactiver" ce bitMask du bit nodeA. par exemple pour empêcher le cercle rouge d'entrer en collision avec le carré violet:
redCircle.physicsBody?.collisionBitMask = redCircle.physicsBody?.collisionBitMask & ~purpleSquareCategory
qui peut être raccourci à:
redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory
Explication:
redCircle.physicsBody.collisonBitMask = 11111111111111111111111111111111
purpleSquareCategory = 00000000000000000000000000000001
~purpleSquareCategory = 11111111111111111111111111111110
11111111111111111111111111111111 & 11111111111111111111111111111110 = 11111111111111111111111111111110
redCircle.physicsBody.collisonBitMask now equals 11111111111111111111111111111110
redCircle n'entre plus en collision avec des corps avec une catégorie de .... 0001 (purpleSquare)
Au lieu de désactiver les bits individuels dans le collsionsbitMask, vous pouvez le définir directement:
blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory)
c'est-à-dire blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)
qui équivaut à blueSquare.physicsBody?.collisionBitMask = ....00000011
blueSquare n'entrera en collision qu'avec des corps avec une catégorie ou ..01 ou ..10
Les contacts ou les collisions entre les 2 corps peuvent être activés (sans affecter les contacts existants ou collisions) à tout moment en utilisant:
redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory
Nous avons logiquement le bitMask de AND redCircle avec le masque de bits de la catégorie purpleSquare pour "activer" ce bit dans le bitMask de redcircle. Cela laisse les autres bits du bitMas de redCircel non affectés.
Vous pouvez vous assurer que chaque forme «rebondit» sur un bord de l'écran comme suit:
// Make sure everything collides with the screen edge
enumerateChildNodes(withName: "//*") { node, _ in
node.physicsBody?.collisionBitMask |= self.edgeCategory //Add edgeCategory to the collision bit mask
}
Remarque:
Les collisions peuvent être unilatérales, c'est-à-dire que l'objet A peut entrer en collision (rebondir) avec l'objet B, tandis que l'objet B continue comme si rien ne s'était passé. Si vous voulez que deux objets se rejoignent, ils doivent tous les deux se heurter à l'autre:
blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory
Les contacts ne sont toutefois pas à sens unique; si vous voulez savoir quand l'objet A a touché (contacté) l'objet B, il suffit de configurer la détection de contact sur l'objet A par rapport à l'objet B. Il n'est pas nécessaire de configurer la détection de contact sur l'objet B pour l'objet A.
blueSquare.physicsBody?.contactTestBitMask = redCircleCategory
Nous n'avons pas besoin de redcircle.physicsBody?.contactTestBitMask= blueSquareCategory
Utilisation avancée:
Non couvert ici, mais les corps physiques peuvent appartenir à plusieurs catégories. Par exemple, nous pourrions configurer notre jeu comme suit:
let squareCategory: UInt32 = 1 << 0 // bitmask is ...00000001
let circleCategory: UInt32 = 1 << 1 // bitmask is ...00000010
let blueCategory: UInt32 = 1 << 2 // bitmask is ...00000100
let redCategory: UInt32 = 1 << 3 // bitmask is ...00001000
let purpleCategory: UInt32 = 1 << 4 // bitmask is ...00010000
let edgeCategory: UInt32 = 1 << 31 // bitmask is 10000...0000000
Chaque corps de physique se voit attribuer les catégories auxquelles il appartient:
//Assign our category bit masks to our physics bodies
purpleSquare.physicsBody?.categoryBitMask = squareCategory | purpleCategory
redCircle.physicsBody?.categoryBitMask = circleCategory | redCategory
blueSquare.physicsBody?.categoryBitMask = squareCategory | blueCategory
leurs categorybitMasks sont maintenant:
purpleSquare.physicsBody?.categoryBitMask = ...00010001
redCircle.physicsBody?.categoryBitMask = ...00001010
blueSquare.physicsBody?.categoryBitMask = ...00000101
Cela affectera la façon dont vous manipulez les champs de bits. Cela peut être utile (par exemple) d’indiquer qu’un corps physique (par exemple une bombe) a changé (par exemple, il pourrait avoir la capacité «super» qui est une autre catégorie, et vous pouvez vérifier qu’un certain objet