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