Change an SKScene centerpoint - swift

I am working with a simple 2d game and I am trying to switch from level 1 (scene one) to level 2 (scene two). For the sake of testing, both levels contain the same content. This is my code for transitioning:
let transition = SKTransition.push(with: SKTransitionDirection.left, duration: 1)
let nextScene = Scene(size: levelBuilder.scene.size)
nextScene.scaleMode = .aspectFill
nextScene.backgroundColor = UIColor(red:0.17, green:0.24, blue:0.31, alpha:1.0)
levelBuilder.scene.view?.presentScene(nextScene, transition: transition)
nextScene.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
nextScene.physicsWorld.contactDelegate = levelBuilder.scene.physicsWorld.contactDelegate
loadLevelContents(level: 2, world: 1, borderPercentage: 30)
This works perfectly, but the issue is not the transitioning but the loading of nodes into scene two which happens here:
loadLevelContents(level: 2, world: 1, borderPercentage: 30)
The positioning goes fine, but the problem is that everything is based according to the center point, or relative (0,0), being in the middle of the screen.
Scene 2 has the center point at the lower left corner
While I need it to be like scene 1, where it is in the middle of the screen:
How can I change this relative point or center point, not sure what it is called, to the middle of the screen for all SKScenes

The point that you are looking for is called anchorPoint. In Spritekit lower left is 0,0 and upper right is 1, 1. So to put it in the center you would have to set and achorPoint of 0.5,0.5 in the didMove func
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
For SKScenes the default value is (0,0), but it's not just SKScenes that have anchorPoints, SKSpriteNodes have it as well.

Related

Aligning ARFaceAnchor with SpriteKit overlay

I'm trying to calculate SpriteKit overlay content position (not just overlaying visual content) over specific geometry points ARFaceGeometry/ARFaceAnchor.
I'm using SCNSceneRenderer.projectPoint from the calculated world coordinate, but the result is y inverted and not aligned to the camera image:
let vertex4 = vector_float4(0, 0, 0, 1)
let modelMatrix = faceAnchor.transform
let world_vertex4 = simd_mul(modelMatrix, vertex4)
let pt3 = SCNVector3(x: Float(world_vertex4.x),
y: Float(world_vertex4.y),
z: Float(world_vertex4.z))
let sprite_pt = renderer.projectPoint(pt3)
// To visualize sprite_pt
let dot = SKSpriteNode(imageNamed: "dot")
dot.size = CGSize(width: 7, height: 7)
dot.position = CGPoint(x: CGFloat(sprite_pt.x),
y: CGFloat(sprite_pt.y))
overlayScene.addChild(dot)
In my experience, the screen coordinates given by ARKit's projectPoint function are directly usable when drawing to, for example, a CALayer. This means they follow iOS coordinates as described here, where the origin is in the upper left and y is inverted.
SpriteKit has its own coordinate system:
The unit coordinate system places the origin at the bottom left corner of the frame and (1,1) at the top right corner of the frame. A sprite’s anchor point defaults to (0.5,0.5), which corresponds to the center of the frame.
Finally, SKNodes are placed in an SKScene which has its origin on the bottom left. You should ensure that your SKScene is the same size as your actual view, or else the origin may not be at the bottom left of the view and thus your positioning of the node from view coordinates my be incorrect. The answer to this question may help, in particular checking the AspectFit or AspectFill of your view to ensure your scene is being scaled down.
The Scene's origin is in the bottom left and depending on your scene size and scaling it may be off screen. This is where 0,0 is. So every child you add will start there and work its way right and up based on position. A SKSpriteNode has its origin in the center.
So the two basic steps to convert from view coordinates and SpriteKit coordinates would be 1) inverting the y-axis so your origin is in the bottom left, and 2) ensuring that your SKScene frame matches your view frame.
I can test this out more fully in a bit and edit if there are any issues
Found the transformation that works using camera.projectPoint instead of the renderer.projectPoint.
To scale the points correctly on the spritekit: set scaleMode=.aspectFill
I updated https://github.com/AnsonT/ARFaceSpriteKitMapping to demo this.
guard let faceAnchor = anchor as? ARFaceAnchor,
let camera = sceneView.session.currentFrame?.camera,
let sie = overlayScene?.size
else { return }
let modelMatrix = faceAnchor.transform
let vertices = faceAnchor.geometry.vertices
for vertex in vertices {
let vertex4 = vector_float4(vertex.x, vertex.y, vertex.z, 1)
let world_vertex4 = simd_mul(modelMatrix, vertex4)
let world_vector3 = simd_float3(x: world_vertex4.x, y: world_vertex4.y, z: world_vertex4.z)
let pt = camera.projectPoint(world_vector3, orientation: .portrait, viewportSize: size)
let dot = SKSpriteNode(imageNamed: "dot")
dot.size = CGSize(width: 7, height: 7)
dot.position = CGPoint(x: CGFloat(pt.x), y: size.height - CGFloat(pt.y))
overlayScene?.addChild(dot)
}

SpriteKit coordinates messed up

I am trying to add sprites to my SKScene, but the coordinate system seems to be weird. The [0, 0] point is in the middle of the screen and not in the lower left corner like it should be. I have no idea how this could have happened as I have tried correcting using a variety of methods I have found in similar questions.
Here is my GameViewController class:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = MenuScene(fileNamed: "MenuScene"){
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
I have tried changed the scaleMode which only changed the scale of sprites and didn't fix the issue. I have also tried changing MenuScene(fileNamed: ) to MenuScene(size: view.bounds.size) which gave me errors. If I am missing any details, please ask.
That is happening because by default, SKScenes have an anchorPoint of (0.5, 0.5). This places the point (0,0) in the centre. If you're looking to change the anchorPoint to the bottom-left, add this to the beginning of didMoveToView:
self.anchorPoint = CGPoint(x: 0, y: 0)
Or initialize the scene with that anchorPoint before presenting it (from GameViewController):
scene.anchorPoint = CGPoint(x: 0, y: 0)
Or change it in the scene editor.
FYI: CGPoint(x: 0, y: 0) is the same as CGPoint.zero
Personally, I find it easier to build the scene outwards from the centre because it makes symmetry easier and laying out HUD easier, instead of from a corner, which results in building the scene from the corner, potentially creating difficulties.

SpriteKit - Nodes not adding to SKCameraNode - Swift

Im trying to pin the game pad controller to the bottom left on my camera node but when i add the node as a child of my camera it doesnt show up?
let gameCamera = SKCameraNode()
var joypadBackground : SKSpriteNode = SKSpriteNode(imageNamed: "a")
override func didMove(to view: SKView) {
//Set game camera
self.camera = gameCamera
joypadBackground.position = convert(CGPoint(x: 0, y: 0), to: gameCamera)
joypadBackground.size = CGSize(width: 50, height: 50)
joypadBackground.zPosition = 1000
gameCamera.addChild(joypadBackground)
}
I had a hard time with this same problem the first time I was working with SKCameraNode and creating a heads up display.
Basically you have to remember that there are two parts to the camera. Running its functionality and rendering its children. By setting the scene's camera to gameCamera you've setup the functionality, but your camera isn't in the node tree for rendering. So, if you ever have a camera that needs to render its children don't forget to add it to the scene as a child, then the camera's children will be displayed.
self.camera = gameCamera
self.addChild(gameCamera)
Hope that helps someone avoid a very common error with a very simple solution.
You don't need
convert(CGPoint(x: 0, y: 0), to: gameCamera)
You can just set the CGPoint position to (0,0) and it should be at that point relative to the camera's space.
Not sure if this helps, at all, but what I do is (generally) position a child node AFTER I've added it to its parent. This is mainly a mental reminder, to me, that the child's position is within the coordinate space of the parent. So I'd do something like this:
gameCamera.addChild(joypadBackground)
joypadBackground.position = CGPoint(x: 0, y: 0)
If you're using a mid screen origin in your SKScene, this should be in the middle of the screen.
Bottom left will be a negative x and negative y value, size of which is relative to your frame size.

SpriteKit convertPointToView / convertPoint

Question How do I get the purple square to stay anchored at the bottom left of the screen regardless of the camera position?
Details I have an SKScene named colorScene that has a camera node and two shape nodes - one blue and one purple square. The camera is constrained to the blue square which is positioned at 0,0:
Now, say I want to position the purple square at the bottom left of the screen. And I want it to stay there no matter where the camera goes. First, I would make the purple square a child of the camera node. But then what? If I position the purple square at 0,0, strangely, it's in the middle of the screen. (Shouldn't the camera node be the entire screen area?) And if I try convertPoint to go from the camera node to the scene coordinates:
purpleNode?.position = convertPoint(CGPoint(x: 0, y: 0), toNode: cameraNode!)
...things get even more mysterious:
From the Apple docs:
The scene is rendered so that the camera node’s origin is placed in the middle of the scene.
This should explain what you experienced
If I position the purple square at 0,0, strangely, it's in the middle of the screen. (Shouldn't the camera node be the entire screen area?)
Solution
Since we want to place purple on the bottom-left of the screen, we should start with the coordinate related to the view
let bottomLeftOfView = CGPoint(x: 0, y: view.frame.height)
Next we need to convert these coordinate from the view to the scene
purple.position = convertPointFromView(bottomLeftOfView)
This is the full code
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let camera = SKCameraNode()
self.camera = camera
addChild(camera)
let purple = SKSpriteNode(imageNamed: "square")
purple.color = SKColor.purpleColor()
purple.colorBlendFactor = 1
let bottomLeftOfView = CGPoint(x: 0, y: view.frame.height)
purple.position = convertPointFromView(bottomLeftOfView)
camera.addChild(purple)
}
}
Purple and anchorpoint
Only a quarter of the purple sprite is inside the screen. This because its anchorpoint is (0.5, 0.5): the center. So we just told SpriteKit to place the center of purple to the corner of the screen.
In order to have purple entirely into the screen we just need to tell SpriteKit to use use the bottom left corner of purple as anchorpoint
purple.anchorPoint = CGPointZero
This is the final full code
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let camera = SKCameraNode()
self.camera = camera
addChild(camera)
let purple = SKSpriteNode(imageNamed: "square")
purple.color = SKColor.purpleColor()
purple.colorBlendFactor = 1
purple.anchorPoint = CGPointZero
let bottomLeftOfView = CGPoint(x: 0, y: view.frame.height)
purple.position = convertPointFromView(bottomLeftOfView)
camera.addChild(purple)
}
}

SKShapeNode ellipseInRect, sprite does not appear in scene

I'm trying to create an ellipse in the center of my scene:
let center = (CGRectGetMidX(view.scene.frame), CGRectGetMidY(view.scene.frame))
let size = (view.scene.frame.size.width * 0.3, view.scene.frame.size.height * 0.3)
let ellipse = SKShapeNode (ellipseInRect: CGRectMake(center.0, center.1, size.0, size.1))
ellipse.strokeColor = UIColor.blackColor()
ellipse.position = CGPointMake(center)
self.addChild(ellipse)
This was added to didMoveToView, and the node count on the view shows 1, but I do not see the path. How do I add an ellipse to my scene using the SKShapeNode ellipseInRect API?
The problem lies in ellipse.position = CGPointMake(center). For some reason, this changes the position of the ellipse relative to itself rather than relative to the view - so if you did ellipse.position = CGPoint(x: 100, y: 100) then it would set the position to 100 up and 100 to the right of the ellipse itself as opposed to 100,100 on the scene. If you comment out this line, then you should be able to see you ellipse on the screen - I certainly could when it tried it. Hope that helps you position it to where you want.