If the default anchor point for an SKScene is (0,0) then does that make the scene stick its top left corner to the top left corner of the device's screen?
Also, if so, what's an easy way to support multiple devices using one scene? For example the default scene size (1024, 768) just fits a landscape iPad such that if I put a node at (0,0) in the scene it appears at the bottom left corner. How do developers usually get around this?
Edit 1
I tried setting the scale mode of the scene to .ResizeFill in didMoveToView():
let screenWidth = UIScreen.mainScreen().bounds.width
let screenHeight = UIScreen.mainScreen().bounds.height
print("Screen size: (\(screenWidth), \(screenHeight))")
print()
print("Scene size before setting scaling: (\(self.frame.width), \(self.frame.height))")
print()
self.scaleMode = .ResizeFill
print("Scene size after setting scaling: (\(self.frame.width), \(self.frame.height))")
This prints out:
Screen size: (667.0, 375.0)
()
Scene size before setting scaling: (1024.0, 768.0)
()
Scene size after setting scaling: (1024.0, 768.0)
Related
Explanation
For some reason, width and height values are being swapped in my game (as you can see below) that is set to be in landscape orientation. This way, the sprite that should be centered in the screen, is totally off the right position.
Code
You can download it here.
GameScene
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
print("width:", self.frame.width)
print("height:", self.frame.height)
//Sprite
let sprite = SKSpriteNode (imageNamed: "sprite")
sprite.anchorPoint = CGPointZero
sprite.position = CGPoint(x: self.frame.width / 2 - sprite.frame.width / 2, y: self.frame.height / 2 - sprite.frame.height / 2)
addChild(sprite)
}
}
GameViewController
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Set view size.
let scene = GameScene(size: view.bounds.size)
// Configure the view.
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill
skView.presentScene(scene)
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .Landscape
} else {
return .Landscape
}
}
}
Thanks in advance,
Luiz.
If you go to targets-General in your xCode project (where you set version number, bundle ID etc) did you untick portrait? You also might have to check your info.plist to see if all portrait entries are removed for both devices.
You approach unfortunately is bad practice a lot of tutorials teach you and I would not continue like this.
If you use "scaleMode ResizeFill" or set the scene size to "view.bounds" your game will never look consistent on all devices. Furthermore all your values (sprite sizes, font sizes, physics values etc) will also not be the same on devices different to the one you are testing on.
Basically you will have to adjust for all of this on like 5-6 devices and its madness, especially using xCode simulator. Trust me I have been there before with 2 games and it nearly killed me. Its a game of game of "yeah this looks about right".
Dont go through this, so what you should do is
1) Change your scene size to the default scene size used by xCode
GameScene(size: CGSize(width: 1024, height: 768)) // swap if portrait
Note: Check update at bottom
If you dont do this, and leave it at view.bounds.size, point 2 will not work.
2) Change your scene scale mode to .AspectFill (also default xCode settings).
This way all your stuff will look great on all iPhones. Your values will scale correctly and you save yourself a lot of work.
On iPads you will have some extra space at the top and bottom (landscape) or left and right (portrait) which you usually just cover with some more background and have as a non playable area
Your scene basically looks like this now.
The red area is your whole scene size (iPad) but if you run on iPhones you will only see the green bit. In Portrait mode that red area would be on the left and right side.
Thats why y positioning is usually done from the centre because if you use frame.minY or frame.maxY you would be in the red zone and won't see the sprite on iPhones. That red zone you just cover with some more background (your background full screen images should be iPad size).
This also make your game more balanced, because if your would just stretch up your game than it will be easier on iPads because you have more space.
So design your game within the green zone. Than on iPads the only thing you might have to do is move up some UI, like pause button or score label, when you want to show them at the top edge. I do it this way.
// Some button
...
someButton.position = CGPoint(x: frame.midX, y: frame.midY + 200)
addChild(someButton)
if UIDevice.current.userInterfaceIdiom == .pad {
// set for iPad
}
// Some label
someLabel.positon = CGPoint(x: frame.midX - 100, someButton.position.y) // because its y position is the same as someButton I only have to adjust someButton.position.y on iPads.
addChild(someLabel)
I would not continue with your approach, even if it means redoing a lot of work.
UPDATE:
It seems with xCode 8 apple changed the default scene size from 1024x768 (768x1024) to 1334x750 (750x1334). I have only played around with those settings for a bit and they seem confusing for a universal game.
So you scene would now look like this and stuff on the xAxis on iPads is offscreen.
That makes no sense as the iPad is clearly not more widescreen than an iPhone.
I actually opened a bug report to see what apple says about this so for now I would probably keep it at 1024x768.
Hope this helps
Override viewWillLayoutSubviews in your GameViewController to ensure you get the proper width/height of the view. The implication with this also is that any objects you may instantiate would need to wait until this method has been called.
So you would want to do something like
class GameViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// NOTE: Add code to do this only once!
// Set view size.
let scene = GameScene(size: view.bounds.size)
// Configure the view.
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill
skView.presentScene(scene)
}
Note I didn't try and compile this and it is missing a trailing }. Additionally, you would want to do this only once, I haven't added the code to do that. In my game, my scene is a property so I just check to see if it is nil and if it is, then I create the scene and present.
Following your code and according to how do you want to construct your sprite and position in GameScene you simply must change the scene.scaleMode to AspectFill or AspectFit (depend by what do you want to achieve):
sources:
#available(iOS 7.0, *)
public enum SKSceneScaleMode : Int {
case Fill /* Scale the SKScene to fill the entire SKView. */
case AspectFill /* Scale the SKScene to fill the SKView while preserving the scene's aspect ratio. Some cropping may occur if the view has a different aspect ratio. */
case AspectFit /* Scale the SKScene to fit within the SKView while preserving the scene's aspect ratio. Some letterboxing may occur if the view has a different aspect ratio. */
case ResizeFill /* Modify the SKScene's actual size to exactly match the SKView. */
}
The iPhone 6 and 6s are supposed to have a 750 x 1334 resolution [1], and the screen ratio for every iPhone since the iPhone 5 is 16:9 [2]. So in order to have a background image for an app that fits perfectly, it must have a 16:9 ratio. I'm working on a project using SpriteKit and I want the game to have a wallpaper that covers the back from edge to edge. However, when I run the app on the simulator the background image always gets cropped on the right and left. I've even tried with all sorts of ratios and resolutions. The code for this background on the project is:
let background = SKSpriteNode(imageNamed: "backtImage")
background.size = self.size
background.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
background.zPosition = 0
self.addChild(background)
What am I doing wrong?
This could happened because your SKView don't match with your SKScene dimensions.
The sources:
#available(iOS 7.0, *)
public enum SKSceneScaleMode : Int {
case Fill /* Scale the SKScene to fill the entire SKView. */
case AspectFill /* Scale the SKScene to fill the SKView while preserving the scene's aspect ratio. Some cropping may occur if the view has a different aspect ratio. */
case AspectFit /* Scale the SKScene to fit within the SKView while preserving the scene's aspect ratio. Some letterboxing may occur if the view has a different aspect ratio. */
case ResizeFill /* Modify the SKScene's actual size to exactly match the SKView. */
}
You can change your code as below:
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
...
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill // <- this is an example
skView.presentScene(scene)
}
}
If your image is getting cropped on the left and right side (in Portrait) Then we are dealing with an .AspectFill scale mode, and your aspect for your scene is shorter than the screen resolution. This is probably happening because the default scene size provided in the template build is a square. Go into your .sks file, and change the scene size to something in a 9x16 ratio, and your problem should be solved
I'm actually creating a puzzle game for iPad. I would like to create a moving slider containing all the jigsaw parts, independently from the other elements of the game.
I've succeded to create the puzzle on the screen, with the different jigsaw parts all around that user can move with finger. To create the moving slider, I think to use the instruction in my main .swift file.
addChild()
One to create the different elements of the game, standing on the screen and the other to create the moving slider. I've made that successfully, with the different jigsaw parts contained on the slider but I can't make it moved horizontally, even with a touchMoved function on the slider. I've created a 4096 pixels large slider, my scene size is fixed to 2048 x 1536 with a a scene.scalemode set to .AspectFill. I think the problem come from here (maybe).
If I create a much bigger scene (like 4096 x 1536) with the scene.scalemode set to .Fill, I get a deformed picture on the iPad. How to manage that ?
class GameViewController: UIViewController {
override func viewWillLayoutSubviews() {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
let scene = GameScene(size: skView.frame.size)
scene.size.width = 4096
scene.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .Fill
skView.presentScene(scene)
}
Thanks by advance for your help,
Although the title and your actual question diverge somewhat, I assume your problem is how to create a SKScene that is larger than the UIView that presents it.
The size of the SKScene is the point resolution that the presenting view (typically an instance of SKView) renders it at. This is before the UIScreen scaling is taken into account, so a scene of 1024*768 in a fullscreen iPad view is rendered at 2048*1536. The SKScene's scaleMode property is only taken into account when the size of the scene and the containing SKView instance is different.
In my opinion, scenarios in which the scene's size differs from the SKView's size are scarce and exceptional. You could compare SKView and SKScene with UIImageView and UIImage, respectively. Only when the aspect ratio differs does the scaleMode impact the rendering/appearance of the UIImage inside the view.
So, how to create a scene bigger than the view? This is a trick question, because you don't need to.
Consider the size of SKScene to be the viewport of the scene. SKNodes inside the scene are not restricted to be positioned only inside the scene's viewport. With an SKScene of size 1024*768, it is perfectly legit to create an SKSpriteNode (say, of size 20*20) and position it at:
(0,0):you see a square of 10*10 points (or 20*20 pixels) in the bottom-left (with three-quarters of it hidden, out of the rendering viewport)
(500,500): completely visible
(1034,778): exactly out of bounds in the top right corner
I'm new to SpriteKit and Swift (done a lot in Obj-C in the past for iOS) but I'm struggling on something that should be very basic.. positioning sprites on the screen!
Centering a label on the screens seems easy enough:
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
But when I try to put in the Bottom-left or top-right of the screen it isn't visible:
//Bottom-left label
myLabel.horizontalAlignmentMode = .Left
myLabel.position = CGPoint(x: 0.0, y: self.size.height)
or
//Top-right label
myLabel.horizontalAlignmentMode = .Right
myLabel.position = CGPoint(x: self.size.width, y: self.size.height)
This is with a new project, no other code. What am I doing wrong, please?
What you have to keep in mind is that a SpriteKit scene by default takes up far more than the screen. Its default size is 1024 by 768 and everything is scaled for universality unless you tell it otherwise. In your view controller you could see how the scene is initialized. You will see that it probably has a statement that says something like
scene.scaleMode = .AspectFill
This is the source of this. Apple in the documentation explains this in a straightforward manner what happens when you use AspectFill scaleMode:
The scaling factor of each dimension is calculated and the larger of the two is chosen. Each axis of the scene is scaled by the same scaling factor. This guarantees that the entire area of the view is filled but may cause parts of the scene to be cropped.
There are other scale Modes to explore, which you can try but Aspect Fill has a large advantage with its universality.
You can specify the size of an SKScene when you create it. Typically you pass the size of the containing SKView so it covers the entire view. If the device rotates, your view is resized (if your app allows the new orientation). I'm not sure if the SKScene if resized. But either way, you need to update the position of your scene contents so that for example the label is in the top-right with the new dimensions.
The view controller whose view presents the scene receives the message viewWillTransitionToSize(_ size:,withTransitionCoordinator coordinator:). You can subclass this method to send a message to a custom method of the presenting SKScene that the size is changing. Then, you can simply reposition the node using the new size since self.frame will update.
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
Back to the core of the question! How to position a label in the top-right of the scene? Take into account that the scene origin is bottom-left. For SKLabelNode, the center is really in the center. So, offset by half of the label's dimensions.
myLabel.position = CGPoint(x: self.size.width - myLabel.size.width /2,
y: self.size.height - myLabel.size.height/2)
By the way, I have never seen an iOS game which uses a sprite based engine (such as Sprite Kit or Cocos2d) which supports device rotation/multiple screen sizes when running on the same device (iPhone/iPad).
I have an image mySprite#2x.png it's dimensions 1240 x 1240 pixels or 620 x 620 points.
I want to use it for all iPhone models (different screen sizes). All I want it to resize it, but I can't figure out how to do this properly.
Here is what's go wrong:
let sprite = SKSpriteNode(imageNamed:"mySprite")
sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
println(sprite.size)
sprite.size = CGSize(width: 300, height: 300)
println(sprite.size)
println(self.view?.frame.size)
self.addChild(sprite)
Here is log from testing on the iPhone 4S:
(620.0,620.0)
(300.0,300.0)
Optional((480.0,320.0))
log looks ok, but on screen I can see that image is bigger than iPhone 4S width. It's width and height (it's a square) almost as high as iPhone 4 screen height (about 440 pts).
Scene scaleMode is AspectFill.
Scene and sprite xScale and yScale = 1.0
I don't understand what I'm doing wrong.
Based on the log message from your view's frame, the width is 480 and the height is 320, meaning your view is in landscape mode.
If your SKScene's size is set to the size of your view in portrait mode, using scaleMode AspectFill will stretch the width of your scene from 320 to 480.
This means your SKSpriteNode will seem 450pts instead of 300pts.
You probably want to use SKSceneScaleModeResizeFill instead?