sprite-kit
Столкновение SKNode
Поиск…
замечания
Детерминантами столкновения Sprite Kit и обработки контактных событий являются созданные вами настройки отношений categoryBitMask
, collisionBitMask
и contactTestBitMask
для каждого из ваших типов взаимодействующих объектов. Рационально устанавливая их для обслуживания ваших желаемых результатов от контактов и столкновений, вы определяете, какие типы могут сталкиваться и сообщать о контактах с другими, и избегать нежелательных столкновений, контактов и обработки физических данных.
Для каждого типа «сущности» вы можете установить все три:
-
categoryBitMask
: категория, специфичная для этого типа узла -
collisionBitMask
: дифференциал столкновения, может отличаться от предыдущего -
contactTestBitMask
: контактный дифференциатор, может отличаться от обоих вышеперечисленных
Общие шаги по внедрению коллизий и контактов:
- установить физический размер тела, форму и (иногда) массу
- добавьте необходимые BitMasks для вашего типа узла из категории, столкновение и контакт выше
- установить сцену в качестве делегата-контакта, позволяющего ей проверять и сообщать о столкновениях и контактах
- применять обработчики контактов и любую другую соответствующую логику для физических событий
Включить физический мир
// World physics
self.physicsWorld.gravity = CGVectorMake(0, -9.8);
Включить узел для параллелизма
Во-первых, мы устанавливаем категорию узлов
let groundBody: UInt32 = 0x1 << 0
let boxBody: UInt32 = 0x1 << 1
Затем добавьте узел типа Ground и узел типа Box.
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)
Ручные контакты
Установить сцену в качестве делегата
//set your scene as SKPhysicsContactDelegate
class yourScene: SKScene, SKPhysicsContactDelegate
self.physicsWorld.contactDelegate = self;
Затем вам необходимо реализовать одну или другую из контактных функций: необязательный func сделалBegin (контакт :) и / или дополнительный фонд didEnd (контакт :) метод для заполнения вашей контактной логики, например, как
//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)
}
}
Альтернатива didBeginContact
ЕСЛИ вы используете простые категории, причем каждое физическое тело относится к одной категории, то эта альтернативная форма didBeginContact может быть более читаемой:
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 Sprite Kit показывает столкновения, контакты и события касания.
Вот простой Sprite-Kit GameScene.swift. Создайте новый, пустой проект SpriteKit и замените GameScene.swift на это. Затем создайте и запустите.
Нажмите на любой из объектов на экране, чтобы заставить их двигаться. Проверьте журналы и комментарии, чтобы увидеть, какие из них сталкиваются и какие из них вступают в контакт.
//
// 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)")}
}
}
}
}
}
Альтернатива обращению с контактом при использовании спрайтов с несколькими категориями
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:()
}
Разница между контактами и столкновениями
В Sprite-Kit существует концепция столкновений, которая относится к физическому движку SK, взаимодействующему с физическими объектами, когда они сталкиваются, т.е. какие из них отскакивают от других.
В нем также есть концепция контактов , которая является механизмом, с помощью которого ваша программа получает информацию, когда пересекаются 2 объекта физики.
Объекты могут сталкиваться, но не создавать контакты, генерировать контакты без столкновения, или сталкиваться и создавать контакт (или вообще не взаимодействовать и не взаимодействовать)
Столкновения также могут быть односторонними, т. Е. Объект A может столкнуться (отскакивать) от объекта B, в то время как объект B продолжается, как будто ничего не произошло. Если вы хотите, чтобы 2 объекта отскакивали друг от друга, им нужно было сказать, что они сталкиваются с другим.
Однако контакты не являются односторонними; если вы хотите знать, когда объект A касался (связался) объекта B, достаточно настроить обнаружение контакта на объекте A относительно объекта B. Вам не нужно настраивать обнаружение контакта на объекте B для объекта A.
Манипулирование битовых масок contactTest и collison для включения / выключения определенных контактов и коллизий.
В этом примере мы будем использовать 4 тела и для простоты будем показывать только последние 8 бит бит-масок. 4 тела - это 3 SKSpriteNodes, каждый из которых имеет физическое тело и границу:
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», поэтому по сравнению с любой другой категорией категорий, Sprite Kit найдет «1», поэтому произойдет столкновение. Если вы не хотите, чтобы это тело сталкивалось с определенной категорией, вам нужно установить правильный бит в collisonBitMask на «0»,
и его contactTestbitMask
устанавливается на все 0
s:
redCircle.physicsBody.contactTestBitMask = 00000000000000000000000000000000 // 32 '0's
То же, что и для collisionBitMask, кроме обратного.
Контакты или столкновения между органами могут быть отключены (оставляя существующий контакт или столкновение без изменений), используя:
nodeA.physicsBody?.collisionBitMask &= ~nodeB.category
Мы логически сбиваем битовую матрицу AND nodeA с инверсным (логическим NOT, оператором ~) битовой маской категории nodeB, чтобы «отключить» бит bitaskask бит bitA. например, чтобы остановить красный круг от столкновения с фиолетовым квадратом:
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 now equals 11111111111111111111111111111110
redCircle больше не сталкивается с телами с категорией .... 0001 (purpleSquare)
Вместо того, чтобы отключать отдельные биты в команде collsionsbitMask, вы можете установить его напрямую:
blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory)
т.е. blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)
который равен blueSquare.physicsBody?.collisionBitMask = ....00000011
blueSquare будет сталкиваться только с телами с категорией или ..01 или ..10
Контакты или столкновение между 2 телами могут быть включены (не затрагивая существующие контакты или столкновение) в любой точке с помощью:
redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory
Мы логически и бит-маска redCircle с битовой маской категории purpleSquare, чтобы «включить» этот бит в битовой маске redcircle. Это оставляет любые другие бит в redMircel's bitMas незатронутыми.
Вы можете убедиться, что каждая форма «отскакивает» от края экрана следующим образом:
// 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 продолжается, как будто ничего не произошло. Если вы хотите, чтобы 2 объекта отскакивали друг от друга, им должно быть сказано, чтобы они столкнулись с другим:
blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory
Однако контакты не являются односторонними; если вы хотите знать, когда объект A касался (связался) объекта B, достаточно настроить обнаружение контакта на объекте A относительно объекта B. Вам не нужно настраивать обнаружение контакта на объекте B для объекта A.
blueSquare.physicsBody?.contactTestBitMask = redCircleCategory
Нам не нужен redcircle.physicsBody?.contactTestBitMask= blueSquareCategory
Расширенное использование:
Здесь не рассматриваются, но физические тела могут принадлежать более чем одной категории. Например, мы можем настроить игру следующим образом:
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
Каждому физическому телу присваиваются категории, к которым он принадлежит:
//Assign our category bit masks to our physics bodies
purpleSquare.physicsBody?.categoryBitMask = squareCategory | purpleCategory
redCircle.physicsBody?.categoryBitMask = circleCategory | redCategory
blueSquare.physicsBody?.categoryBitMask = squareCategory | blueCategory
их categorybitMasks теперь:
purpleSquare.physicsBody?.categoryBitMask = ...00010001
redCircle.physicsBody?.categoryBitMask = ...00001010
blueSquare.physicsBody?.categoryBitMask = ...00000101
Это повлияет на то, как вы манипулируете полями бит. Может быть полезно (например) указать, что физическое тело (например, бомба) каким-то образом изменилось (например, оно могло бы получить «супер» способность, которая является другой категорией, и вы можете проверить, что определенный объект (инопланетная мать