SpriteKit coordinate system messed up - ios

In portrait mode, i created a SKShapeNode at the position of 0,0. Yet it seems that it does not appear in the screen at all.
This is the code i am using now
let test = SKShapeNode(rect: CGRectMake(0, 0, 10, 10))
test.fillColor = SKColor.blackColor()
self.addChild(test)
The background colour white, so the shape node will definitely appear if it was on the screen
Yet 0 node appears in the screen?
children.count returns 1
And this is how i display the scene
override func viewWillLayoutSubviews () {
super.viewWillLayoutSubviews()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.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 = .AspectFill
skView.presentScene(scene)
}
}
The SKShapeNode is drawn, but it is outside the screen?
EDIT 1: I change the scale mode to .AspectFit, and this is what happened
Is the game forcefully running in landscape although the game is suppose to run in portrait?

Base on #fuzzygoat answer at https://stackoverflow.com/a/24170460/1879382.
Simply open GameScene.sks, then change the value of x and y to the appropriate value needed.
For me, I used 320 x 568 since i run the game on iPhone 5S

Related

Swapped width and height values

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. */
}

Adding a background image to a SpriteKit project

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

Universal app background SpriteKit

I'm working on universal game (iPhone and iPad) with SpriteKit and I have issue with background size.
I created image size 2048x1536 and more image for iPhone devices in size 1136x852 and still the background is cropped on iPhone.
How can I fix it?
let screenHeight = screenSize.size.height
//Backgorund
var dayBackground = SKSpriteNode(imageNamed:"BackgroundDay")
dayBackground.zPosition = -1
dayBackground.position = CGPoint(x:size.width/2, y:size.height/2)
dayBackground.anchorPoint = CGPointMake(0.5, 0.5)
dayBackground.size = self.frame.size
GameViewController:
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.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 = .AspectFill
skView.presentScene(scene)
}
iPhone 6 Plus:
iPad Pro:
iPhone 4s:
Assets file:
#1x size is 2048x1536 and #3x is 1136x852
The sense scale is .AspectFill with .ResizeAll the position of SKSprtieNode it not like what I want.
Thanks!
I tried to split the image to parts but still is cropped
I think is happen because the iPhone 6 plus screen size is 736x414 and the scene size is 1024x768, and if I change the scene to the current device I need to position the sprites for each screen
The devices's screen size ratios are different, and one will have to stretch dimensions to fit the screen, and this may look weird. But you can use .Fill if you are sure you like this option. The smoothest alternative is to remain using .AspectFill and create an image for each screen dimension. Be creative! Good luck!

How to change SKView letterbox color when scaleMode .AspectFit is used

I'm using standard full screen SpriteKit view from the boilerplate code (new game iOS project), and when I use
scene.scaleMode = .AspectFit
to make sure that scene fits, remaining (letterboxed) area is colored in black.
I've tried these options to change the color:
let skView = self.view as SKView
// was hoping this one would work
skView.backgroundColor = UIColor.redColor()
// didn't expect this would work, since scene is scaled anyways
scene.backgroundColor = SKColor.redColor()
Also tried to change SKView background color in storyboard editor to another color, but no luck.
Any tips on where to look to change the color of the letterboxed area?
I think the should be a way to set the colour or place sprites in the ears of the letterhead... but it seems that there isn't. Also, I disagree with LearnCocos2D. There's nothing wrong with AspectFit. It has its use.
What I've done is use AspectFit but also calculate the size of the scene based on screen size. This isn't that helpful for an OSX game but it'll work fine for iOS.
func createScene() -> GameScene {
let screenSize = UIScreen.mainScreen().bounds.size
var size:CGSize = CGSizeZero
size.height = Constants.sceneHeight
size.width = screenSize.width * (size.height / screenSize.height);
return GameScene(size: size)
}
override func viewDidLoad() {
super.viewDidLoad()
let scene = self.createScene()
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFit
skView.presentScene(scene)
}
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.
The letterboxing is create IN the view so there is no way around it. That said there is often the need for an .aspectFit setting if you want to use a specific scene size and universal coordinate system. If that is your case, the following should help.
You need to figure out how much a standard .aspectFit would need in padding to remove the letter boxing from your current device and add that to your scene. You finish with a scene that is lighter a little bigger or a little wider then your original sizes but the scene has at least the same size as your original scene size. You can then move the anchorPoint to the original (0, 0) coordinates or the (0.5, 0.5) one depending on what coordinate system you want to use for your game.
The code for this is convoluted and could change in time. If you want a quick fix, I created a Framework exactly for that purpose. It's small but portable :)
https://github.com/Tokuriku/tokuriku-framework-stash
Just:
Download the ZIP file for the Repository
Open the "SceneSizer" sub-folder
Drag the SceneSizer.framework "lego block" in your project
Make sure that the Framework in Embedded and not just Linked
Import the Framework somewhere in your code import SceneSizer
And you're done, you can now call the sizer Class with:
SceneSizer.calculateSceneSize(#initialSize: CGSize, desiredWidth: CGFloat, desiredHeight: CGFloat) -> CGSize
Documentation is in the folder for a clean and full use with a standard scene. Hope this helps!

What is wrong with the coordinate system in this SpriteKit setup?

I created a SKView which presents an SKScene subclass like this:
SKView *skv = [[SKView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:skv];
self.skScene = [[TestScene alloc] initWithSize:v.bounds.size];
[skv presentScene:_skScene];
Then to see coordinate system origin, I add a small 10x10 colored square to the scene.
SKSpriteNode *ori = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(10,10)];
[self addChild:ori];
You can see green square in lower left corner:
From what I thought, SpriteKit coordinate system is such that origin is always in center. But in the scene the origin is in lower left corner. When I add a child node to {0,0} it appears in lower left corner as well.
When I add a SKSpriteNode and position it {0,0} in the scene, it appears in lower left corner. But it is centered around scene origin in lower left corner (clipped off half to left, and half to bottom).
But now it gets more confusing. When I add a SKSpriteNode to another SKSpriteNode, the sub-sprite is CENTERED in the parent.
So does it mean the Scene coordinate system works different than the sprite coordinate system?
Recap:
When I position a sprite in scene at {0,0}, it appears in bottom
left, clipped off 50% (centered around origin).
When I position a sprite in a sprite at {0,0}, it appears centered in the sprite.
Is my Scene configured wrong or is this the way it works?
This is the way it works. All OpenGL views (the 2D ones at least) have their origin at the lower left corner.
The position of your sprites is correct too. Both are located at 0,0 by default. The texture of the sprites is drawn relative to the node position based on the anchorPoint factor. It's default value of 0.5, 0.5 places the texture centered on the node's position.
You can change the anchorPoint of the scene to 0.5,0.5 which will move the sprites to the center of the scene. You can also change the sprite's anchorPoint though that isn't recommended since it affects things like rotation, scale, collision detection and child node position.
you can add this code to GameViewController.swift :
override func viewDidLoad() {
super.viewDidLoad()
// Detect the screensize
var sizeRect = UIScreen.mainScreen().applicationFrame
var width = sizeRect.size.width * UIScreen.mainScreen().scale
var height = sizeRect.size.height * UIScreen.mainScreen().scale
// Scene should be shown in fullscreen mode
let scene = GameScene(size: CGSizeMake(width, height))
// Configure the view.
let skView = self.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 = .AspectFill
skView.presentScene(scene)
}
What I did was to set the size of the SKScene you want to show to the bounds.size of the Viewcontroller you want to show the scene from. Something like this:
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
scene.size = skView.bounds.size
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
I hope this worked for you as well, if I made any mistakes here, please let me know as well :)

Resources