Position SKSpriteNode at 0,0? - swift

I've read that 0,0 is at the bottom left corner in Sprite Kit but when I position a sprite at that location nothing shows up on screen.
let squareSprite = SKSpriteNode(color: UIColor.purpleColor(), size: CGSize(width: 100, height: 100))
squareSprite.anchorPoint = CGPoint(x: 0, y: 0) // bottom left corner of square
squareSprite.position = CGPoint(x: 0, y: 0)
addChild(squareSprite)
What am I missing?

This is probably happening because your view size, and your scene size have different dimensions. I guess you are using default game project template where the scene is loaded from .sks file (scene size in that case is 1024x768). If scene has different size than a view even if the sprite is added, it might not be visible because it can be off-screen. You can test this by adding a sprite at other position (eg. x=300,y=300). One thing you can do is to simply change scene's size (inside view controller) like this: scene.size = skView.bounds.size Also the important thing here is scene's scale mode (from the docs):
After a scene is rendered, its contents are copied into the presenting
view. If the view and the scene are the same size, then the content
can be directly copied into the view. If the two differ, then the
scene is scaled to fit in the view. The scaleMode property determines
how the content is scaled.
On top of this, I would suggest you to read this about how to initialize your scene in view controller and there is also debate about viewDidLoad vs viewWillLayoutSubviews methods.
Unrelated to your problem, but you might find useful to know that changing anchor point will not affect on physics body of a node. It's a purely visual property.
Hope this helps.

Related

How to add node to ARKit Scene based on where the centre of the device is on button press?

Complete swift beginner struggling to add nodes to scene on tap of a button.
I am aware I can use tap gesture to get the tap coordinates and perform a hit test and then place an object in 3D space where tapped. However I want to show a sphere or crosshair in the centre of device screen and when tap a button at the bottom of the screen have the same behaviour of obtaining the same information a tap gesture would give.(At least i think that is what I need to do here i'm not sure.)
After loading my scene I add a small Sphere to the child node of the camera point of view like this:
sphereNode = SCNNode(geometry: SCNSphere(radius: 0.020))
sphereNode?.position = SCNVector3Make(0, 0, -2)
sceneView.pointOfView?.addChildNode(sphereNode!)
This places the sphere in the centre of the screen slightly in front of the camera. As I move the device around the room the sphere stays in the middle as expected. I want to be able to tap a button then obtain the information that a tap gesture would give in order to perform a hit test and place a new sphere in the scene at that location. Unless there is another better way of doing this?
The behaviour I am looking for is very similar to that of the iOS measuring app. The measure point stays in the centre of the screen at all times, then on tapping add a new point is added.
Your approach of using hitTest(_:types:) is the way to go. I think that the linked one, from ARSession, is the most straightforward.
Since you want to hit test from the center of the screen, and the CGPoint argument expected by hitTest(_:types:) is in normalized coordinate space, from (0.0, 0.0) to (1.0, 1.0), all you need to do is pass (0.5, 0.5).
Here is some code:
func buttonAction() {
guard let currentFrame = session.currentFrame,
let hitTest = currentFrame.hitTest(CGPoint(x: 0.5, y: 0.5), types: .featurePoint).last else {
return
}
let anchor = ARAnchor(transform: hitTest.worldTransform)
session.add(anchor: anchor)
}
After the anchor is added, you can add the node using ARSCNViewDelegate's method renderer(_:nodeFor:). Basically, this method is going to be called when you add an anchor to the session.
By the way, since it seems that you're using ARSCNView, you can access your AR session like this sceneView.session.

How to prevent SpriteKit overlay in SceneKit to stretch?

In WWDC 2017 SceneKit session they recommend using overlaySKScene to overlay a SpriteKit layer over SceneKit for HUD, etc. It seems to work fine until I rotate the iPhone/iPad. Now the text in SpriteKit all got stretched (if turn from portrait to landscape) or shrunk (if turn from landscape to portrait). Is there anyway to keep the text or graphics in same size regardless of orientation?
sceneView.overlaySKScene = SKScene(size: sceneView.frame.size)
I solved the issue in a very simple way. Just resize the overlaySKScene and reposition everything in
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let width = sceneView.frame.height
let height = sceneView.frame.width
sceneView.overlaySKScene?.size = CGSize(width:width, height:height)
...
}
Hope this helps others facing similar problem.
You need to look at how SKScene works:
This means set the overlay to size of the iphone screen for the given orientation:
sceneView.overlaySKScene = SKScene(size: sceneView.frame.size)
Now I am assuming nothing else changes, so that means your scaleMode is set to .fill, which means stretch graphics till all sides are filled in.
When sitting in portrait mode, you will not notice anything because it is 1:1 scale.
Now you flip it to landscape.
The system is going to attempt to convert your portrait screen size to a landscape size.
This means you are going to get fat stubby graphics because it is going to stretch out your sides, and shrink your top and bottom to fill all edges.
This is why doing code like sceneView.overlaySKScene = SKScene(size: sceneView.frame.size) is bad. You end up getting yourself confused.
I always recommend either using an SKS file, or setting a constant scene size, so sceneView.overlaySKScene = SKScene(size: CGSize(750,1334))
Then, depending on how I want to display my overlay, I use the proper scale mode.
In games where I want the overlay to take up the exact percentage of screen space, I use .aspectFill
In games where I want the overlay to take up the exact amount of pixel space, I use .resizeFill
By looking at the scaleMode, I now know how my scene is suppose to behave.
With .aspectFill, I am going to need to detect when orientation was flipped, and at that time either present the SKS corresponding to orientation, or move nodes corresponding to orientation.
With .resizeFill, I need to create a resize method that adheres to when the orientation is flipped, and move the nodes in proportion to the new size.
You can detect when orientation flips in the ViewController with
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
then use the view to get to the scene to call your resize function/load a new scene.

SKSpriteNode with a size relative to the screen

I'm trying to create a 'modal' SKSpriteNode that fills most of the screen minus a border on each side. I have a subclass of SKSpriteNode as I need to pass some other info to it for display purposes, so I have a custom init method.
After I initialise my Modal I'm then setting the size as below:
let modal = Modal(items: [Item])
let newSize = CGSize(width: (frame.width) - 40, height: (frame.height) - 40)
modal.size = newSize
modal.zPosition = 999
addChild(modal)
The trouble is there is no border at the top or bottom on my Modal, it fills the entire screen vertically. My custom 'init' method of modal calls the super.init passing in a size of 50,50 so even that should be a lot smaller then the screen if that's what it was using but it's not. My game is in landscape mode and I tried switching the width and height above but that fills the entire screen (so there's no border on the left and right either). I know I've not set the position in the above code, but even then I would expect to see a 'border' somewhere evening if it isn't uniform.
This is all done in my main scene file, so frame here relates to the frame of the main game scene.
Any ideas?
So for anyone else has a similar issue the problem was that my scene was loaded from an SKS file.
The SKS file in question had it's size set to freeform. Changing this to a specific device e.g iPhone 6 and making sure the orientation was set to landscape has fixed the issues with sizing

Swift/Sprite-kit Adding a static hud that is always at the top and bottom, with the moveable game in the middle?

I have been playing around trying to learn spritekit in xcode7. So far I have a level that is larger than the screen, a hero the I can move around and a camera that follows the hero as he moves off the the viewable screen area. Next I'm trying to create a hud at the top and bottom of the screen. The bottom one would be where the joystick/buttons live.
I tried something like this:
let hud = SKSpriteNode(color: SKColor.blackColor(), size: CGSizeMake(self.size.width, self.size.height * 0.05))
hud.anchorPoint = CGPointMake(0, 0)
hud.position = CGPointMake(0, (self.view?.bounds.height)! - hud.size.height)
self.addChild(hud)
But this puts a black view at the top of my level and I can only see it if I walk my hero up there. That means if I move it to the bottom, as soon as my hero walks up the joystick and buttons will be off the screen.
Can anyone please provide any direction on how I can add huds to sprite-kit that are always at the top and bottom of the viewable area, while still allowing the hero to move around the level?
You could add the HUD as a child of the scene, then add the world as a child of the scene too. The character must be a child of the world.
This way, the character can move around the world and then the world can be moved on the scene so the character could be visible, without messing with the HUD.
Swift 3:
This should move your hud (suppose is a SKNode) from the origin anchorPoint (0.5,0.5) of the camera to CGPoint.zero always visible:
override func didSimulatePhysics() {
hud.position = CGPoint(x:-self.frame.midX,y:-self.frame.midY)
}
There are two options to doing this, one is to add UIButtons to your view which will always appear on the top, the other (which I commonly use) is dynamically setting the position of the hud based off of the anchor point of the scene.
The first way is pretty simple, just create the UIButtons and UILabels in Interface Builder and they should be on top of your scene (you may need to mess around with view layer numbers to get it appearing above though).
The other way is to put some code in the didSimulatePhysics method (which will only be called after the update, and after the physics have been simulated). Basically, doing what you are doing, and setting the position of the hud to the scene's anchorPoint multiplied by the width and height of the scene. Something like this (my Swift is a little rough):
let x = (((-self.scene.anchorPoint.x) * self.frame.size.x) + anyOffsetRequired.x)
let y = (((-self.scene.anchorPoint.y) * self.frame.size.y) + anyOffsetRequired.y)
hud.position = CGPointMake(x, y)
The - on the anchorPoint is due to how moving the scene's anchorPoint works (going right requires negative, going down requires positive). anyOffsetRequired is just a CGPoint that allows you to set the offset, you may or may not need it in your case.

SpriteKit Coordinates differ between iPad and iPhone

I have a very simple SpriteKit scene that is not behaving like I would expect. It is a universal app and the code that I am using to draw a single red square in the scene is as follows:
let redSquare = SKSpriteNode(color: UIColor .redColor(), size: CGSizeMake(100.0, 100.0))
redSquare.anchorPoint = CGPointMake(0.0, 0.0)
redSquare.position = CGPointMake(1.0, 1.0)
addChild(redSquare)
This code is in the GameScene didMoveToView method of the default app that xCode creates for you when you select SpriteKit game.
When run on the iPad it behaves as expected drawing the red square 1 point off the bottom and left edge of the screen.
When I run it on the iPHone 6 Plus simulator however it shows up like this:
The spaceship shows up at the correct touchpoint on both devices, however a println() of the touch location shows (1,1) on the iPad and (1,100) on the iPhone.
This indicates that the coordinates from (x,0) - (x,99) are off the bottom of the iPhone screen which makes no sense to me at all.
Why do the two devices not display it the same way? The behavior is the same in both the simulators and on the actual devices.
Thanks!
Scooter
This is caused because the size of the screen on iPhone is incorrect - have a look in the scene's sks file, you should see the size of the scene is set to (1024, 768) by default. To correct this you need to set the scene size and scale mode:
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
You should do this is didMoveToView or when you instantiate the scene.