I am a complete beginner at Swift 3 and programming in general so this will be an incredibly basic question.
I have been following tutorials and am currently positioning a SpriteNode using CGPoint. The tutorial recommends the following to position the Node centrally at the bottom of the screen:
Ground.position = CGPoint(x: self.frame.width / 2, y: 0)
However, that causes it to stick to the top-right of the screen.
When I use the following code:
Ground.position = CGPoint(x: 0 - self.frame.width / 2, y: 0 - self.frame.height / 2)
It positions at the bottom-centre as intended.
I do not understand why this happens as there is very little else done in the tutorial at this point to cause the error.
Set the anchorPoint of the scene to the bottom left corner at the beginning of your didMove(toView: SKView) (or in GameViewController):
scene.anchorPoint = CGPoint(x: 0, y: 0)
Keep in mind that everything you place on the scene is placed based on the anchorPoint. So with this anchorPoint, the origin of your scene is the bottom left corner.
Also note that if you're using .AspectFill as your sceneScaleMode, you don't have to use self.frame and instead set the scene size to 768x1024 (portrait)/ 1024x768 (landscape) then just use number values within the scene size.
Additionally, there are advantages of setting your scenes anchorPoint to the centre:
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
This makes it easier to fit the scene into iPad size as well as iPhone, and also simplifies centering nodes.
See this link for more info on making your app universal: iOS Universal Device App with SpriteKit, how to scale nodes for all views?
Related
Completely new to SpriteKit. Currently I have a UIView, and I want to add a sprite node to it (like a small UIImageView, but I want animation for it so using SpriteKit). Therefore I didn't initialize my project to be a game project, as found in almost all of tutorials for SpriteKit. I've found a note here: link and what I have now is sth like:
func initializeImage() {
let imageView = SKView()
imageView.frame = CGRect(x: self.frame.width / 2 - Constants.imageWidth / 2, y: self.frame.height - Constants.imageHeight, width: Constants.imageWidth, height: Constants.imageHeight)
// so place it somewhere in the bottom middle of the whole frame
let sheet = SpriteSheet(texture: ...)
let sprite = SKSpriteNode(texture: sheet.itemFor(column: 0, row: 0))
sprite.position = imageView.center //basically the same position as the imageView.frame's x and y value
let scene = SKScene(size: imageView.frame.size)
scene.backgroundColor = SKColor.clear
scene.addChild(sprite)
imageView.presentScene(scene)
self.frame.addSubview(imageView)
}
The SpriteSheet is similar to this: sprite sheet; it's essentially cutting an image atlas and divide it into smaller images. I tracked the process and this step is indeed giving the smaller image (the var 'sprite'). But if running I only have a black square now (should be the size as defined by Constants). If I set scene.backgroundColor to be white then it's white. May I know how I should proceed from here, as how should I make the sprite showing up?
All of your code looks good except for this:
sprite.position = imageView.center // basically the same position as the imageView.frame's x and y value
That is basically not the position you think it is. The coordinate system in SpriteKit is a) relative to the (SK)scene, not to whatever view the SKView is contained in, and b) flipped vertically relative to the UIKit coordinate system. If you want a sprite centered in the scene, you probably want to set its position based on the scene's size:
sprite.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
By the way, the external SpriteSheet code might not be needed (and you're more likely to benefit from Apple's optimizations) if you slice up your sprite sheet and put it in an Xcode asset catalog.
I tried to understand the basics of anchor points in swift reading this :
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Nodes/Nodes.html
I understood that in siwft, with sprite kit, the anchor point of the scene is in the down-left corner of the screen.
So if I position a node like this :
myNode.position = CGPoint(x: 0, y: 0)
self.addChild(myNode)
It doesn't appear on my view. So i tried another thing :
myNode.position = CGPoint(x: middleOfmyView, y: 0)
self.addChild(myNode)
And now I see half of the top of my square. So I see 20 pixels up from the bottom of the screen.
So I added :
myNode.position = CGPoint(x: middleOfmyView, y: 0+myNode.frame.size.height/2)
self.addChild(myNode)
And now I can see my entire square, on the middle of the screen.
Then I put it at x: 0 and let the Y point like that :
myNode.position = CGPoint(x: 0, y: 0+myNode.frame.size.height/2)
self.addChild(myNode)
And now I can't see my node appearing even if there is written "1 Node" in the bottom right corner of my screen!
So i'm wondering is the 0,0 point of the iPhone screen really on the down-left corner of the screen ?
Then where is the anchorpoint of my sprite node? How can I put my square right in the down left corner of my screen ?
This is all just to understand how things work… Thanks a lot in advance for your help :)
I'm using Xcode 7.3.1 and I still do not understand why the point 0 makes that I do not see my stuff… it's definitely a bug ?
SKSpritenodes default to an anchorPoint of (0.5, 0.5) meaning that the middle of the node goes where the position is set to. To change the anchorPoint do this: node.anchorPoint = CGPoint(x: 0, y: 0) this example sets the anchorPoint of the node to its bottom left corner. Keep in mind that changing the anchorPoint also changes the origin of rotation, and that anchorPoint values are between 0 and 1. To put the square in the bottom left corner:
myNode.anchorPoint = CGPoint(x: 0, y: 0)
myNode.position = CGPoint(x: frame.minX, y: frame.minY)
If you need help with anything else (or if I didn't cover what you wanted) feel free to ask me and I'll do my best to help
I want to constrain and position a game center button to the top left and make it constrained for every device, how do I do this. I am really new to sprite kit.
let gameCenterBtn = SKSpriteNode(imageNamed: "Game-Center-icon.png")
gameCenterBtn.size = CGSize(width: 60, height: 60)
gameCenterBtn.name = "gameCenterButton"
gameCenterBtn.position = CGPointMake(320, 730)
gameCenterBtn.position = CGPoint(x: self.frame.width , y: self.frame.height )
For example how do I make a position a node and make it work for any device?
I figured it out. The code below works.
gameCenterBtn.position = CGPoint(x: CGRectGetMidX(self.frame) - 188 , y: CGRectGetMaxY(self.frame) - 25)
Scene cropping is still happening as I said in my old answer I deleted.
Open a brand new Xcode game template and paste your code with a position like this
gameCenterBtn.position = CGPoint(x: CGRectGetMaxX(self.frame) - (gameCenterButton.size.width / 2) , y: CGRectGetMidY(self.frame))
and run on all the simulators to see what happens.
On iPads you see half the image on the right and on iPhones you only see 1/4.
Now go the the GameViewController and change the scene scale mode to ResizeFill and see what happens. You sprits will be at the same spot on all devices but you will have to readjust the image size.
This is how you set the scene size.
How to change SKscene size
I originally created my game with iOS 8 and tested on my iPhone 6S. The game looks fine in the 5, 5S, 6, 6 Plus, 6S, and 6S Plus (since all devices have the same ratio of 16:9). As you can see from the image, the music button is offset from the top right corner. The image is offset by this code:
muteButton.position = CGPoint(x: CGRectGetMidX(self.frame) + 920, y: CGRectGetMidY(self.frame) + 480)
The problem I have is if someone tried this game on an iPad, it will display this. As you can see, the bottom graphic and the mute button are offset from the sides by a lot.
I want to make it so that the objects will always stay close to the sides of the frame/view. Making the app "universal" on xCode does not fix it either. Or do I just make a completely new project built for the iPad?
Don't forget about the 4s, you will get the same problems as iPad. SpriteKit does not have constraints like in the UI builder, so you are going to have to accommodate for the 4:3 and the 16:9 devices by applying some math, or force the 4:3 to be 16:9 with black borders using the .AspectFit scaling method.
Now I am not sure where 920, and 480 are coming from, but those numbers may have to be tweaked in this code when detecting the device. Simplest way to determine your aspect ration is to do UIScreen.mainScreen().bounds.width/UIScreen.mainScreen().bounds.height, then work from there.
Solution! I figured it out! For those who come from the future and also might need help with this. This works with landscape orientation and portrait orientation.
Note: You must have your scene.scaleMode set to .AspectFill for this to work on all scenes and the scene size has to be 2048x1536 or 1536x2048. This will make it scaleable for iPad too.
I have declared the following variable on the top of my class.
class StartScene: SKScene {
let playableArea: CGRect
}
Then, I have the following code inside the override init() function.
override init(size: CGSize) {
//1. Get the aspect ratio of the device
let deviceWidth = UIScreen.mainScreen().bounds.width
let deviceHeight = UIScreen.mainScreen().bounds.height
let maxAspectRatio: CGFloat = deviceWidth / deviceHeight
//2. For landscape orientation, use this*****
let playableHeight = size.width / maxAspectRatio
let playableMargin = (size.height - playableHeight) / 2.0
playableArea = CGRect(x: 0, y: playableMargin, width: size.width, height: playableHeight)
//3. For portrait orientation, use this*****
let playableWidth = size.height / maxAspectRatio
let playableMargin = (size.width - playableWidth) / 2.0
playableArea = CGRect(x: playableMargin, y: 0, width: playableWidth, height: size.height)
super.init(size: size)
}
From here, I then use the variable playableArea to position my objects.
titleChild.position = CGPoint(x: CGRectGetMidX(playableArea), y: CGRectGetMaxY(playableArea) - (titleChild.size.height * 0.90))
Works amazing. Looks good in the iPhone 4S, 5, 5S, 6, 6 Plus, 6S, 6S Plus, and iPads.
If you want to see the box in the app to make sure you did it right, use the following function.
func drawPlayableArea() {
let shape = SKShapeNode()
let path = CGPathCreateMutable()
CGPathAddRect(path, nil, playableArea)
shape.path = path
shape.strokeColor = SKColor.redColor()
shape.lineWidth = 8
addChild(shape)
}
Then just call the function in the didMoveToView() function to view the red frame to make sure you did the code right. This will create a red frame the size of the view that is viewable to the user. Now that you have playableArea to hold the frame that the user can see, you can use it for other things such as making sure objects don't or can't leave the bounds, etc. For this screenshot, I use it to prevent the user from moving the spaceship outside the device.
Here's what I have so far in my didMoveToView() function:
backgroundLayer.zPosition = -1
hudLayer.zPosition = 100
addChild(backgroundLayer)
addChild(hudLayer)
backgroundColor = SKColor.whiteColor()
let healthBar: SKSpriteNode = SKSpriteNode(imageNamed: "Healthbar")
healthBar.position = CGPoint(x: 0, y: 0)
healthBar.anchorPoint = CGPoint(x: 0, y: 1.0)
hudLayer.addChild(healthBar)
My background shows up fine, and I have both a backgroundLayer and a hudLayer. I even made the zposition much higher for the hudLayer to make sure it's in front. As for the positioning, I think I have the anchor point to be at the top-left of the sprite, and tried various ways to position it- it seems to only show up when I use something like:
healthBar.position = CGPoint(x: 0, y: size.width/2)
My question is why? size.width/2 isn't working for when I test on different screen sizes (on an iphone4, the health bar is further down).
Doesn't size take the current size of the screen? I thought using size would make it somewhat 'responsive' but I can't get the behavior to work right. Therefore, I thought maybe the CGPoint coordinate system (0-1.0) would work better, but it's not showing up at all with that.
Thanks for reading, I don't understand why it won't show up :(