Понимание 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