Понимание SpriteKit CollisionBitMask

Я учусь пользоваться SpriteKit и я следую учебнику для столкновений. Я изо всех сил пытаюсь понять следующий код:

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
}

Почему мы назначаем эти вещи под названием bitMaps и как они работают позже в коде ниже?

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)

Спасибо!

2 ответа

Битовые маски - это флаги, используемые для описания элемента в двоичном формате.

так что представьте, у вас есть 8 способов что-то описать. (В спрайтеките у вас 32)

Мы можем поместить эти 8 элементов в один байт, так как 8 бит находятся в байте, что позволяет нам экономить место и быстрее выполнять операции.

Вот пример 8 описаний

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

Теперь у меня есть лучник и хочу классифицировать его. Я хочу сказать, что он живой дружелюбный юнит

Я бы использовал categoryBitmask классифицировать его:

archer.categoryBitmask = Ranged

Это будет представлено в 1 байте как

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

Теперь давайте скажем, что его стрелы являются огненными стрелами, я бы классифицировал это так:

arrow.categoryBitmask = Burning

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

и, наконец, у нас есть зомби, которого можно поразить и со временем восстановить.

zombie.categoryBitmask = Attackable + Undead + Regenerate

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

Теперь я хочу, чтобы моя стрела только попала Attackable спрайты (в данном случае зомби)

Я бы установил contactTestBitmask сказать стрелке, что он может ударить

arrow.contactTestBitmask = Attackable 00000001

Теперь нам нужно проверить, когда стрелка попадает в зомби, это где didBeginContact приходит в

Какие didBeginContact будет делать, это проверить contactTestBitmask движущегося элемента к categoryBitmask что он попадает с помощью операции AND, чтобы найти совпадение

В нашем случае

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

Поскольку наше значение> 0, контакт был успешным.

Это значит, что Беджинс уволен.

Теперь, когда мы находимся в didBegins, нам нужно определить, какое физическое тело является нашей стрелой, а какое физическое тело является нашим зомби.

это где следующее заявление приходит

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
}

Поскольку стрелка = 00100000 и зомби = 00010101, мы знаем, что зомби имеет меньшее значение, чем стрелка, поэтому в этом случае зомби равен <стрелка.

Мы назначаем firstBody зомби, и secondBody стрелять

Теперь нам нужно предоставить условие.

Мы хотим сказать, что если существо нежити подвергается воздействию горящего объекта, сделайте что-нибудь.

Так что в коде это будет

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

Но что, если стрела была ледяной стрелой? Мы не хотим вдаваться в это заявление.

Что ж, теперь мы можем добавить второе условие, которое позволит нам заморозить зомби.

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

То, что делают эти if, - это убедиться, что в теле включены определенные функции, а затем выполнить какое-то действие в ответ на них.

Чтобы узнать больше о том, как работают битовые маски, я бы исследовал, как создавать таблицы истинности. По сути, это то, к чему это сводится. Мы просто создаем несколько таблиц истинности и пытаемся выяснить, является ли утверждение истинным, и если оно истинно, выполнить действие.

Управление битовыми масками contactTest и collison для включения / отключения определенных контактов и коллизий.

Для этого примера мы будем использовать 4 тела и покажем только последние 8 бит битовых масок для простоты. 4 тела - это 3 узла SKSpriteNode (каждое с физическим телом) и граница:

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

Обратите внимание, что физическое тело "ребра" - это физическое тело сцены, а не узел.

Мы определяем 4 уникальные категории

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

Каждому физическому телу присваиваются категории, к которым он принадлежит:

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

Если бит в collisionBitMask тела установлен в 1, то он сталкивается (отскакивает) от любого тела, у которого '1' находится в той же позиции в его категории BitMask. Аналогично для contactTestBitMask.

Если не указано иное, все сталкивается со всем остальным, и контакты не генерируются (ваш код не будет уведомлен, когда что-либо связывается с чем-либо еще):

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

Каждый бит в каждой позиции равен "1", поэтому по сравнению с любой другой категорией "BitMask" Sprite Kit найдет "1", поэтому произойдет столкновение. Если вы не хотите, чтобы это тело сталкивалось с определенной категорией, вам нужно будет установить правильный бит в collisonBitMask на "0"

и его contactTestbitMask устанавливается на все 0:

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

То же, что и для collisionBitMask, за исключением обратного.

Контакты или столкновения между телами можно отключить (оставив существующий контакт или столкновение без изменений), используя:

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

Логически мы используем битовую маску коллизий AND узла A с помощью обратной (логический оператор NOT, оператор ~) битовой маски категории nodeB, чтобы "отключить" битовую маску этого узла A. например, чтобы красный круг не сталкивался с фиолетовым квадратом:

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

который можно сократить до:

redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory

Объяснение:

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

redCircle.physicsBody.collisonBitMask теперь равен 11111111111111111111111111111110 redCircle больше не сталкивается с телами с категорией....0001 (purpleSquare)

Вместо того, чтобы отключать отдельные биты в collsionsbitMask, вы можете установить его напрямую:

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

что равно blueSquare.physicsBody?.collisionBitMask = ....00000011

BlueSquare будет сталкиваться только с телами с категорией или..01 или..10

Контакты или столкновения между двумя телами можно включить (не затрагивая существующие контакты или столкновения) в любой точке, используя:

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

Мы логически И redMirk bitMask с битовой маской категории purpleSquare, чтобы "включить" этот бит в bitMask redcircle. Это не затрагивает любые другие биты в bitMas от RedCircel.

Вы можете убедиться, что каждая фигура "отскакивает" от края экрана следующим образом:

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

Замечания:

Столкновения могут быть односторонними, то есть объект A может столкнуться (отскочить) от объекта B, тогда как объект B продолжает действовать так, как будто ничего не произошло. Если вы хотите, чтобы два объекта отскакивали друг от друга, им обоим нужно сказать, чтобы они сталкивались друг с другом:

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

Однако контакты не являются односторонними; если вы хотите узнать, когда объект A коснулся (связался) объекта B, достаточно настроить обнаружение контакта на объекте A в отношении объекта B. Вам не нужно настраивать обнаружение контакта на объекте B для объекта A.

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

Нам не нужны redcircle.physicsBody?.contactTestBitMask= blueSquareCategory

Другие вопросы по тегам