sprite-kit
SKNode-Kollision
Suche…
Bemerkungen
Die Determinanten der Sprite Kit-Kollisions- und Kontaktereignisverarbeitung sind die von Ihnen erstellten Beziehungseinstellungen von categoryBitMask
, collisionBitMask
und contactTestBitMask
für jeden Ihrer interagierenden Objekttypen. Indem Sie diese rational für Ihre gewünschten Ergebnisse aus Kontakten und Kollisionen einsetzen, bestimmen Sie, welche Typen kollidieren und über Kontakte mit anderen informieren können, und vermeiden unerwünschten Kollisions-, Kontakt- und physischen Verarbeitungsaufwand.
Für jeden Entitätstyp können Sie alle drei einstellen:
-
categoryBitMask
: Eine für diesen Knotentyp spezifische Kategorie -
collisionBitMask
: ein Kollisionsunterscheidungsmerkmal, kann von oben abweichen -
contactTestBitMask
: Ein Kontaktunterscheidungsmerkmal, das sich von beiden unterscheiden kann
Die allgemeinen Schritte zur Implementierung von Kollisionen und Kontakten sind:
- Körpergröße, -form und (manchmal) Masse einstellen
- Fügen Sie die erforderlichen BitMasks für Ihren Knotentyp aus Kategorie, Kollision und Kontakt oben hinzu
- Szene als Kontaktdelegierte festlegen, um Kollisionen und Kontakte zu überprüfen und zu informieren
- Implementieren Sie Kontakthandler und andere relevante Logik für physikalische Ereignisse
Physikwelt aktivieren
// World physics
self.physicsWorld.gravity = CGVectorMake(0, -9.8);
Aktivieren Sie den Knoten zum Kollidieren
Zuerst setzen wir die Knotenkategorie
let groundBody: UInt32 = 0x1 << 0
let boxBody: UInt32 = 0x1 << 1
Fügen Sie dann den Bodentypknoten und den Boxentypknoten hinzu.
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)
Kontakte behandeln
Szene als Delegierter festlegen
//set your scene as SKPhysicsContactDelegate
class yourScene: SKScene, SKPhysicsContactDelegate
self.physicsWorld.contactDelegate = self;
Dann müssen Sie die eine oder andere Kontaktfunktion implementieren: optional func didBegin (contact :) und / oder optionale Methode didEnd (contact :), um Ihre Kontaktlogik zB auszufüllen
//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 didBeginContact
Wenn Sie einfache Kategorien verwenden, wobei jeder Physikkörper nur zu einer Kategorie gehört, ist diese alternative Form von didBeginContact möglicherweise lesbarer:
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")
}
}
Einfaches Sprite-Kit-Projekt mit Kollisionen, Kontakten und Berührungsereignissen.
Hier ist ein einfaches Sprite-Kit GameScene.swift. Erstellen Sie ein neues, leeres SpriteKit-Projekt und ersetzen Sie das GameScene.swift durch dieses. Dann bauen und losfahren.
Klicken Sie auf eines der Objekte auf dem Bildschirm, um sie zu bewegen. Überprüfen Sie die Protokolle und Kommentare, um zu sehen, welche aufeinander treffen und welche Kontakt aufnehmen.
//
// 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 zum Handling-Kontakt bei Sprites mit mehreren Kategorien
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:()
}
Unterschied zwischen Kontakten und Kollisionen
In Sprite-Kit gibt es das Konzept von Kollisionen, das sich auf die Physik-Engine von SK bezieht, wie Physikobjekte bei Kollisionen interagieren, dh welche von welchen anderen abprallen.
Es hat auch das Konzept von Kontakten . Dies ist der Mechanismus, durch den Ihr Programm informiert wird, wenn sich zwei Physikobjekte schneiden.
Objekte können kollidieren, aber keine Kontakte erzeugen, Kontakte erzeugen, ohne zu kollidieren, oder kollidieren und einen Kontakt generieren (oder keinen oder überhaupt keinen Kontakt haben)
Kollisionen können auch einseitig sein, dh Objekt A kann Objekt B kollidieren (abprallen), während Objekt B weitergeht, als ob nichts passiert wäre. Wenn Sie möchten, dass zwei Objekte voneinander abprallen, müssen beide aufgefordert werden, mit dem anderen zu kollidieren.
Kontakte sind jedoch nicht einseitig; Wenn Sie wissen möchten, wann Objekt A Objekt B berührt (kontaktiert) hat, reicht es aus, Kontakterkennung für Objekt A in Bezug auf Objekt B einzurichten. Sie müssen Kontakterkennung für Objekt A nicht für Objekt A einrichten.
Bearbeiten von contactTest- und Collison-Bitmasken, um bestimmte Kontakte und Kollisionen zu aktivieren / deaktivieren.
In diesem Beispiel verwenden wir 4 Körper und zeigen der Einfachheit halber nur die letzten 8 Bits der Bitmasken. Bei den 4 Körpern handelt es sich um 3 SKSpriteNodes, die jeweils einen physischen Körper und eine Grenze haben:
let edge = frame.insetBy(dx: 0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: edge)
Beachten Sie, dass der physische Körper der "Kante" der physische Körper der Szene ist und kein Knoten.
Wir definieren 4 eindeutige Kategorien
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
Jedem Physikkörper werden die Kategorien zugewiesen, zu denen er gehört:
//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
Wenn ein Bit in der collisionBitMask eines Körpers auf 1 gesetzt ist, kollidiert (prallt es) jeder Körper, der eine '1' an derselben Position in seiner KategorieBitMask hat. Ähnlich für contactTestBitMask.
Wenn Sie nichts anderes angeben, kollidiert alles mit allem anderen und es werden keine Kontakte generiert (Ihr Code wird nicht benachrichtigt, wenn irgendetwas anderes Kontakt hat):
purpleSquare.physicsBody.collisonBitMask = 11111111111111111111111111111111 // 32 '1's.
Jedes Bit in jeder Position ist '1', so dass das Sprite Kit im Vergleich zu jeder anderen categoryBitMask eine '1' findet, so dass eine Kollision auftritt. Wenn Sie nicht möchten, dass dieser Körper mit einer bestimmten Kategorie kollidiert, müssen Sie das korrekte Bit in der collisonBitMask auf '0' setzen.
und seine contactTestbitMask
ist auf alle 0
s eingestellt:
redCircle.physicsBody.contactTestBitMask = 00000000000000000000000000000000 // 32 '0's
Dasselbe wie für collisionBitMask, außer umgekehrt.
Kontakte oder Kollisionen zwischen Körpern können deaktiviert werden (bestehende Kontakte oder Kollisionen bleiben unverändert).
nodeA.physicsBody?.collisionBitMask &= ~nodeB.category
Wir verknüpfen die Kollisionsbitmaske von nodeA logisch mit der Inversen (logisch NICHT ~ Operator ~) der Kategorie-Bitmaske von nodeB, um die Bitmaske von bit nodeA 'auszuschalten'. zB um zu verhindern, dass der rote Kreis mit dem violetten Quadrat kollidiert:
redCircle.physicsBody?.collisionBitMask = redCircle.physicsBody?.collisionBitMask & ~purpleSquareCategory
was kann verkürzt werden zu:
redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory
Erläuterung:
redCircle.physicsBody.collisonBitMask = 11111111111111111111111111111111
purpleSquareCategory = 00000000000000000000000000000001
~purpleSquareCategory = 11111111111111111111111111111110
11111111111111111111111111111111 & 11111111111111111111111111111110 = 11111111111111111111111111111110
redCircle.physicsBody.collisonBitMask now equals 11111111111111111111111111111110
redCircle kollidiert nicht mehr mit Körpern der .... 0001 (purpleSquare)
Anstatt einzelne Bits in der collsionsbitMask auszuschalten, können Sie sie direkt setzen:
blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory)
dh blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)
was entspricht blueSquare.physicsBody?.collisionBitMask = ....00000011
blueSquare kollidiert nur mit Körpern mit einer Kategorie oder ..01 oder ..10
Kontakte oder Kollisionen zwischen 2 Körpern können eingeschaltet werden (ohne irgendwelche bestehenden Kontakte oder Kollisionen zu beeinflussen) an jedem Punkt unter Verwendung von :
redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory
Wir logisch UND redCircles Bitmaske mit der Kategorie-Bitmaske von purpleSquare, um dieses Bit in redCircles Bitmaske zu aktivieren. Alle anderen Bits in redCircels bitMas bleiben davon unberührt.
Sie können sicherstellen, dass jede Form wie folgt vom Bildschirmrand abprallt:
// Make sure everything collides with the screen edge
enumerateChildNodes(withName: "//*") { node, _ in
node.physicsBody?.collisionBitMask |= self.edgeCategory //Add edgeCategory to the collision bit mask
}
Hinweis:
Kollisionen können einseitig sein, dh Objekt A kann Objekt B kollidieren (abprallen), Objekt B dagegen, als ob nichts passiert wäre. Wenn zwei Objekte gegeneinander springen sollen, müssen sie beide aufgefordert werden, mit dem anderen zu kollidieren:
blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory
Kontakte sind jedoch nicht einseitig; Wenn Sie wissen möchten, wann Objekt A Objekt B berührt (kontaktiert) hat, reicht es aus, Kontakterkennung für Objekt A in Bezug auf Objekt B einzurichten. Sie müssen Kontakterkennung für Objekt A nicht für Objekt A einrichten.
blueSquare.physicsBody?.contactTestBitMask = redCircleCategory
Wir brauchen keine redcircle.physicsBody?.contactTestBitMask= blueSquareCategory
Erweiterte Nutzung:
Nicht hier behandelt, aber Physikkörper können zu mehr als einer Kategorie gehören. ZB könnten wir unser Spiel wie folgt einrichten:
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
Jedem Physikkörper werden die Kategorien zugewiesen, zu denen er gehört:
//Assign our category bit masks to our physics bodies
purpleSquare.physicsBody?.categoryBitMask = squareCategory | purpleCategory
redCircle.physicsBody?.categoryBitMask = circleCategory | redCategory
blueSquare.physicsBody?.categoryBitMask = squareCategory | blueCategory
Ihre categorybitMasks sind jetzt:
purpleSquare.physicsBody?.categoryBitMask = ...00010001
redCircle.physicsBody?.categoryBitMask = ...00001010
blueSquare.physicsBody?.categoryBitMask = ...00000101
Dies beeinflusst, wie Sie die Bitfelder bearbeiten. Es kann (z. B.) nützlich sein, darauf hinzuweisen, dass sich ein Physikkörper (z. B. eine Bombe) auf irgendeine Weise verändert hat (z. B. hat er möglicherweise die Super-Fähigkeit erworben, eine andere Kategorie, und Sie können prüfen, ob ein bestimmter Gegenstand (ein Alien-Mothersh.)