sprite-kit
SKNode botsing
Zoeken…
Opmerkingen
De bepalende factoren voor de botsing van Sprite Kit en de verwerking van contactgebeurtenissen zijn de door u gemaakte relatie-instellingen van categoryBitMask
, collisionBitMask
en contactTestBitMask
voor elk van uw interactieve objecttypen. Door deze rationeel in te stellen ten behoeve van uw gewenste resultaten van contacten en botsingen, bepaalt u welke typen kunnen botsen en informeren over contacten met anderen, en voorkomt u ongewenste overhead van botsingen, contact en fysica.
Voor elk type 'entiteit' kunt u alle drie instellen:
-
categoryBitMask
: een categorie specifiek voor dit type knooppunt -
collisionBitMask
: een collision differentiator, kan van bovenaf verschillen -
contactTestBitMask
: een contactdifferentiator, kan verschillen van beide hierboven
De algemene stappen om botsingen en contacten te implementeren zijn:
- fysieke lichaamsgrootte, vorm en (soms) massa instellen
- voeg de benodigde BitMasks toe voor uw type knooppunt uit categorie, botsing en contact hierboven
- scène instellen als een contactafgevaardigde waarmee deze botsingen en contacten kan controleren en informeren
- contacthandlers en andere relevante logica voor natuurkundige gebeurtenissen implementeren
Schakel Physics World in
// World physics
self.physicsWorld.gravity = CGVectorMake(0, -9.8);
Schakel knooppunt in botsen in
Eerst stellen we de knooppuntcategorie in
let groundBody: UInt32 = 0x1 << 0
let boxBody: UInt32 = 0x1 << 1
Voeg vervolgens het knooppunt Aardtype en het knooppunt Boxtype toe.
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)
Contacten afhandelen
Stel scène in als gemachtigde
//set your scene as SKPhysicsContactDelegate
class yourScene: SKScene, SKPhysicsContactDelegate
self.physicsWorld.contactDelegate = self;
Vervolgens moet u een van de contactfuncties implementeren: optionele func didBegin (contact :) en / of optionele fonds didEnd (contact :) methode om uw contactlogica in te vullen, bijvoorbeeld
//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)
}
}
Alternatieve didBeginContact
ALS je eenvoudige categorieën gebruikt, waarbij elke fysica-instantie tot slechts één categorie behoort, dan is deze alternatieve vorm van didBeginContact misschien beter leesbaar:
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")
}
}
Eenvoudig Sprite Kit-project met botsingen, contacten en aanraakgebeurtenissen.
Hier is een eenvoudige Sprite-Kit GameScene.swift. Maak een nieuw, leeg SpriteKit-project en vervang de GameScene.swift hiermee. Bouw en ren dan.
Klik op een van de objecten op het scherm om ze te laten bewegen. Controleer de logboeken en de opmerkingen om te zien welke botsen en welke contact maken.
//
// 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)")}
}
}
}
}
}
Alternatief voor contactafhandeling bij het omgaan met sprites met meerdere categorieën
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:()
}
Verschil tussen contacten en botsingen
In Sprite-Kit is er het concept van botsingen dat verwijst naar de SK physics-engine die handelt hoe fysica-objecten op elkaar inwerken wanneer ze botsen, dwz welke tegen welke andere botsen.
Het heeft ook het concept van contacten , dat is het mechanisme waarmee uw programma wordt geïnformeerd wanneer twee fysische objecten elkaar kruisen.
Objecten kunnen botsen maar geen contacten genereren, contacten genereren zonder in botsing te komen, of botsen en een contact genereren (of geen van beide en helemaal niet communiceren)
Botsingen kunnen ook eenzijdig zijn, dat wil zeggen object A kan botsen op object B, terwijl object B doorgaat alsof er niets is gebeurd. Als u wilt dat 2 objecten van elkaar stuiteren, moeten beide worden verteld dat ze tegen elkaar moeten botsen.
Contacten zijn echter niet eenzijdig; als u wilt weten wanneer object A aangeraakt (gecontacteerd) object B, volstaat het om contactdetectie in te stellen op object A met betrekking tot object B. U hoeft geen contactdetectie in te stellen op object B voor object A.
Contact manipuleren Test en test bitmaskers om specifiek contact en botsingen in / uit te schakelen.
Voor dit voorbeeld zullen we 4 lichamen gebruiken en zullen voor de eenvoud alleen de laatste 8 bits van de bitmaskers tonen. De 4 lichamen zijn 3 SKSpriteNodes, elk met een fysicalichaam en een grens:
let edge = frame.insetBy(dx: 0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: edge)
Merk op dat het 'rand'-fysicalichaam het fysicalichaam van de scène is, geen knooppunt.
We definiëren 4 unieke categorieën
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
Aan elk natuurkundelichaam worden de categorieën toegewezen waartoe het behoort:
//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
Als een beetje in de botsing van een lichaam BitMask is ingesteld op 1, dan botst (stuitert) elk lichaam dat een '1' heeft in dezelfde positie in zijn categorie BitMask. Evenzo voor contactTestBitMask.
Tenzij u anders aangeeft, komt alles in botsing met al het andere en worden er geen contacten gegenereerd (uw code wordt niet op de hoogte gebracht wanneer iets anders contact maakt):
purpleSquare.physicsBody.collisonBitMask = 11111111111111111111111111111111 // 32 '1's.
Elk bit in elke positie is '1', dus in vergelijking met elke andere categorie BitMask zal Sprite Kit een '1' vinden zodat een botsing zal optreden. Als u niet wilt dat dit lichaam tegen een bepaalde categorie botst, moet u het juiste bit in de collisonBitMask op '0' zetten
en zijn contactTestbitMask
is ingesteld op alle 0
s:
redCircle.physicsBody.contactTestBitMask = 00000000000000000000000000000000 // 32 '0's
Hetzelfde als voor collisionBitMask, behalve omgekeerd.
Contacten of botsingen tussen lichamen kunnen worden uitgeschakeld (bestaande contacten of botsingen ongewijzigd laten) met behulp van:
nodeA.physicsBody?.collisionBitMask &= ~nodeB.category
We logisch EN nodeA's collision bit mask met de inverse (logisch NIET, de ~ operator) van nodeB's categorie bitmasker om bitMask van dat bit nodeA te 'uitschakelen'. bijv. om te voorkomen dat de rode cirkel op het paarse vierkant botst:
redCircle.physicsBody?.collisionBitMask = redCircle.physicsBody?.collisionBitMask & ~purpleSquareCategory
die kan worden ingekort tot:
redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory
Uitleg:
redCircle.physicsBody.collisonBitMask = 11111111111111111111111111111111
purpleSquareCategory = 00000000000000000000000000000001
~purpleSquareCategory = 11111111111111111111111111111110
11111111111111111111111111111111 & 11111111111111111111111111111110 = 11111111111111111111111111111110
redCircle.physicsBody.collisonBitMask now equals 11111111111111111111111111111110
redCircle botst niet langer met lichamen met een categorie van ... 0001 (purpleSquare)
In plaats van afzonderlijke bits in de collsionsbitMask uit te schakelen, kunt u deze rechtstreeks instellen:
blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory)
ie blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)
welke gelijk is aan blueSquare.physicsBody?.collisionBitMask = ....00000011
blueSquare zal alleen botsen met lichamen met een categorie of ..01 of ..10
Contacten of botsingen tussen 2 lichamen kunnen worden ingeschakeld (zonder dat bestaande contacten of botsingen) op elk punt met:
redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory
We logisch EN redCircle's bitMask met purpleSquare's categorie bitmasker om dat bit in te schakelen in redcircle's bitMask. Dit laat alle andere bits in de bitMas van redCircel onverlet.
Je kunt ervoor zorgen dat elke vorm als volgt een schermrand 'stuitert':
// Make sure everything collides with the screen edge
enumerateChildNodes(withName: "//*") { node, _ in
node.physicsBody?.collisionBitMask |= self.edgeCategory //Add edgeCategory to the collision bit mask
}
Notitie:
Botsingen kunnen eenzijdig zijn, dwz object A kan botsen op object B, terwijl object B doorgaat alsof er niets is gebeurd. Als u wilt dat 2 objecten van elkaar stuiteren, moeten beide worden verteld dat ze tegen elkaar moeten botsen:
blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory
Contacten zijn echter niet eenzijdig; als u wilt weten wanneer object A aangeraakt (gecontacteerd) object B, volstaat het om contactdetectie in te stellen op object A met betrekking tot object B. U hoeft geen contactdetectie in te stellen op object B voor object A.
blueSquare.physicsBody?.contactTestBitMask = redCircleCategory
We hebben geen redcircle.physicsBody?.contactTestBitMask= blueSquareCategory
nodig redcircle.physicsBody?.contactTestBitMask= blueSquareCategory
Geavanceerd gebruik:
Hier niet behandeld, maar fysische lichamen kunnen tot meer dan één categorie behoren. We kunnen ons spel bijvoorbeeld als volgt instellen:
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
Aan elk natuurkundelichaam worden de categorieën toegewezen waartoe het behoort:
//Assign our category bit masks to our physics bodies
purpleSquare.physicsBody?.categoryBitMask = squareCategory | purpleCategory
redCircle.physicsBody?.categoryBitMask = circleCategory | redCategory
blueSquare.physicsBody?.categoryBitMask = squareCategory | blueCategory
hun categorybitMasks zijn nu:
purpleSquare.physicsBody?.categoryBitMask = ...00010001
redCircle.physicsBody?.categoryBitMask = ...00001010
blueSquare.physicsBody?.categoryBitMask = ...00000101
Dit heeft invloed op de manier waarop u de bitvelden manipuleert. Het kan handig zijn (bijvoorbeeld) om aan te geven dat een natuurkundig lichaam (bijvoorbeeld een bom) op de een of andere manier is veranderd (bijvoorbeeld dat het de 'super'-vaardigheid heeft verkregen die een andere categorie is, en je zou kunnen controleren dat een bepaald object (een buitenaardse moeder