Brandon Tomblinson
Brandon Tomblinson

Reputation: 316

Keep an SKNode from leaving the view

I am working on a "Space Invaders" game, I have implemented CMMotionManager to move my heroShip on tilt(the game only runs in landscape), but for some reason my ship will move out of the view even though

self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)

should have prevented this by making a physics edge. Any ideas why?

   import SpriteKit
    import CoreMotion

class GameScene: SKScene,SKPhysicsContactDelegate{
    let collisionBulletCategory: UInt32  = 0x1 << 0
    let collisionHeroCategory: UInt32    = 0x1 << 1
    let background = SKSpriteNode(imageNamed: "background")
    let heroShip = SKSpriteNode(imageNamed: "heroShip")
    let enemyShip = SKSpriteNode(imageNamed: "enemyShip")
    let MotionManager = CMMotionManager()



    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        self.physicsWorld.contactDelegate = self

        self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
        background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
        heroShip.anchorPoint = CGPointMake(1.0, 0.5)
        heroShip.physicsBody?.mass = 0.02
        heroShip.physicsBody?.dynamic = true
        heroShip.physicsBody = SKPhysicsBody(rectangleOfSize: heroShip.size)
        heroShip.physicsBody?.affectedByGravity = false
        heroShip.physicsBody?.categoryBitMask = collisionHeroCategory
        heroShip.physicsBody?.contactTestBitMask = collisionBulletCategory
        heroShip.physicsBody?.collisionBitMask = 0x0
        heroShip.position =  CGPointMake(self.size.width/6.0, self.size.height/2.0)
        enemyShip.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
        enemyShip.zPosition = 1.0
        self.heroShip.zPosition = 1.0
        self.addChild(enemyShip)
        self.addChild(background)
        self.addChild(heroShip)

        if MotionManager.accelerometerAvailable{
            MotionManager.startAccelerometerUpdates()
        }

    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let bullet = SKSpriteNode(imageNamed: "bullet")
        bullet.position = CGPointMake(heroShip.position.x, heroShip.position.y)
        bullet.zPosition = 1.0
        // Add physics body for collision detection
        bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.frame.size)
        bullet.physicsBody?.dynamic = true
        bullet.physicsBody?.affectedByGravity = false
        bullet.physicsBody?.categoryBitMask = collisionBulletCategory
        bullet.physicsBody?.contactTestBitMask = collisionHeroCategory
        bullet.physicsBody?.collisionBitMask = 0x0;
        let action = SKAction.moveToX(CGRectGetMaxX(self.frame) + bullet.size.width, duration: 0.75)
        self.addChild(bullet)
        bullet.runAction(action, completion: {
            bullet.removeAllActions()
            bullet.removeFromParent()
        })
    }

    func didBeginContact(contact: SKPhysicsContact) {

    }
    override func update(currentTime: CFTimeInterval) {
        let data = MotionManager.accelerometerData
        if data?.acceleration.x == nil{
            print("nil")
        }
        else if fabs((data?.acceleration.x)!) > 0.2 {
            self.heroShip.physicsBody?.applyForce(CGVectorMake(0.0, CGFloat(50 * (data?.acceleration.x)!)))

        }

    }
}

Upvotes: 2

Views: 122

Answers (2)

Gliderman
Gliderman

Reputation: 1205

Adding to Whirlwind's answer, you probably also want to enable usesPreciseCollisionDetection on your physics body. By setting this to true, this will prevent your heroShip from popping through the wall if it encounters a massive force from the accelerometer data.

Basically, the physics is calculated as where it is going to be. So if there is a massive force, it could say, "Oh, this massive force means I should be way over here outside the bounding box." If you have precise collision detection enabled, this will force it to calculate physics for multiple positions from the start position of this frame, to the calculated position. If it hits something before getting to the calculated position, then it adjusts so that it doesn't go through whatever it hit. For the most part, you don't have to worry about this, but it is better to be safe than sorry.

So put this line somewhere in your physics body configuration code after initialization of it:

heroShip.physicsBody?.usesPreciseCollisionDetection = true

Upvotes: 0

Whirlwind
Whirlwind

Reputation: 13665

According to the docs, this is how collision happen:

When two physics bodies contact each other, a collision may occur. This body’s collision mask is compared to the other body’s category mask by performing a logical AND operation. If the result is a nonzero value, this body is affected by the collision. Each body independently chooses whether it wants to be affected by the other body. For example, you might use this to avoid collision calculations that would make negligible changes to a body’s velocity.

You have set player's collisionBitMask to something like 0x00000000 (all bits cleared). By default a categoryBitMask is set to 0xFFFFFFFF (all bits set). Because you haven't stated otherwise, scene's physics body categoryBitMask is set to 0xFFFFFFFF. When logical AND (&) operator is performed between those two, a result will be zero, means no collision.

To fix this, you can just remove this line:

 heroShip.physicsBody?.collisionBitMask = 0x0

Or set it properly by defining a Wall category...

HINTS:

  • Anchor point defines how texture is drawn relative to the node's position. It has nothing with physics body. Physics body, by default is centered to the node's position. If you turn-on physics visual representation (skView.showsPhysics = true) you will see, in your example, that physics body is not positioned as it should.

  • Setting up physics body before you have actually initialized it make no sense. You have to initialize physics body first.

Upvotes: 4

Related Questions