Sök…


Anmärkningar

Bestämningarna för Sprite Kit-kollision och behandling av kontakthändelser är relationsinställningarna, skapade av dig, av categoryBitMask , collisionBitMask och contactTestBitMask för var och en av dina interagerande objekttyper. Genom att rationellt sätta dessa i tjänst för önskade resultat från kontakter och kollisioner, bestämmer du vilka typer som kan kollidera och informera om kontakter med andra och undvika oönskade kollisioner, kontakt- och fysikbearbetningskostnader.

För varje typ av "enhet" kan du ställa in alla tre:

  1. categoryBitMask : en kategori som är specifik för denna typ av nod
  2. collisionBitMask : en kollisionsdifferentierare, kan skilja sig från ovan
  3. contactTestBitMask : en kontaktseparator, kan skilja sig från båda ovan

De allmänna stegen för att genomföra kollisioner och kontakter är:

  1. ställa in fysisk kroppsstorlek, form och (ibland) massa
  2. lägg till nödvändiga BitMasks för din nodtyp från kategori, kollision och kontakt ovan
  3. sätta upp scenen som en kontaktdelegat så att den kan kontrollera och informera om kollisioner och kontakter
  4. implementera kontakthanterare och annan relevant logik för fysikhändelser

Aktivera fysikvärlden

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

Aktivera nod att kollidera

För det första sätter vi noder kategori

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

Lägg sedan till marknoden och noden Boxtyp.

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)

Hantera kontakter

Ställ in scenen som delegat

//set your scene as SKPhysicsContactDelegate

class yourScene: SKScene, SKPhysicsContactDelegate

self.physicsWorld.contactDelegate = self;

Då måste du implementera den ena eller den andra av kontaktfunktionerna: valfri func didBegin (kontakt :) och / eller valfri fond didEnd (kontakt :) -metod för att fylla i din kontaktlogik t.ex.

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

   }
}

Alternativ didBeginContact

Om du använder enkla kategorier, där varje fysikorgan tillhör en kategori, kan denna alternativa form av didBeginContact vara mer läsbar:

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

Enkelt Sprite Kit-projekt som visar kollisioner, kontakter och touch-händelser.

Här är en enkel Sprite-Kit GameScene.swift. Skapa ett nytt, tomt SpriteKit-projekt och ersätt GameScene.swift med detta. Bygg sedan och kör.

Klicka på något av objekten på skärmen för att få dem att flytta. Kontrollera loggarna och kommentarerna för att se vilka som kolliderar och vilka som tar 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)")}
                }
            }
        }
    }
}

Alternativ till Hantering av kontakt vid hantering av spriter i flera kategorier

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

Skillnad mellan kontakter och kollisioner

I Sprite-Kit finns begreppet kollisioner som hänvisar till SK-fysikmotorn som hanterar hur fysikobjekt samverkar när de kolliderar, dvs vilka som studsar från vilka andra.

Det har också begreppet kontakter , som är mekanismen genom vilket ditt program informeras när två fysikobjekt korsar varandra.

Objekt kan kollidera men inte generera kontakter, generera kontakter utan att kollidera eller kollidera och generera en kontakt (eller inte heller och inte interagerar alls)

Kollisioner kan också vara ensidiga, dvs objekt A kan kollidera (studsa av) objekt B, medan objekt B fortsätter som om ingenting hade hänt. Om du vill att två objekt ska studsa av varandra måste de båda få höra att kollidera med den andra.

Kontakter är emellertid inte ensidiga; om du vill veta när objekt A berört (kontaktat) objekt B räcker det att ställa in kontaktdetektering på objekt A med avseende på objekt B. Du behöver inte ställa in kontaktdetektering på objekt B för objekt A.

Hantera contactTest och kollison bitmasker för att aktivera / inaktivera specifik kontakt och kollisioner.

I det här exemplet kommer vi att använda fyra kroppar och visar endast de sista 8 bitarna av bitmaskerna för enkelhetens skull. De fyra kropparna är 3 SKSpriteNodes, var och en med en fysikkropp och en gräns:

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

Observera att den "kant" fysiska kroppen är fysikens kropp i scenen, inte en nod.

Vi definierar fyra unika kategorier

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

Varje fysikorgan tilldelas de kategorier som det tillhör:

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

Om en bit i en kropps kollisionBitMask är inställd på 1, kolliderar (slår den av) någon kropp som har en '1' i samma position i sin kategoriBitMask. På samma sätt för contactTestBitMask.

Om du inte anger något annat, kolliderar allt med allt annat och inga kontakter genereras (din kod kommer inte att meddelas när något kontaktar något annat):

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

Varje bit i varje position är '1', så jämfört med någon annan kategoriBitMask, kommer Sprite Kit att hitta en '1' så att en kollision kommer att inträffa. Om du inte vill att detta organ ska kollidera med en viss kategori måste du ställa in rätt bit i collisonBitMask till '0'

och dess contactTestbitMask är inställd på alla 0 er:

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

Samma som för kollisionBitMask, utom omvänd.

Kontakter eller kollisioner mellan organ kan stängas av (lämna befintlig kontakt eller kollision oförändrad) med:

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

Vi logiskt OCH nodeAs kollisionsbitmask med den omvända (logiska INTE, ~ operatören) av nodeBs kategori bitmask för att "stänga av" den bitnodenAs bitmask. t.ex. för att hindra den röda cirkeln från att kollidera med den lila fyrkanten:

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

som kan förkortas till:

redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory

Förklaring:

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

redCircle kolliderar inte längre med kroppar med en kategori av .... 0001 (lilaSquare)

Istället för att stänga av enskilda bitar i collsionsbitMask, kan du ställa in det direkt:

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

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

vilket är lika med blueSquare.physicsBody?.collisionBitMask = ....00000011

blueSquare kolliderar endast med kroppar med en kategori eller ..01 eller ..10

Kontakter eller kollisioner mellan två organ kan slås (utan att påverka befintliga kontakter eller kollisioner) när som helst med:

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

Vi logiskt OCH redCircles bitmask med purpleSquares kategori bitmask för att "slå på" den biten i redcircles bitMask. Detta gör att andra bitar i redCircels bitMas inte påverkas.

Du kan se till att varje form "slår av" en skärmkant på följande sätt:

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

Notera:

Kollisioner kan vara ensidiga, dvs objekt A kan kollidera (studsa av) objekt B, medan objekt B fortsätter som om ingenting hade hänt. Om du vill att två objekt ska studsa av varandra måste de båda få höra att kollidera med den andra:

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

Kontakter är emellertid inte ensidiga; om du vill veta när objekt A berört (kontaktat) objekt B räcker det att ställa in kontaktdetektering på objekt A med avseende på objekt B. Du behöver inte ställa in kontaktdetektering på objekt B för objekt A.

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

Vi behöver inte redcircle.physicsBody?.contactTestBitMask= blueSquareCategory

Avancerad användning:

Inte omfattas här, men fysikorgan kan tillhöra mer än en kategori. Vi kan t.ex. ställa in vårt spel på följande sätt:

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

Varje fysikorgan tilldelas de kategorier som det tillhör:

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

deras kategoribit Masker är nu:

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

Detta kommer att påverka hur du manipulerar bitfälten. Det kan vara användbart (till exempel) att indikera att en fysikkropp (t.ex. en bomb) har på något sätt förändrats (t.ex. kan det ha fått den "super" -förmågan som är en annan kategori, och du kan kontrollera att ett visst objekt (en främmande morsh



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow