tutorial spritekit kits gameplaykit swift xcode sprite-kit

swift - kits - Comprender SpriteKit CollisionBitMask



spritekit tutorial (2)

Estoy aprendiendo a usar SpriteKit y estoy siguiendo un tutorial para colisiones. Me cuesta entender el siguiente código:

struct PhysicsCategory { static let None : UInt32 = 0 static let All : UInt32 = UInt32.max static let Monster : UInt32 = 0b1 // 1 static let Projectile: UInt32 = 0b10 // 2 }

¿Por qué asignamos estas cosas llamadas bitMaps y cómo funcionan más adelante en el código a continuación ?:

func didBegin(_ contact: SKPhysicsContact) { // 1 var firstBody: SKPhysicsBody var secondBody: SKPhysicsBody if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask { firstBody = contact.bodyA secondBody = contact.bodyB } else { firstBody = contact.bodyB secondBody = contact.bodyA } // 2 if ((firstBody.categoryBitMask & PhysicsCategory.Monster != 0) && (secondBody.categoryBitMask & PhysicsCategory.Projectile != 0)) { if let monster = firstBody.node as? SKSpriteNode, let projectile = secondBody.node as? SKSpriteNode { projectileDidCollideWithMonster(projectile: projectile, monster: monster)

¡Gracias!


Las máscaras de bits son marcas utilizadas para describir un elemento en formato binario

así que imagina que tienes 8 formas de describir algo. (En Spritekit tienes 32)

Podemos ajustar estas 8 cosas en un solo byte, ya que 8 bits están en un byte, lo que nos permite ahorrar espacio y realizar operaciones más rápido.

Aquí hay un ejemplo de 8 descripciones

Attackable 1 << 0 Ranged 1 << 1 Undead 1 << 2 Magic 1 << 3 Regenerate 1 << 4 Burning 1 << 5 Frozen 1 << 6 Poison 1 << 7

Ahora tengo un arquero y quiero clasificarlo. Quiero decir que él es una unidad amigable que está a distancia

Usaría la categoryBitmask Bitmask para clasificarlo:

archer.categoryBitmask = Ranged

Esto se representaría en 1 byte como

00000010 ||||||||_ Attackable |||||||_ Ranged ||||||_ Undead |||||_ Magic ||||_ Regenerate |||_ Burning ||_ Frozen |_ Poison

Ahora digamos que sus flechas son flechas de fuego, clasificaría esto como:

arrow.categoryBitmask = Burning

00100000 ||||||||_ Attackable |||||||_ Ranged ||||||_ Undead |||||_ Magic ||||_ Regenerate |||_ Burning ||_ Frozen |_ Poison

y finalmente, tenemos un zombie que puede ser golpeado y se regenera con el tiempo

zombie.categoryBitmask = Attackable + Undead + Regenerate

00010101 ||||||||_ Attackable |||||||_ Ranged ||||||_ Undead |||||_ Magic ||||_ Regenerate |||_ Burning ||_ Frozen |_ Poison

Ahora quiero que mi flecha solo golpee sprites Attackable (zombie en este caso)

Establecería contactTestBitmask para decirle a la flecha qué puede golpear

arrow.contactTestBitmask = Attackable 00000001

Ahora tenemos que comprobar cuando una flecha golpea a un zombie, aquí es donde entra en didBeginContact

Lo que didBeginContact hará, es verificar el contactTestBitmask del elemento en movimiento a la categoryBitmask que golpea usando una operación AND para encontrar una coincidencia

En nuestro caso

arrow.contactTestBitmask = 00000001 zombie.categoryMask = 00010101 AND -------- 00000001

Como nuestro valor es> 0, un contacto fue exitoso.

Esto significa que didBegins disparó.

Ahora que estamos en didBegins, necesitamos determinar qué cuerpo de física es nuestra flecha y qué cuerpo de física es nuestro zombi.

aquí es donde entra esta siguiente declaración

func didBegin(_ contact: SKPhysicsContact) { // 1 var firstBody: SKPhysicsBody var secondBody: SKPhysicsBody if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask { firstBody = contact.bodyA secondBody = contact.bodyB } else { firstBody = contact.bodyB secondBody = contact.bodyA }

Dado que arrow = 00100000 y zombie = 00010101, sabemos que zombie tiene un valor más bajo que arrow, por lo que en este caso, zombie es <arrow.

firstBody a zombie y secondBody a arrow

Ahora necesitamos proporcionar una condición.

Queremos decir si un ser no-muerto es golpeado por un objeto quemable, hacer algo.

Entonces en código esto sería

if (firstBody & Undead > 0) && (secondBody & Burning > 0) { //burn zombie }

Pero, ¿y si la flecha fuera una flecha de hielo? No queremos entrar en esa declaración if.

Bueno, ahora podemos agregar una segunda condición para permitirnos congelar al zombi.

if (firstBody & Undead > 0) && (secondBody & Frozen > 0) { //freeze zombie }

Lo que estos if están haciendo es asegurarse de que el cuerpo tenga ciertas características activadas y luego realizar alguna acción en respuesta a ellas.

Para obtener más información sobre cómo funcionan las máscaras de bits, investigaría cómo hacer tablas de verdad. Eso es esencialmente a lo que se reduce esto. Solo estamos creando algunas tablas de verdad y estamos tratando de averiguar si una declaración es verdadera y si es verdadera, realizar una acción.


Manipulación de máscaras de bits de prueba y colisión 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 ''edge'' 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ía BitMask. Del mismo modo para contactTestBitMask.

A menos que especifique lo contrario, todo colisiona con todo lo demás y no se generan contactos (su código no será notificado cuando algo contacte a otra cosa):

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

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

y su contactTestbitMask se establece en todos los 0:

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

Igual que para collisionBitMask, excepto al revés.

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

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

Lógicamente, la máscara de bits de colisión de AND nodeA con el inverso (lógico NO, el operador ~) de la máscara de bits de categoría de nodeB para ''desactivar'' ese bitMask de bitA de nodo. Por ejemplo, para evitar que el círculo rojo choque 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 ahora es igual a 11111111111111111111111111111110 redCircle ya no colisiona con cuerpos con una categoría de ... 0001 (purpleSquare)

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

blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory) i.e. blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)

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

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

Los contactos o colisiones entre 2 cuerpos se pueden ENCENDER (sin afectar los contactos o colisiones existentes) en cualquier momento utilizando:

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

Lógicamente, Y el bitMask de redCircle con la máscara de bit de la categoría de purpleSquare para ''activar'' ese bit en el bitMask de redcircle. Esto deja cualquier otro bit en el bitMas de redCircel sin verse afectado.

Puede asegurarse de que cada forma ''rebota'' en el 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 unilaterales, es decir, el objeto A puede chocar (rebotar) con el objeto B, mientras que el objeto B continúa como si nada hubiera pasado. Si desea que dos objetos reboten entre sí, a ambos se les debe pedir que choquen entre sí:

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 contacto en el objeto A con respecto al objeto B. No tiene que configurar la detección de contacto en el objeto B para el objeto A.

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

No necesitamos redcircle.physicsBody?.contactTestBitMask= blueSquareCategory