Game Development with Swift
上QQ阅读APP看书,第一时间看更新

Centering the camera on a sprite

Games often require that the camera follows the player sprite as it moves through space. We definitely want this camera behavior for Pierre, our penguin character, whom we will soon be adding to the game. Since SpriteKit does not come with built-in camera functionality, we will create our own structure to simulate the effect we want.

One way we could accomplish this is by keeping Pierre in one position and moving every other object past him. This is effective, yet semantically confusing, and can cause errors when you are positioning game objects.

Creating a new world

I prefer to create a world node and attach all of our game nodes to it (instead of directly to the scene). We can move Pierre forward through the world and simply reposition the world node so that Pierre is always at the center of our device's viewport. All of our enemies, power-ups, and structures will be children of the world node, and will appear to move past the screen as we scroll through the world.

Tip

Each sprite node's position is always relative to its direct parent. When you change a node's position, all of its child nodes come along for the ride. This is very handy behavior for simulating our camera.

This diagram illustrates a simplified version of this technique with some made-up numbers:

You can find the code for our camera functionality in the following code block. Read the comments for a detailed explanation. This is just a quick recap of the changes:

  • Our didMoveToView function was becoming too crowded. I broke out our flying bee code into a new function named addTheFlyingBee. Later, we will encapsulate game objects, such as bees, into their own classes.
  • I created two new constants on the GameScene class: the world node and the bee node.
  • I updated the didMoveToView function. It adds the world node to the scene's node tree, and calls the new addTheFlyingBee function.
  • Inside the new bee function, I removed the bee constant, as GameScene now declares it above as its own property.
  • Inside the new bee function, instead of adding the bee node to the scene, with self.addChild(bee), we want to add it to the world, with world.addChild(bee).
  • We are implementing a new function: didSimulatePhysics. SpriteKit calls this function every frame after performing physics calculations and adjusting positions. It is a great place to update our world position. The math to change the world position resides in this new function.

Please update your entire GameScene.swift file to match mine:

import SpriteKit

class GameScene: SKScene {
    // Create the world as a generic SKNode
    let world = SKNode()
    // Create our bee node as a property of GameScene so we can 
    // access it throughout the class
    // (Make sure to remove the old bee declaration inside the 
    // didMoveToView function.)
    let bee = SKSpriteNode()
    
    override func didMoveToView(view: SKView) {
        self.backgroundColor = UIColor(red: 0.4, green: 0.6, blue: 
            0.95, alpha: 1.0)
        
        // Add the world node as a child of the scene
        self.addChild(world)
        // Call the new bee function
        self.addTheFlyingBee()
    }
    
    // I moved all of our bee animation code into a new function:
    func addTheFlyingBee() {
        // Position our bee
        bee.position = CGPoint(x: 250, y: 250)
        bee.size = CGSize(width: 28, height: 24)
        // Notice we now attach our bee node to the world node:
        world.addChild(bee)
        
        /*
            all of the same bee animation code remains here,
            I am excluding it in this text for brevity
        */
    }
    
    // A new function
    override func didSimulatePhysics() {
        // To find the correct position, subtract half of the   
        // scene size from the bee's position, adjusted for any  
        // world scaling.
        // Multiply by -1 and you have the adjustment to keep our 
        // sprite centered:
        let worldXPos = -(bee.position.x * world.xScale - 
            (self.size.width / 2))
        let worldYPos = -(bee.position.y * world.yScale - 
            (self.size.height / 2))
        // Move the world so that the bee is centered in the scene
        world.position = CGPoint(x: worldXPos, y: worldYPos)
    }
    
}

Run the game. You should see our bee stuck directly at the center of the screen, flipping back and forth every two seconds.

The bee is actually changing position, just as before, but the world is compensating to keep the bee centered on the screen. When we add more game objects in Chapter 3, Mix in the Physics, our bee will appear to fly as the entire world pans past the screen.