Ricerca…


Osservazioni

I fattori determinanti della collisione di Sprite Kit e dell'elaborazione degli eventi di contatto sono le impostazioni di relazione, create dall'utente, di categoryBitMask , collisionBitMask e contactTestBitMask per ciascuno dei tipi di oggetti che interagiscono. Impostando razionalmente questi in servizio dei risultati desiderati dai contatti e dalle collisioni, si determina quali tipi possono scontrarsi e informare dei contatti con gli altri, ed evitare sovraccarichi indesiderati di elaborazione, contatto e elaborazione fisica.

Per ogni tipo di 'entità' puoi impostare tutti e tre:

  1. categoryBitMask : una categoria specifica per questo tipo di nodo
  2. collisionBitMask : un differenziatore di collisione, può essere diverso da sopra
  3. contactTestBitMask : un differenziatore di contatti, può essere diverso da entrambi sopra

I passaggi generali per implementare collisioni e contatti sono:

  1. imposta la dimensione fisica del corpo, la forma e (a volte) la massa
  2. aggiungi i BitMasks necessari per il tuo tipo di nodo da categoria, collisione e contatto sopra
  3. imposta la scena come delegato di contatto che consente di controllare e informare di collisioni e contatti
  4. implementare i gestori dei contatti e qualsiasi altra logica pertinente per gli eventi di fisica

Abilita il mondo fisico

// World physics
    self.physicsWorld.gravity         = CGVectorMake(0, -9.8);

Abilita nodo su collisione

In primo luogo, impostiamo la categoria del nodo

let groundBody: UInt32 = 0x1 << 0
let boxBody: UInt32 = 0x1 << 1

Quindi aggiungere il nodo Tipo di terra e il nodo Tipo di casella.

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)

Gestire i contatti

Imposta la scena come delegata

//set your scene as SKPhysicsContactDelegate

class yourScene: SKScene, SKPhysicsContactDelegate

self.physicsWorld.contactDelegate = self;

Quindi devi implementare l'una o l'altra delle funzioni di contatto: opzionale func didBegin (contatto :) e / o fondo opzionale didEnd (contatto :) metodo per inserire la tua logica di contatto, ad es.

//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)

   }
}

Alternativa didBeginContact

Se si utilizzano categorie semplici, con ciascun corpo fisico appartenente a una sola categoria, questa forma alternativa di didBeginContact potrebbe essere più leggibile:

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")
}
}

Progetto Simple Sprite Kit che mostra collisioni, contatti e eventi touch.

Ecco un semplice Sprite-Kit GameScene.swift. Crea un nuovo progetto SpriteKit vuoto e sostituisci GameScene.swift con questo. Quindi costruisci e corri.

Clicca su uno qualsiasi degli oggetti sullo schermo per farli muovere. Controlla i log e i commenti per vedere quali si scontrano e quali contattano.

//
//  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)")}
                }
            }
        }
    }
}

Alternativa al contatto di gestione quando si tratta di sprite di categoria multipla

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:()
  
}

Differenza tra contatti e collisioni

In Sprite-Kit, c'è il concetto di collisione che si riferisce al motore fisico SK che gestisce come gli oggetti della fisica interagiscono quando si scontrano, ovvero quali rimbalzano su altri.

Ha anche il concetto di contatti , che è il meccanismo con cui il tuo programma viene informato quando 2 oggetti fisici si intersecano.

Gli oggetti possono entrare in collisione ma non generare contatti, generare contatti senza scontrarsi o collidere e generare un contatto (o non fare nessuno dei due e non interagire affatto)

Le collisioni possono anche essere unilaterali, cioè l'oggetto A può scontrarsi (rimbalzare) l'oggetto B, mentre l'oggetto B continua come se nulla fosse accaduto. Se vuoi che 2 oggetti rimbalzino l'uno sull'altro, entrambi devono essere messi in collisione con l'altro.

I contatti tuttavia non sono unilaterali; se vuoi sapere quando un oggetto B toccato (contattato), è sufficiente impostare il rilevamento dei contatti sull'oggetto A per quanto riguarda l'oggetto B. Non devi impostare il rilevamento dei contatti sull'oggetto B per l'oggetto A.

Manipolazione di test di contatto e mascheramento di maschere di bit per abilitare / disabilitare specifici contatti e collisioni.

Per questo esempio, utilizzeremo 4 corpi e mostreremo solo gli ultimi 8 bit delle maschere di bit per semplicità. I 4 corpi sono 3 SKSpriteNodes, ciascuno con un corpo fisico e un limite:

    let edge = frame.insetBy(dx: 0, dy: 0)
    physicsBody = SKPhysicsBody(edgeLoopFrom: edge)

Si noti che il corpo fisico del 'bordo' è il corpo fisico della scena, non un nodo.

Definiamo 4 categorie uniche

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

Ad ogni corpo di fisica sono assegnate le categorie a cui appartiene:

        //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

Se un bit nella collisionBitMask di un corpo è impostato su 1, allora collide (rimbalza) qualsiasi corpo che ha un '1' nella stessa posizione nella sua categoriaBitMask. Allo stesso modo per contactTestBitMask.

Se non diversamente specificato, tutto si scontra con tutto il resto e nessun contatto viene generato (il tuo codice non verrà avvisato quando qualcosa contatterà qualcos'altro):

purpleSquare.physicsBody.collisonBitMask = 11111111111111111111111111111111 // 32 '1's.

Ogni bit in ogni posizione è '1', quindi se paragonato a qualsiasi altra categoriaBitMask, Sprite Kit troverà un '1' in modo che si verifichi una collisione. Se non vuoi che questo corpo entri in collisione con una determinata categoria, dovrai impostare il bit corretto in collisonBitMask su '0'

e il suo contactTestbitMask è impostato su tutti 0 s:

redCircle.physicsBody.contactTestBitMask = 00000000000000000000000000000000  // 32 '0's

Come per collisionBitMask, ad eccezione del contrario.

I contatti o le collisioni tra i corpi possono essere disattivati (lasciando invariato il contatto esistente o la collisione) utilizzando:

nodeA.physicsBody?.collisionBitMask &= ~nodeB.category

Abbiamo logicamente la maschera di bit di collisione AND nodeA con l'inverso (NOT logico, l'operatore ~) del bitmask di categoria del nodo B per 'disattivare' la bitMask del bit nodeA. per esempio per impedire al cerchio rosso di scontrarsi con il quadrato viola:

redCircle.physicsBody?.collisionBitMask = redCircle.physicsBody?.collisionBitMask & ~purpleSquareCategory

che può essere abbreviato in:

redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory

Spiegazione:

redCircle.physicsBody.collisonBitMask = 11111111111111111111111111111111
purpleSquareCategory  = 00000000000000000000000000000001
~purpleSquareCategory = 11111111111111111111111111111110 
11111111111111111111111111111111 & 11111111111111111111111111111110 = 11111111111111111111111111111110 
redCircle.physicsBody.collisonBitMask now equals 11111111111111111111111111111110 

redCircle non si scontra più con i corpi con una categoria di .... 0001 (purpleSquare)

Invece di disattivare singoli bit in collsionsbitMask, puoi impostarlo direttamente:

blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory)

cioè blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)

quale è uguale a blueSquare.physicsBody?.collisionBitMask = ....00000011

blueSquare si scontrerà solo con i corpi con una categoria o ..01 o ..10

Contatti o collisioni tra 2 corpi possono essere attivati (senza modificare alcun contatto esistenti o collisioni) in qualsiasi punto utilizzando:

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

Abbiamo logicamente AND bitMask di redCircle con la bitmask di categoria di purpleSquare per 'accendere' quel bit in bitMask di redcircle. Ciò lascia inalterati altri bit nel bit di redCircel.

Puoi assicurarti che ogni forma "rimbalzi" su un bordo dello schermo come segue:

// Make sure everything collides with the screen edge
enumerateChildNodes(withName: "//*") { node, _ in
    node.physicsBody?.collisionBitMask |= self.edgeCategory  //Add edgeCategory to the collision bit mask
}

Nota:

Le collisioni possono essere unilaterali, cioè l'oggetto A può scontrarsi (rimbalzare) l'oggetto B, mentre l'oggetto B continua come se nulla fosse accaduto. Se vuoi che 2 oggetti rimbalzino l'uno sull'altro, entrambi devono essere messi in collisione con l'altro:

blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory

I contatti tuttavia non sono unilaterali; se vuoi sapere quando un oggetto B toccato (contattato), è sufficiente impostare il rilevamento dei contatti sull'oggetto A per quanto riguarda l'oggetto B. Non devi impostare il rilevamento dei contatti sull'oggetto B per l'oggetto A.

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

Non abbiamo bisogno di redcircle.physicsBody?.contactTestBitMask= blueSquareCategory

Utilizzo avanzato:

Non trattato qui, ma i corpi fisici possono appartenere a più di una categoria. Ad esempio, potremmo impostare il gioco come segue:

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

Ad ogni corpo di fisica sono assegnate le categorie a cui appartiene:

        //Assign our category bit masks to our physics bodies
        purpleSquare.physicsBody?.categoryBitMask = squareCategory | purpleCategory
        redCircle.physicsBody?.categoryBitMask = circleCategory | redCategory
        blueSquare.physicsBody?.categoryBitMask = squareCategory | blueCategory

i loro categorybitMasks sono ora:

purpleSquare.physicsBody?.categoryBitMask = ...00010001
redCircle.physicsBody?.categoryBitMask    = ...00001010
blueSquare.physicsBody?.categoryBitMask   = ...00000101

Ciò influirà sulla modalità di manipolazione dei campi di bit. Può essere utile (ad esempio) per indicare che un corpo fisico (ad esempio una bomba) è cambiato in qualche modo (ad esempio potrebbe aver acquisito l'abilità "super" che è un'altra categoria, e si potrebbe verificare che un determinato oggetto (una madri aliena



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow