Objects appear at bottom of screen in SpriteKit - ios

I'm learning SpriteKit and Swift, and I'm trying to make 5 small images "spawn" at the top of the screen, and slide down to the bottom. This is what I got so far, added in the didMoveToView method.
var myArray = NSMutableArray()
myArray.addObject(NSNumber(int: 40))
myArray.addObject(NSNumber(int: 80))
myArray.addObject(NSNumber(int: 120))
myArray.addObject(NSNumber(int: 160))
myArray.addObject(NSNumber(int: 200))
for item in myArray
{
let location = CGPoint(x: CGFloat(item.floatValue), y: 1)
let sprite = SKSpriteNode(imageNamed: "myImage")
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = location
let action = SKAction.moveToY(-4, duration: 4.5)
sprite.runAction(action)
addChild(sprite)
}
But all the objects appear at the bottom. I've tried to change the y-position, with the same result..
Thanks!

You need to increase the y coordinate.
Think about the axis system as the left bottom corner is (0,0) and the top right is (self.frame.size.width,self.frame.size.height)
You may also want to read about anchor points - the default anchor point for an object is 0.5,0.5 so if you locate it on 1 half of it (+ 1 pixel) will appear from the top.
If you want it to pop in from out of the screen you need either to change the anchor point or the poisition

Try changing your location variable to this:
let location = CGPoint(x: CGFloat(item.floatValue) , y:self.size.height)
This will set the y-value to the top of the screen and move the nodes there.

Related

Xcode SpriteKit how to adapt label in GameScene with different screen sizes?

I have a label in GameScene, there is no auto layout to use here, how I can use CGpoint to make sure this label stay at the same position on different screens.
Below is the code where I put my score label
scoreLabel?.position = CGPoint(x: -size.width / 2 + 120, y: size.height / 2 - 150)
let scoreLabel = //insert functionality of scoreLabel here
scoreLabel?.position = CGPoint(x: size.width + //insert dimention,
y: size.height + //insert dimension)
self.addChild(scoreLabel)
seeing your previous comment, I would suggest you use the following functions, which change the dimensions when they are run on different screen sizes:
AspectFill
AspectFit
ResizeFill
Here is a link that could help you on how to use these functions: Sprite Kit Scene Editor GameScene.sks scene width and height
Hope this helps!
Here is what I put in my View Controller:
var h, w = 1000
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
w = view.frame.width / (view.frame.height / 1000)
let scene = GameScene(size: CGSize(width: w, height: h))
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
}
}
It's handy to know that I make the height of any device 1000. It's much easier to make it universal.
Then I place everything percentage-wise on the screen.
label.position.x = w/2
label.position.y = h/2
label2.position.x = w/2
label2.position.y = h/2 - 50
These lines of code will place 2 labels in the center of the screen. Even if you rotate your device, and even if you use different sized devices. That - 50 I used will always stay the same.
My main takeaway: I create global states of the relative size of the screen.
Helpful tip: I always make my scene's anchor point to be .zero. That way, the min width and height are always 0, and the max height is always 1000.

How to pin a label / UIView to a node in ARKit

I’d like to have a UILabel stay on top of a node in ARKit, similar to the dimensions labels in iOS 12’s Measure app.
I’ve tried adding the label as a plane node and using a billboard constraint, but then the text gets smaller as you move away, which isn’t ideal.
At WWDC, they referred to this as Screen Space, but didn’t say how to achieve it.
Thanks in advance!
I've come up with the following solution to make it work.
First, I create the UILabel and add it as a subview. Next, I convert the position of the node I want to follow to screen coordinates in renderer(_: updateAtTime:). Now the label follows the node correctly and stays fixed in scale. However, the label stays horizontal to the screen, which looks weird. To make it stay horizontal to the world, I rotate the label according to the ARCamera's yaw (z rotation).
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
// Convert the node's position to screen coordinates
let screenCoordinate = self.sceneView.projectPoint(node.position)
DispatchQueue.main.async {
// Move the label
label.center = CGPoint(x: CGFloat(screenCoordinate.x), y: CGFloat(screenCoordinate.y))
// Hide the label if the node is "behind the screen"
label.isHidden = (screenCoordinate.z > 1)
// Rotate the label
if let rotation = sceneView.session.currentFrame?.camera.eulerAngles.z {
label.transform = CGAffineTransform(rotationAngle: CGFloat(rotation + Float.pi/2))
}
}
}
Try with a SCNText geometry, I've used the following code some months ago:
var txtNote = SCNText()
txtNote.string = "Hello AR word!"
txtNote.font = UIFont.systemFont(ofSize: 2)
txtNote.extrusionDepth = 1.0
txtNote.containerFrame = CGRect(x: 2.5, y: 0, width: 16, height: 10)
txtNote.isWrapped = true
txtNote.alignmentMode = kCAAlignmentLeft
txtNote.materials.first?.diffuse.contents = UIColor.black.cgColor
var txtNode = SCNNode(geometry: txtNote)

Correctly position the camera when panning

I'm having a hard time setting boundaries and positioning camera properly inside my view after panning. So here's my scenario.
I have a node that is bigger than the screen and I want to let user pan around to see the full map. My node is 1000 by 1400 when the view is 640 by 1136. Sprites inside the map node have the default anchor point.
Then I've added a camera to the map node and set it's position to (0.5, 0.5).
Now I'm wondering if I should be changing the position of the camera or the map node when the user pans the screen ? The first approach seems to be problematic, since I can't simply add translation to the camera position because position is defined as (0.5, 0.5) and translation values are way bigger than that. So I tried multiplying/dividing it by the screen size but that doesn't seem to work. Is the second approach better ?
var map = Map(size: CGSize(width: 1000, height: 1400))
override func didMove(to view: SKView) {
(...)
let pan = UIPanGestureRecognizer(target: self, action: #selector(panned(sender:)))
view.addGestureRecognizer(pan)
self.anchorPoint = CGPoint.zero
self.cam = SKCameraNode()
self.cam.name = "camera"
self.camera = cam
self.addChild(map)
self.map.addChild(self.cam!)
cam.position = CGPoint(x: 0.5, y: 0.5)
}
var previousTranslateX:CGFloat = 0.0
func panned (sender:UIPanGestureRecognizer) {
let currentTranslateX = sender.translation(in: view!).x
//calculate translation since last measurement
let translateX = currentTranslateX - previousTranslateX
let xMargin = (map.nodeSize.width - self.frame.width)/2
var newCamPosition = CGPoint(x: cam.position.x, y: cam.position.y)
let newPositionX = cam.position.x*self.frame.width + translateX
// since the camera x is 320, our limits are 140 and 460 ?
if newPositionX > self.frame.width/2 - xMargin && newPositionX < self.frame.width - xMargin {
newCamPosition.x = newPositionX/self.frame.width
}
centerCameraOnPoint(point: newCamPosition)
//(re-)set previous measurement
if sender.state == .ended {
previousTranslateX = 0
} else {
previousTranslateX = currentTranslateX
}
}
func centerCameraOnPoint(point: CGPoint) {
if cam != nil {
cam.position = point
}
}
Your camera is actually at a pixel point 0.5 points to the right of the centre, and 0.5 points up from the centre. At (0, 0) your camera is dead centre of the screen.
I think the mistake you've made is a conceptual one, thinking that anchor point of the scene (0.5, 0.5) is the same as the centre coordinates of the scene.
If you're working in pixels, which it seems you are, then a camera position of (500, 700) will be at the top right of your map, ( -500, -700 ) will be at the bottom left.
This assumes you're using the midpoint anchor that comes default with the Xcode SpriteKit template.
Which means the answer to your question is: Literally move the camera as you please, around your map, since you'll now be confident in the knowledge it's pixel literal.
With one caveat...
a lot of games use constraints to stop the camera somewhat before it gets to the edge of a map so that the map isn't half off and half on the screen. In this way the map's edge is showing, but the furthest the camera travels is only enough to reveal that edge of the map. This becomes a constraints based effort when you have a player/character that can walk/move to the edge, but the camera doesn't go all the way out there.

Cannot get SKActionmove to work reliably

I'm trying to build a small spriteKitGame. I have been trying to use the function to move a couple of tank sprite nodes to a specific point.
Attached below is the code snippet.
let tankSpawn = CGPoint(x: self.size.width , y: 70);
tank.position = tankSpawn;
tank.zPosition = 3.0;
let targetPoint = CGPoint(x: -tank.size.width/2, y: tank.position.y);
let actionMove = SKAction.move(to: targetPoint, duration: TimeInterval(tankMoveDuration))
This is my result. They are spawning at the correct point ( 70units high ), but are going down as shown.
I want them to go in a straight line. I set the target points y as a constant. I have no idea why they are going to wards some bottom source.
I have similar code for planes that spawn above( which are working perfectly ).
let plane = SKSpriteNode(imageNamed: "SpaceShip");
let planeMoveDuration = 3.0
let planeSpawn = CGPoint(x: self.size.width , y: self.size.height/2);
plane.position = planeSpawn;
plane.zPosition = 3.0;
let actionMove = SKAction.move(to: CGPoint(x: -plane.size.width/2, y: plane.position.y), duration: TimeInterval(planeMoveDuration))
I have no idea what my mistake here is.
I have tried changing the y co ordinate of the target to tank.position.y, but it doesn't work.
Are they falling under gravity? A sprite-kit scene with physics bodies will have gravity and things will fall unless you take specific action to avoid this.
It's easily done - you're developing your game, you have basic elements on screen and movement etc is all working well, then you want to add some collision detection so you add physicsBodies and then whoosh - where'd my sprites go?

How to "center" SKTexture in SKSpriteNode

I'm trying to make Jigsaw puzzle game in SpriteKit. To make things easier I using 9x9 squared tiles board. On each tile is one childNode with piece of image from it area.
But here's starts my problem. Piece of jigsaw puzzle isn't perfect square, and when I apply SKTexture to node it just place from anchorPoint = {0,0}. And result isn't pretty, actually its terrible.
https://www.dropbox.com/s/2di30hk5evdd5fr/IMG_0086.jpg?dl=0
I managed to fix those tiles with right and top "hooks", but left and bottom side doesn't care about anything.
var sprite = SKSpriteNode()
let originSize = frame.size
let textureSize = texture.size()
sprite.size = originSize
sprite.texture = texture
sprite.size = texture.size()
let x = (textureSize.width - originSize.width)
let widthRate = x / textureSize.width
let y = (textureSize.height - originSize.height)
let heightRate = y / textureSize.height
sprite.anchorPoint = CGPoint(x: 0.5 - (widthRate * 0.5), y: 0.5 - (heightRate * 0.5))
sprite.position = CGPoint(x: frame.width * 0.5, y: frame.height * 0.5)
addChild(sprite)
Can you give me some advice?
I don't see a way you can get placement right without knowing more about the piece texture you are using because they will all be different. Like if the piece has a nob on any of the sides and the width width/height the nob will add to the texture. Hard to tell in the pic but even if it doesn't have a nob and instead has an inset it might add varying sizes.
Without knowing anything about how the texture is created I am not able to offer help on that. But I do believe the issue starts with that. If it were me I would create a square texture with additional alpha to center the piece correctly. So the center of that texture would always be placed in the center of a square on the grid.
With all that being said I do know that adding that texture to a node and then adding that node to a SKNode will make your placement go smoother with the way you currently have it. The trick will then only be placing that textured piece correctly within the empty SKNode.
For example...
let piece = SKSpriteNode()
let texturedPiece = SKSpriteNode(texture: texture)
//positioning
//offset x needs to be calculated with additional info about the texture
//for example it has just a nob on the right
let offsetX : CGFloat = -nobWidth/2
//offset y needs to be calculated with additional info about the texture
//for example it has a nob on the top and bottom
let offsetY : CGFloat = 0.0
texturedPiece.position = CGPointMake(offsetX, offsetY)
piece.addChild(texturedPiece)
let squareWidth = size.width/2
//Now that the textured piece is placed correctly within a parent
//placing the parent is super easy and consistent without messing
//with anchor points. This will also make rotations nice.
piece.position = CGPoint(x: squareWidth/2, y: squareWidth/2)
addChild(piece)
Hopefully that makes sense and didn't confuse things further.

Resources