Szukaj…


Uwagi

Uwarunkowaniami kolizji i przetwarzania zdarzeń kontaktowych Sprite Kit są utworzone przez Ciebie ustawienia relacji z categoryBitMask contactTestBitMask , collisionBitMask i contactTestBitMask dla każdego z typów interakcji. Racjonalnie ustawiając je w celu uzyskania pożądanych rezultatów z kontaktów i kolizji, określasz, które typy mogą kolidować i informować o kontaktach z innymi, a także unikać niepożądanych kolizji, kontaktów i przetwarzania fizyki.

Dla każdego rodzaju „bytu” można ustawić wszystkie trzy:

  1. categoryBitMask : kategoria specyficzna dla tego typu węzła
  2. collisionBitMask : wyróżnik kolizji, może różnić się od powyższego
  3. contactTestBitMask : contactTestBitMask kontaktu, może różnić się od obu powyższych

Ogólne kroki do wdrożenia kolizji i kontaktów to:

  1. ustawić fizyczny rozmiar ciała, kształt i (czasami) masę
  2. dodaj niezbędne maski bitowe dla swojego typu węzła z kategorii, kolizji i kontaktu powyżej
  3. ustawić scenę jako delegata kontaktu, umożliwiając jej sprawdzanie i informowanie o kolizjach i kontaktach
  4. wdrożyć procedury obsługi kontaktów i wszelkie inne stosowne elementy logiczne zdarzeń fizycznych

Włącz świat fizyki

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

Włącz węzeł do kolizji

Po pierwsze, ustalamy kategorię węzła

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

Następnie dodaj węzeł typu Ground i węzeł typu Box.

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)

Obsługuj kontakty

Ustaw scenę jako delegata

//set your scene as SKPhysicsContactDelegate

class yourScene: SKScene, SKPhysicsContactDelegate

self.physicsWorld.contactDelegate = self;

Następnie musisz zaimplementować jedną lub drugą funkcję kontaktu: opcjonalna funkcja didBegin (kontakt :) i / lub opcjonalny fundusz didEnd (kontakt :), aby wypełnić logikę kontaktu, np.

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

   }
}

Alternatywny didBeginContact

JEŻELI używasz prostych kategorii, a każde ciało fizyki należy tylko do jednej kategorii, ta alternatywna forma didBeginContact może być bardziej czytelna:

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

Prosty projekt Sprite Kit pokazujący kolizje, kontakty i zdarzenia dotykowe.

Oto prosty zestaw Sprite-Kit GameScene.swift. Utwórz nowy, pusty projekt SpriteKit i zastąp GameScene.swift tym. Następnie zbuduj i uruchom.

Kliknij dowolny obiekt na ekranie, aby go przesunąć. Sprawdź dzienniki i komentarze, aby zobaczyć, które kolidują, a które nawiązują kontakt.

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

Alternatywa dla obsługi kontaktu w przypadku duszków z wielu kategorii

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

Różnica między kontaktami a kolizjami

W Sprite-Kit istnieje koncepcja kolizji, która odnosi się do silnika fizyki SK, który obsługuje interakcje między obiektami fizyki podczas zderzenia, tj. Które odbijają się od innych.

Ma również koncepcję kontaktów , która jest mechanizmem, za pomocą którego twój program jest informowany, gdy przecinają się 2 obiekty fizyczne.

Obiekty mogą kolidować, ale nie mogą generować kontaktów, generować kontaktów bez kolizji lub kolidować i generować kontakt (lub nie wykonywać żadnych i nie wchodzić w interakcje)

Zderzenia mogą być również jednostronne, tzn. Obiekt A może zderzać się (odbijać) od obiektu B, podczas gdy obiekt B zachowuje się tak, jakby nic się nie wydarzyło. Jeśli chcesz, aby 2 obiekty odbiły się od siebie, oba muszą się zderzyć.

Kontakty nie są jednak jednostronne; jeśli chcesz wiedzieć, kiedy obiekt A dotknął (dotknął) obiektu B, wystarczy ustawić wykrywanie kontaktu na obiekcie A w odniesieniu do obiektu B. Nie musisz ustawiać wykrywania kontaktu na obiekcie B dla obiektu A.

Manipulowanie kontaktem Testuj i masek bitowych Collison, aby włączyć / wyłączyć określony kontakt i kolizje.

W tym przykładzie użyjemy 4 ciał i dla uproszczenia pokażemy tylko ostatnie 8 bitów masek bitowych. 4 ciała to 3 SKSpriteNodes, każde z ciałem fizycznym i granicą:

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

Zauważ, że „brzegowe” ciało fizyczne to fizyczne ciało sceny, a nie węzeł.

Definiujemy 4 unikalne kategorie

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

Każde ciało fizyczne ma przypisane kategorie, do których należy:

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

Jeśli bit w obiekcie collisionBitMask ma wartość 1, wówczas zderza się (odbija) od dowolnego ciała, które ma „1” w tej samej pozycji w swojej kategoriiBitMask. Podobnie w przypadku contactTestBitMask.

O ile nie określisz inaczej, wszystko koliduje ze wszystkim innym i żadne kontakty nie są generowane (Twój kod nie zostanie powiadomiony, gdy cokolwiek skontaktuje się z kimkolwiek innym):

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

Każdy bit w każdej pozycji ma wartość „1”, więc w porównaniu z innymi kategoriami BitMask, Sprite Kit znajdzie „1”, co spowoduje kolizję. Jeśli nie chcesz, aby to ciało kolidowało z określoną kategorią, musisz ustawić poprawny bit w collisonBitMask na „0”

i jej contactTestbitMask jest ustawiony na wszystkie 0 s:

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

Taki sam jak w przypadku collisionBitMask, tyle że odwrócony.

Kontakty lub kolizje między ciałami można wyłączyć (pozostawiając istniejący kontakt lub kolizję bez zmian) za pomocą:

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

Logicznie ORAZ maska bitowa kolizyjna węzła A z odwrotnością (operator logiczny NOT, operator ~) maski bitowej kategorii nodeB, aby „wyłączyć” bit bit maski tego węzła. np. aby zatrzymać kolizję czerwonego koła z fioletowym kwadratem:

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

który można skrócić do:

redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory

Wyjaśnienie:

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

redCircle nie koliduje już z ciałami o kategorii .... 0001 (purpleSquare)

Zamiast wyłączać poszczególne bity w masce CollsionsbitMask, możesz ustawić to bezpośrednio:

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

tj. blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)

co równa się blueSquare.physicsBody?.collisionBitMask = ....00000011

blueSquare zderzy się tylko z ciałami z kategorią lub ..01 lub ..10

Kontakt lub kolizji między 2 organy mogą być włączone (bez wpływu na jakiekolwiek istniejące kontakty lub kolizji) w dowolnym momencie przy użyciu:

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

Logicznie ORAZ maska bitowa redCircle z maską kategorii purpleSquare, aby „włączyć” ten bit w masce bitowej redcircle. To pozostawia wszelkie inne bity w bitMas redCircel bez zmian.

Możesz upewnić się, że każdy kształt „odbija się” od krawędzi ekranu w następujący sposób:

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

Uwaga:

Kolizje mogą być jednostronne, tzn. Obiekt A może zderzać się (odbijać) od obiektu B, podczas gdy obiekt B zachowuje się tak, jakby nic się nie wydarzyło. Jeśli chcesz, aby 2 obiekty odbijały się od siebie, oba muszą zostać poinformowane o zderzeniu się z drugim:

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

Kontakty nie są jednak jednostronne; jeśli chcesz wiedzieć, kiedy obiekt A dotknął (dotknął) obiektu B, wystarczy ustawić wykrywanie kontaktu na obiekcie A w odniesieniu do obiektu B. Nie musisz ustawiać wykrywania kontaktu na obiekcie B dla obiektu A.

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

Nie potrzebujemy redcircle.physicsBody?.contactTestBitMask= blueSquareCategory

Zaawansowane użycie:

Nie omówione tutaj, ale ciała fizyczne mogą należeć do więcej niż jednej kategorii. Np. Moglibyśmy ustawić naszą grę w następujący sposób:

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

Każde ciało fizyczne ma przypisane kategorie, do których należy:

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

ich kategoriaMaski są teraz:

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

Wpłynie to na sposób manipulowania polami bitowymi. Przydatne może być (na przykład) wskazanie, że ciało fizyki (np. Bomba) jakoś się zmieniło (np. Mogło zyskać zdolność „super”, która jest inną kategorią, i możesz sprawdzić, czy jakiś obiekt (obca matka



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow