Buscar..


Observaciones

Los determinantes de la colisión del Sprite Kit y el procesamiento de eventos de contacto son los ajustes de relación, creados por usted, de categoryBitMask , collisionBitMask y contactTestBitMask para cada uno de sus tipos de objetos interactivos. Al establecer racionalmente estos servicios al servicio de los resultados deseados de los contactos y las colisiones, usted determina qué tipos pueden colisionar e informar de los contactos con otros, y evita la colisión no deseada, el contacto y la sobrecarga de procesamiento de la física.

Para cada tipo de 'entidad' puede configurar los tres:

  1. categoryBitMask : una categoría específica para este tipo de nodo
  2. collisionBitMask : un diferenciador de colisión, puede ser diferente del anterior
  3. contactTestBitMask : un diferenciador de contactos, puede ser diferente de los dos anteriores

Los pasos generales para implementar colisiones y contactos son:

  1. establecer tamaño corporal físico, forma y (a veces) masa
  2. agregue las BitMasks necesarias para su tipo de nodo de la categoría, colisión y contacto arriba
  3. configura la escena como un delegado de contacto que le permite verificar e informar sobre colisiones y contactos
  4. Implementar controladores de contactos y cualquier otra lógica pertinente para eventos de física.

Habilitar el mundo de la física

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

Habilitar nodo para colisionar

En primer lugar, establecemos la categoría de nodo.

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

Luego agregue el nodo Tipo de tierra y el nodo Tipo de caja

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)

Contactos de la manija

Establecer escena como delegado

//set your scene as SKPhysicsContactDelegate

class yourScene: SKScene, SKPhysicsContactDelegate

self.physicsWorld.contactDelegate = self;

Luego, tiene que implementar una u otra de las funciones de contacto: la función opcional comido Comenzar (contacto :) y / o el método opcional de fondos de fondos (contacto :) para completar su lógica de contacto, por ejemplo, como

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

Si está utilizando categorías simples, y cada cuerpo de física pertenece a una sola categoría, entonces esta forma alternativa de didBeginContact puede ser más legible:

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

Simple proyecto de Sprite Kit que muestra colisiones, contactos y eventos táctiles.

Aquí hay un simple Sprite-Kit GameScene.swift. Cree un nuevo proyecto SpriteKit vacío y reemplace el GameScene.swift con esto. Luego construir y ejecutar.

Haga clic en cualquiera de los objetos en la pantalla para hacer que se muevan. Verifique los registros y los comentarios para ver cuáles chocan y cuáles entran en contacto.

//
//  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 manejo de contacto cuando se trata de sprites de múltiples categorías

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

Diferencia entre contactos y colisiones.

En Sprite-Kit, existe el concepto de colisiones que se refiere al motor de física de SK que maneja cómo interactúan los objetos de la física cuando chocan, es decir, cuáles rebotan sobre otros.

También tiene el concepto de contactos , que es el mecanismo por el cual su programa se informa cuando se cruzan 2 objetos de física.

Los objetos pueden chocar pero no generar contactos, generar contactos sin chocar, o chocar y generar un contacto (o no hacer nada y no interactuar en absoluto)

Las colisiones también pueden ser de un solo lado, es decir, el objeto A puede chocar (rebotar) el objeto B, mientras que el objeto B continúa como si nada hubiera sucedido. Si quieres que 2 objetos reboten entre sí, debes decirles que colisionen con el otro.

Sin embargo, los contactos no son unilaterales; Si desea saber cuándo el objeto A tocó (contactó) el objeto B, es suficiente configurar la detección de contactos en el objeto A con respecto al objeto B. No tiene que configurar la detección de contactos en el objeto B para el objeto A.

Manipular las máscaras de bits contactTest y collison para habilitar / deshabilitar contactos y colisiones específicos.

Para este ejemplo, usaremos 4 cuerpos y mostraremos solo los últimos 8 bits de las máscaras de bits para simplificar. Los 4 cuerpos son 3 SKSpriteNodes, cada uno con un cuerpo de física y un límite:

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

Tenga en cuenta que el cuerpo físico de 'borde' es el cuerpo físico de la escena, no un nodo.

Definimos 4 categorías únicas.

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

A cada cuerpo de física se le asignan las categorías a las que pertenece:

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

Si un bit en collisionBitMask de un cuerpo se establece en 1, entonces colisiona (rebota) cualquier cuerpo que tenga un '1' en la misma posición en su categoríaBitMask. Del mismo modo para contactTestBitMask.

A menos que especifique lo contrario, todo choca con todo lo demás y no se generan contactos (no se notificará a su código cuando nada entre en contacto con cualquier otra cosa):

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

Cada bit en cada posición es '1', por lo que, en comparación con cualquier otra categoría de BitMask, Sprite Kit encontrará un '1' por lo que se producirá una colisión. Si no desea que este cuerpo colisione con una determinada categoría, deberá establecer el bit correcto en collisonBitMask en '0'

y su contactTestbitMask se establece en todos los 0 s:

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

Igual que para collisionBitMask, excepto revertido.

Los contactos o las colisiones entre cuerpos se pueden desactivar (dejando el contacto existente o la colisión sin cambios) usando:

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

En forma lógica, la máscara de bits de colisión de AND nodeA con el inverso (NOT lógico, el operador ~) de la máscara de bits de la categoría de nodeB para "desactivar" la bitMask de bitAnd de ese bit. Por ejemplo, para evitar que el círculo rojo colisione con el cuadrado púrpura:

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

que se puede acortar a:

redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory

Explicación:

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

redCircle ya no colisiona con cuerpos con una categoría de ... 0001 (purpleSquare)

En lugar de desactivar bits individuales en collsionsbitMask, puede establecerlo directamente:

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

es decir, blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)

que es igual a blueSquare.physicsBody?.collisionBitMask = ....00000011

blueSquare solo chocará con cuerpos de una categoría o ..01 o ..10

Contactos o colisiones entre 2 cuerpos pueden estar activada (sin afectar a los contactos existentes o colisiones) en cualquier punto usando:

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

Lógicamente AND redCircle's bitMask con la máscara de bits de purpleSquare para "activar" ese bit en bitMask de redcircle. Esto deja cualquier otro bit en los bitMas de redCircel no afectado.

Puede asegurarse de que cada forma "rebote" en un borde de la pantalla de la siguiente manera:

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

Las colisiones pueden ser de un solo lado, es decir, el objeto A puede chocar (rebotar) el objeto B, mientras que el objeto B continúa como si nada hubiera sucedido. Si desea que 2 objetos reboten entre sí, debe decirles a ambos que colisionen con el otro:

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

Sin embargo, los contactos no son unilaterales; Si desea saber cuándo el objeto A tocó (contactó) el objeto B, es suficiente configurar la detección de contactos en el objeto A con respecto al objeto B. No tiene que configurar la detección de contactos en el objeto B para el objeto A.

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

No necesitamos redcircle.physicsBody?.contactTestBitMask= blueSquareCategory

Uso avanzado:

No se cubre aquí, pero los cuerpos físicos pueden pertenecer a más de una categoría. Por ejemplo, podríamos configurar nuestro juego de la siguiente manera:

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

A cada cuerpo de física se le asignan las categorías a las que pertenece:

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

su categoríabitMasks ahora son:

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

Esto afectará cómo manipulas los campos de bits. Puede ser útil (por ejemplo) para indicar que un cuerpo de física (por ejemplo, una bomba) ha cambiado de alguna manera (por ejemplo, podría haber ganado la habilidad 'super' que es otra categoría, y usted puede verificar que un determinado objeto (una madre alienígena)



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow