I don’t know if this is a strange error in Xcode or something about the SpriteKit 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 physical body from SKPhysicsContactDelegate's “didBeginContact” body, the node is always positioned relative to the scene (instead of its parent). Note that calling the same block works as intended when it starts anywhere, but with "didBeginContact". Another thing is that if I remove the physical body of the mentioned node, the block will now work as intended, even when called from "didBeginContact".
I am stuck on this issue for two days and it will be too drag and drop to give other details about my actual code. Therefore, I made a very simple project demonstrating this anomaly. Just create a new project in Xcode 6 with the Spritekit template and replace GameViewController.swift and GameSwift.swift with the codes below. Just launch the iPad Air and everything else should be clear.
Concluding remarks:
- When you press the first button and touch the second, look in the lower left corner of the screen. You will see that the boxes are "wrong" located there.
- Try removing the physical body of the window in the "AddBox". Now he will work as intended.
- Please let me know if you think this is a mistake or something in the coordinate system or in the physical 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()
}
}