I don't know if this is a weird bug in Xcode or there's something about the SpriteKit's coordinate system I don't understand.
The premise is that the position of a node is always relative to its parent. However, whenever I call a block that creates and positions a node with a physics body from "didBeginContact" of SKPhysicsContactDelegate, the node is always positioned relative to the scene (instead of its parent). Note that calling this same block works as intended when triggered anywhere but "didBeginContact". Another thing is that, if I remove the physics body of the said node, the block will now work as intended even when called from "didBeginContact".
I've been stuck with this issue for two days and it would be too draggy to give other details about my actual code. So I've made a very simple project demonstrating this anomaly. Just create a new project in Xcode 6 with Spritekit Template, and replace GameViewController.swift and GameSwift.swift with the codes posted below. Just run in iPad Air and everything else should be self explanatory.
Final Notes:
- As you press the first button and makes contact to the second button, look at the lower left corner of the screen. You will see that the boxes are being "wrongly" positioned there.
- Try to remove the physics body of the box in "AddBox". It will now work as intended.
- Please let me know if you think this is a bug or there's something in the coordinates system or physics world that I don't understand
GameViewController.swift:
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene()
scene.size = view.frame.size
let skView = self.view as SKView
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
GameScene.swift:
import SpriteKit
let kButtonSize = CGSize(width: 500, height: 100)
let kContainerSize = CGSize(width: 500, height: 300)
let kBoxSize = CGSize(width: 25, height: 25)
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
addFirstButton()
addSecondButton()
addContainer()
}
func addFirstButton() {
let button = SKSpriteNode(color: SKColor.blueColor(), size: kButtonSize)
let label = SKLabelNode(text: "Call 'addBox' from didBeginContact")
button.name = "firstButton"
label.name = "firstLabel"
button.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
button.physicsBody = SKPhysicsBody(rectangleOfSize: button.size)
button.physicsBody.allowsRotation = false
button.physicsBody.affectedByGravity = false
button.physicsBody.categoryBitMask = 0x1
button.physicsBody.contactTestBitMask = 0x1
button.addChild(label)
addChild(button)
}
func addSecondButton() {
let button = SKSpriteNode(color: SKColor.blueColor(), size: kButtonSize)
let label = SKLabelNode(text: "Call 'addBox' from touchesBegan")
button.name = "secondButton"
label.name = "secondLabel"
button.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)-200)
button.physicsBody = SKPhysicsBody(rectangleOfSize: button.size)
button.physicsBody.dynamic = false
button.physicsBody.categoryBitMask = 0x1
button.physicsBody.contactTestBitMask = 0x1
button.addChild(label)
addChild(button)
}
func addContainer() {
let container = SKSpriteNode(color: SKColor.greenColor(), size:kContainerSize)
let label = SKLabelNode(text: "Created node should fall here")
label.fontColor = SKColor.blackColor()
container.name = "container"
container.physicsBody = SKPhysicsBody(edgeLoopFromRect: container.frame)
container.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)+300)
container.addChild(label)
addChild(container)
}
func addBox() {
let container = childNodeWithName("container")
let box = SKSpriteNode(color: SKColor.blueColor(), size: kBoxSize)
box.physicsBody = SKPhysicsBody(rectangleOfSize: box.size)
box.position = CGPointMake(0, 100)
container.addChild(box)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let point = touch.locationInNode(self)
let node = nodeAtPoint(point)
if node.name == nil {return}
switch node.name! {
case "firstButton", "firstLabel":
let button = childNodeWithName("firstButton") as SKSpriteNode
button.physicsBody.applyImpulse(CGVectorMake(0, -500))
case "secondButton", "secondLabel":
addBox()
default:
break
}
}
func didBeginContact(contact: SKPhysicsContact!) {
addBox()
}
}