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:

  1. categoryBitMask : Eine für diesen Knotentyp spezifische Kategorie
  2. collisionBitMask : ein Kollisionsunterscheidungsmerkmal, kann von oben abweichen
  3. contactTestBitMask : Ein Kontaktunterscheidungsmerkmal, das sich von beiden unterscheiden kann

Die allgemeinen Schritte zur Implementierung von Kollisionen und Kontakten sind:

  1. Körpergröße, -form und (manchmal) Masse einstellen
  2. Fügen Sie die erforderlichen BitMasks für Ihren Knotentyp aus Kategorie, Kollision und Kontakt oben hinzu
  3. Szene als Kontaktdelegierte festlegen, um Kollisionen und Kontakte zu überprüfen und zu informieren
  4. 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.)



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow