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:
VS having the black background overflow in the camera as such:
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:
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)
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
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.
I didn't use your code. I made a sample project and got this working.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With