Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clamping camera around the background of a scene in SpriteKit

so I have a base game setup that can be found at the bitbucket link below:

Game link

I'm currently having a hard time understanding how to translate the camera node in relation to the scene's layout.

The goal is to have the camera follow the player up until hitting a corner boundary defined by the size of the scene. In this particular test scenario setup, the scene is 1000x1000 in size with a camera scale of 1.

The code below is used to modify the position of the camera when a new position is set to follow the character:

    var cameraPosition: CGPoint {

        get {
            return CGPoint(x: camera!.position.x, y: camera!.position.y)
        }

        set {

            let cameraScale = CGFloat(1)
            let sceneScale = CGFloat(1)//CGFloat(1 - 0.44  + 0.05 /*possible rounding error adjustment*/)
//            let viewAspectRatio = CGRectGetWidth(view!.frame)/CGRectGetHeight(view!.frame)
            let newPositionValue = double2(x: Double(newValue.x * sceneScale), y: Double(newValue.y * sceneScale))

            let scaledSceneSize = CGSize(width: size.width * sceneScale , height: size.height * sceneScale)
////            scaledSceneSize.height = scaledSceneSize.height / viewAspectRatio

            let cameraSize = view!.bounds.size
            let scaledCameraSize = CGSize(width: cameraSize.width * cameraScale, height: cameraSize.height * cameraScale)


            let minX = 0//-scaledSceneSize.width * anchorPoint.x + scaledCameraSize.width / 2
            let minY = -219//-scaledSceneSize.height * anchorPoint.y + scaledCameraSize.height / 2

            let minValues = double2(x: Double(minX), y: Double(minY))

            let maxX = 0//(scaledSceneSize.width * anchorPoint.x - scaledCameraSize.width / 2) //size.width - cameraSize.width / 2
            let maxY = 219//(scaledSceneSize.height * anchorPoint.y - scaledCameraSize.height / 2) //- cameraSize.height / 2

            let maxValues = double2(x: Double(maxX), y: Double(maxY))

            let clampedPosition = clamp(newPositionValue, min: minValues, max: maxValues)


            camera!.position = CGPoint(x: (clampedPosition.x / Double(sceneScale)), y: (clampedPosition.y / Double(sceneScale)))
        }

    }

There are hardcore value that currently fit the required scene size, and I'm unsure how to get those results via the scale. The scale by default is:

/* Set the scale mode to scale to fit the window */
            scene.scaleMode = .AspectFill

Without the knowledge of knowing there's a translation in scale, by default, I would expect the boundaries to be largestSceneDimensionXValue - cameraSize.width/2 largestSceneDimensionYValue - cameraSize.height/2

As a high level example. Would anyone be able to assist me in getting this translation?

Overall the scene should look like the following in all corners: goal

VS having the black background overflow in the camera as such:

anti goal

like image 364
TheCodingArt Avatar asked Feb 01 '16 18:02

TheCodingArt


2 Answers

Applications like this are exactly what SKConstraint is for.

You can see a demo of this exact feature — constraining a camera so that it follows the player, but doesn't show too much empty space around the edge of the level — in the WWDC15 session Deeper into GameplayKit with DemoBots.* (The link there should jump to about 7:27 in the talk where discussion of this feature begins.)

The gist of what's in the video, with some snippets from the DemoBots sample code:

  1. Use a distance constraint to keep the camera centered on the player (automatically, without having to set camera.position directly on every update()).

    // Constrain the camera to stay a constant distance of 0 points from the player node.
    let zeroRange = SKRange(constantValue: 0.0)
    let playerBotLocationConstraint = SKConstraint.distance(zeroRange, toNode: playerNode)
    
  2. Use a position constraint to keep the camera within a certain range of the edge of the level. Calculate that range by taking the frame of the level and insetting that rect by the distance the camera should keep from the edge of the level.

    // get the scene size as scaled by `scaleMode = .AspectFill`
    let scaledSize = CGSize(width: size.width * camera.xScale, height: size.height * camera.yScale)
    
    // get the frame of the entire level contents
    let boardNode = childNodeWithName(WorldLayer.Board.nodePath)!
    let boardContentRect = boardNode.calculateAccumulatedFrame()
    
    // inset that frame from the edges of the level
    // inset by `scaledSize / 2 - 100` to show 100 pt of black around the level
    // (no need for `- 100` if you want zero padding)
    // use min() to make sure we don't inset too far if the level is small
    let xInset = min((scaledSize.width / 2) - 100.0, boardContentRect.width / 2)
    let yInset = min((scaledSize.height / 2) - 100.0, boardContentRect.height / 2)
    let insetContentRect = boardContentRect.insetBy(dx: xInset, dy: yInset)
    
    // use the corners of the inset as the X and Y range of a position constraint
    let xRange = SKRange(lowerLimit: insetContentRect.minX, upperLimit: insetContentRect.maxX)
    let yRange = SKRange(lowerLimit: insetContentRect.minY, upperLimit: insetContentRect.maxY)
    let levelEdgeConstraint = SKConstraint.positionX(xRange, y: yRange)
    levelEdgeConstraint.referenceNode = boardNode
    
  3. Apply both constraints to your SKCameraNode.

    camera.constraints = [playerBotLocationConstraint, levelEdgeConstraint]
    

For a deeper look, download Apple's DemoBots sample code project, which has a lot of comments and supporting code that I trimmed from the above snippets to keep this post from getting excessively long. Everything for the camera constraint is in func setCameraConstraints() in LevelScene.swift.

* Despite the session name, it's about a lot more than just GameplayKit... it shows how to leverage many the technologies introduced in iOS 8 / OS X 10.11 / Xcode 7 to build something resembling a full-scale game: App Thinning, new SpriteKit features, ReplayKit, and a lot more.

like image 188
rickster Avatar answered Oct 19 '22 02:10

rickster


I didn't use your code. I made a sample project and got this working.

enter image description here

heres my code

import SpriteKit

class GameScene: SKScene {

    let world = SKSpriteNode(imageNamed: "world.jpg")
    let player = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(10, 10))

    var cam: SKCameraNode!

    override init(size: CGSize) {
        super.init(size: size)
        print(world.size)
        addChild(world)
        addChild(player)

        world.zPosition = 1
        player.zPosition = 2

        cam = SKCameraNode()
        self.camera = cam
        addChild(cam)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        for touch: AnyObject in touches {
            let location = touch.locationInNode(self)

            player.position = location
        }
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        for touch: AnyObject in touches {
            let location = touch.locationInNode(self)

            player.position = location
        }
    }

    func clampCamera(){

        func clamp(inout input: CGFloat, num1: CGFloat, num2: CGFloat) {
            if input < num1 {
                input = num1
            }
            else if input > num2 {
                input = num2
            }
        }

        let lBoundary = -world.size.width/2 + size.width/2
        let rBoundary = world.size.width/2 - size.width/2
        let bBoundary = -world.size.height/2 + size.height/2
        let tBoundary = world.size.height/2 - size.height/2

        clamp(&camera!.position.x, num1: lBoundary, num2: rBoundary)
        clamp(&camera!.position.y, num1: bBoundary, num2: tBoundary)

    }

    override func update(currentTime: NSTimeInterval) {
        camera!.position = player.position
        clampCamera()
    }
}

this is the same image i used as my "world" http://i.imgur.com/XhZbh8q.jpg

like image 26
hamobi Avatar answered Oct 19 '22 03:10

hamobi