Constraining proportions of GUI elements in Spritekit game - ios

I apologize in advance because of huge post, but everybody who ever tried to make some kind of universal app knows that this is pretty problematic stuff, so please be easy on me...
The goal
What I am trying to achieve (shown on image above) is to use #2x assets on both iPhone 5 and 6, and maintain same look of an app. And if possible, to do all that without manually calculating scale and position properties of nodes based on detected device... So in short, how to achieve that app automatically constrain proportions of, and between elements (and scene)? Also I would like to have same look of an app on iPhone 6+ using #3x assets, but because of simplicity I've concentrated only on iPhone 5 an 6.
What I have found on the web is that some people saying that this (downsampling) is done automatically by iOS, for example they suggest this:
"Make #2x assets at the size for iPhone 6, and then iOS will do
downscaling automatically for iPhone 5".
But that's obviously not true when it comes to Spritekit scene, or I am missing something.
The problem
Even though iPhone 6 and iPhone 5 have same aspect ratio and same PPI, using the same asset won't look the same compared to the scene size (look menu sprite on 1st and 2nd image compared to the scene size) because PPI are related to pixel density, and iPhone 6 has more space (bigger diagonal, more inches) which means it has more pixels than iPhone 5. And this is where my problem comes from and I don't know what would be an efficient way to handle it.
What I have done so far
The second image is not a problem for GUI, but for a gameplay, in my case it is, because I want same look and feel on different devices. Just look first and third image.
Thanks to Skyler Lauren's suggestion I've managed to have same look of an app across all iPhone 5 devices either on 7.1 or 8.1 systems as well as on iPhone 6. So now, the problem is how to adapt this code to works with iPhone 6+ using #3x textures, as well as on iPhone 4s. Here is the solution for iPhone 5 and 6:
View controller.m
GameScene *scene = [GameScene sceneWithSize:CGSizeMake(375,677)];//fixed instead of view.bounds.size
scene.scaleMode = SKSceneScaleModeAspectFill;
So the scene always has fixed size at iPhone 6 dimensions and view size is changing according to device. I am using assets catalog for launch images and not xib file. Images are sized at recommended size - 640x960px for 4s, 640x1136px for 5, 750x1334px for 6 and 1242x2208 for 6+ model. Assets are sized for iPhone 6 so for that model there is no scaling at all.
Related to 4s model, when I am using this method described above, there are two black bars from each side...
So far I've tested this only on simulator and iPhone 6 device (what I see looks like on first image either on device or simulator).
Question
For now as I said everything works on iPhone 4s(with two black bars because of different aspect ratios), 5, 6 using #2x assets, but how make everything to work with iPhone 6+ using #3x assets ?
If I use 375x667 dimensions for the scene, then everything is positioned properly and has good proportions, but quality suffers (because of upscaling #2x)

Uniform GUI and Game Play
As far as I can tell the best way to handle a uniform GUI and game play is to set your scene size (regardless of device) and let SpriteKit scale from there.
GameScene *scene = [GameScene sceneWithSize:CGSizeMake(375,677)];//fixed instead of view.bounds.size
scene.scaleMode = SKSceneScaleModeAspectFill;
That is the points for an iPhone 6. Because SpriteKit works in points but devices display in pixels the scene size will be 750px x 1354px pixels for #2x devices and 1125px x 2031px for the iPhone 6+ (the device in pixels is actual 1080 x 1920).
How does this work with assets?
Well it works rather well for 1x and 2x assets in a .atlas folder. Again because everything is converted to points you can have button.png and button#2x.png in a texture atlas and the positioning will be the same and look the same for all iPhones.
What about #3x?
This is a better question for Apple. Apparently SpriteKit does not support #3x images in a texture atlas. There are a few question already on SO that have tried to address this.
One example...
Spritekit - not loading #3x images from SKTextureAtlas
It appears it hasn't been fixed in Xcode 6.2 either. If you are reading this and want #3x it might be worth filing a radar with Apple. One thing to note is that I didn't see anywhere in the docs claiming that texture atlases are suppose to support #3x (or even #2x for that matter) When they are supported you won't have to do any changes to your code. Just throw the #3x assets into your .atlas folders.
What can/should I do about the #3x assets?
My advice is to not worry about it and run #2x assets. SpriteKit does a decent job scaling images and there are a lot of apps out there that don't support #3x. As a fellow iPhone 6+ owner it is just something I have learned to live with at the moment. I hope that Apple supports the #3x images in the .atlas folder very soon.
Warnings
You are asking all devices to scale down with the exception of the iPhone 6 (and scaling up iPhone 6+) In most cases you shouldn't notice a big difference in your art (or performance from my testing), but as you know if you shrink images they may look slightly different. Also there is the black bar issue on the 4s which I don't have a solution for you at the moment.
Closing Points
You will get the exact same look and feel in your app across all devices if you set the scene size and set your scene to SKSceneScaleModeAspectFill however you are asking older devices to scale down. It saves a ton of time and planning with minor draw backs as far as I see it.
Hopefully that helps and the best of luck on your app.

Your main issues seem to be handling the various screen sizes in relation to your image assets and screen object coordinates.
My solution is to write your code as if you are coding for the iPhone 6 plus. Make all your images #3x size and your screen layout coordinates for the iPhone 6 screen size.
With just a bit of code I was able to get an uniform layout for the iPhone 6 plus, 6, 5 and 4 screen sizes. I have included screen shots for each one. The character image is 300x300. The 2 button images are 100x100.
static const float kIphone6PlusScaleFactorX = 1.0;
static const float kIphone6PlusScaleFactorY = 1.0;
static const float kIphone6ScaleFactorX = 0.9;
static const float kIphone6ScaleFactorY = 0.9;
static const float kIphone5ScaleFactorX = 0.772;
static const float kIphone5ScaleFactorY = 0.772;
static const float kIphone4ScaleFactorX = 0.772;
static const float kIphone4ScaleFactorY = 0.652;
#import "GameScene.h"
#implementation GameScene {
float scaleFactorX;
float scaleFactorY;
SKSpriteNode *node0;
SKSpriteNode *node1;
SKSpriteNode *node2;
SKLabelNode *label0;
}
-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor blackColor];
if(view.frame.size.height == 736) {
NSLog(#"iPhone 6 plus");
scaleFactorX = kIphone6PlusScaleFactorX;
scaleFactorY = kIphone6PlusScaleFactorY;
}
if(view.frame.size.height == 667) {
NSLog(#"iPhone 6");
scaleFactorX = kIphone6ScaleFactorX;
scaleFactorY = kIphone6ScaleFactorY;
}
if(view.frame.size.height == 568) {
NSLog(#"iPhone 5");
scaleFactorX = kIphone5ScaleFactorX;
scaleFactorY = kIphone5ScaleFactorY;
}
if(view.frame.size.height == 480) {
NSLog(#"iPhone 4");
scaleFactorX = kIphone4ScaleFactorX;
scaleFactorY = kIphone4ScaleFactorY;
}
node0 = [SKSpriteNode spriteNodeWithImageNamed:#"Pic"];
node0.position = CGPointMake(self.size.width/2, self.size.height/2);
[node0 setScale:scaleFactorX];
[self addChild:node0];
node1 = [SKSpriteNode spriteNodeWithImageNamed:#"button0"];
node1.position = CGPointMake(100*scaleFactorX, 100*scaleFactorY);
[node1 setScale:scaleFactorX];
[self addChild:node1];
node2 = [SKSpriteNode spriteNodeWithImageNamed:#"button1"];
node2.position = CGPointMake(314*scaleFactorX, 100*scaleFactorY);
[node2 setScale:scaleFactorX];
[self addChild:node2];
label0 = [SKLabelNode labelNodeWithFontNamed:#"HelveticaNeue-Bold"];
label0.text = #"Big Game Menu";
label0.fontSize = 48*scaleFactorX;
label0.fontColor = [SKColor whiteColor];
label0.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
label0.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
label0.position = CGPointMake(207*scaleFactorX,690*scaleFactorY);
[self addChild:label0];
}
iPhone 4
iPhone 5
iPhone 6
iPhone 6+
Notice how even the text label is scaled down correctly not just by font size but also location.
For your reference, I did use the standard code in my GameViewController because I find it easier to work with a simpler version. This is the code I used to present my SKView:
- (void)viewDidLoad {
[super viewDidLoad];
SKView * skView = (SKView *)self.view;
SKScene *scene = [GameScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}

Related

SpriteKit - How do I handle multiple screen sizes?

My new game is finished and now I'm just testing on multiple real devices. I came across multiple issues after testing. The big issue is how to handle screen sizes. The game is how I want it to look on 6 Plus/6s Plus, but not on 6s, 6, 5s, 5, 4s, 4, or iPad.
I found these two answers but I don't know how to implement them: How to support multiple screen sizes in SpriteKit?
and SpriteKit how to get correct screen size
I really would like any type of help, because this is irritating me.
Its quite a commonly asked question.
What we usually do in SpriteKt is to give the SKScene a fixed size and let SpriteKit do the scaling for you on different devices.
So basically we have 2 ways to do it correctly
1) Set scene size to iPad (e.g 1024x768 -landscape, 768x1024 - portrait). This was the default setting in Xcode 7.
You than usually just have/show some extra background at the top/bottom (landscape) or left/right (portrait) on iPads which gets cropped on iPhones.
Examples of games that show more on iPads / crop on iPhones:
Altos Adventure, Leos Fortune, Limbo, The Line Zen, Modern Combat 5.
2) Apple changed the default scene size in xCode 8 to iPhone 6/7 (750*1334-Portait, 1337*750-Landscape). This setting will crop your game on iPads.
Examples of games that show less on iPads:
Lumino City, Robot Unicorn Attack
Choosing between those 2 options is up to you and depends what game you are making. I usually prefer to use option 1 and show more background on iPads.
For scale mode it is usually best to either use .aspectFill or .aspectFit.
You would use the Universal asset slot and/or device specific images. This way you will have a consistent experience on all devices
Spritekit scale full game to iPad
How to make SKScene have fixed width?
Hope this helps
In my case I have a portrait game, and I want to keep the height fixed while showing more or less content left/right depending on the device's screen.
For that in Xcode's Scene Editor I added and set an SKCameraNode for my scene, and set the scene size to the "widest" aspect ratio device (iPhone X ), that is 2436x1125 pixels.
Then set a custom class for the scene and override sceneDidLoad:
override func sceneDidLoad()
{
super.sceneDidLoad()
self.size.width = self.size.height * (UIScreen.main.bounds.size.width / UIScreen.main.bounds.size.height)
self.scaleMode = .aspectFit
}
If I want to preview how my game will look in the "narrowest" device (any iPad has a 1.5:1 ratio) I just temporarily change my scene's width to around 1500 pixels.
Note: SKView's contentMode changes nothing.
I was reminded of checking the screen size and changing the node sizes, I found this as an answer: how to check screen size of iphone 4 and iphone 5 programmatically in swift
All I had to add was this in my GameScene and it was called in every .swift:
extension UIScreen {
enum SizeType: CGFloat {
case Unknown = 0.0
case iPhone4 = 960.0
case iPhone5 = 1136.0
case iPhone6 = 1334.0
case iPhone6Plus = 1920.0
}
var sizeType: SizeType {
let height = nativeBounds.height
guard let sizeType = SizeType(rawValue: height) else { return .Unknown }
return sizeType
}
}
And this
if UIScreen.mainScreen().sizeType == .iPhone4 {
// Make specific layout for small devices.
}

How to scale app so iPhone 6 is identical(zoomed) to iPhone 5 SpriteKit

Ive been making an app using sprite kit in Xcode 7. I made all my sprite nodes positioned around by referencing other sprite nodes and self.frame
for example
self.block.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)
When I came to test my app on an iPhone 6 Plus (I previously tested on iPhone 4s but it worked nicely on an iPhone 5) My app didn't scale the nodes, Just the space Between them. The game relies on everything being the same otherwise people playing on an iPhone 6 would find it easier then people playing on a smaller device. Is there anyway that I could make the iPhone 6 view just a stretched version of the iPhone 5 view? Thanks Guys!
It's best not to position your sprites using self.frame as a reference. Much better to treat the scene as the "true" coordinates and let SpriteKit scale your scene for you.
ie. Specify the scale mode of your scene when you create it (probably in the view controller viewDidLoad). I prefer AspectFill for the scale mode, which makes sure the screen is filled, but you will lose the edges on some devices. Just make sure the core gameplay is in the middle of the scene.
scene.scaleMode = .AspectFill
Then, position your sprites using the size of your scene for reference. On a landscape scene, I tend to use a scene size of 1024x768 (iPad size).
Hope that helps.
Another way: If you remove the iPhone 6 and iPhone 6+ launch images from the project then those devices will switch to "zoom mode", which is exactly the same view as on iPhone 5, just bigger (well, zoomed).
Just look in your project for "Images.xcassets", there is a image-set called "LaunchImage". In this image-set are multiple other images, just remove the one for iPhone 6 and 6+.

Easier way to scale assets for various iPhone sizes?

When creating apps, is there any easier way to scale images, aside from creating and loading different assets based on the detected device? It just seems kind of ridiculous to have to go through the pain of creating different images for iPhone 6+, 6, 5, 4, and also tweaking various positioning on the screen based on the device.
Also, I'm using Spritekit particle effects, and those have to be re-created too for different devices.
I know you can just have Xcode upscale iPhone 5 assets, but obviously the trade-off is image sharpness. It would be nice if you could just design everything for the largest iPhone, and have it scale down automatically for other devices. There isn't such a way, is there?
define a micro in ViewController.m file
#define IS_WIDESCREEN ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )
invoke below method
now see the code below this method work fine for every iPad device if you have aspect ratio of 1024*768 but little bit squashed for iPhone5 ,5s,6,6+ but this method is the fastest way to create game for all your device because its squashed SKScene based on screen ratio.
-(void)addAspect
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
if (IS_WIDESCREEN) {
_globalscaleX=(1024.0f*2)/(568*2);
_globalscaleY=(768.0f*2)/(320*2);
LoaderScreen = [loadingScene sceneWithSize:CGSizeMake(_yourSKView.bounds.size.width*_globalscaleX, _yourSKView.bounds.size.height*_globalscaleY)];
//LoaderScreen.scaleMode = SKSceneScaleModeAspectFit;
LoaderScreen.scaleMode = SKSceneScaleModeFill;
} else {
if([[UIScreen mainScreen] bounds].size.height==480)
{
_globalscaleX=(1024.0f*2)/(480*2);
_globalscaleY=(768.0f*2)/(320*2);
}
else
{
_globalscaleX=(1024.0f*2)/(568*2);
_globalscaleY=(768.0f*2)/(320*2);
}
LoaderScreen = [loadingScene sceneWithSize:CGSizeMake(_yourSKView.bounds.size.width*_globalscaleX, __yourSKView.bounds.size.height*_globalscaleY)];
LoaderScreen.scaleMode = SKSceneScaleModeFill;
}
} else {
_deviceType=ipad;
_globalscaleX=1.0f;
_globalscaleY=1.0f;
LoaderScreen = [loadingScene sceneWithSize:CGSizeMake(_yourSKView.bounds.size.width, _yourSKView.bounds.size.height)];
LoaderScreen.scaleMode = SKSceneScaleModeAspectFill;
}
// Present the scene.
[_yourSKView presentScene:LoaderScreen];
}
2) now second solution and best solution (quality wise) is that go two textures atlas where first texture atlas follow iPad series ratio and second 5 and 6 series because 5 and 6 follow same aspect ratio so you don't need to worry but for 4s squashed little SKScene (that can't be fixed without three texture atlas)
It is all about the Aspect Ratio for the different models.
Luckily there only two for iOS devices 4:3 or 16:9. This would be much harder for android devices as there are more different aspect ratio's.
Check here for info on that :
iOS Device Resolution
You could take the highest res images for each aspect ratio and use texturepacker to do the scaling.
This will cost some money but it's worth it.(not affiliated with the company).
Or checkout the free android stuff!:
Android Asset Studio

Spritekit multiple device resolution detection

I have backgrounds for iPhone 4s, 5, 6, 6plus, iPad, iPad retina. My question is how to distinguish them from each other since many are retina(#2x). Can I use the same filename for the background and add #2x, #3x? It seems to pick up the wrong backgrounds from the #2x ones when I do this(i,.e iPhone 5 on a iPad Retina). There must be an easier way then checking every single resolution and pulling that image. Clarity on this would be very helpful thanks!
In the scene itself I use
SKSpriteNode * MainMenuBackground;
MainMenuBackground = [SKSpriteNode spriteNodeWithImageNamed:#"Background1"];
so for the other devices can I do these filenames:
Background1#2x~iphone5.png (1334 x 750)
Background1#2x~iphone6.png (1136 x 640)
Background1#3x~iphone6plus.png (2208x1242)
Background1~ipad.png (1024x768)
Background1#2x~ipadRetina.png(2048x1536)

Sprite Kit art assets naming convention

Making my 1st Sprite Kit game & almost finished it. Just getting my images finalized. Problem is, with the addition of iPhone 6 & 6 Plus, I am having issues figuring out how to name my game assets. For instance, one of my several background images is called "1MBack.png". Sometimes people write "Default–568h#2x.png" and I try to figure out if I need the "Default–568h" part in my game asset names. The naming convention I used for my art is as following:
<< 1x (iPhone 2G, 3G, 3GS) = 1MBack.png >> << 2x (iPhone 4, 4s) = 1MBack#2x.png (640x1136) >> << Retina 4 2x (iPhone 5, 5s) = 1MBack#2x.png (750x1334) >> << 3x (iPhone 6 Plus) = 1MBack#3x.png >>
Did I name my image's correctly? What about iPhone 6 - do I name an image for it 1MBack#2x.png or 1MBack-667h#2x.png? It's important to me to get these names right because I sat for quite a while making each asset for each screen and want them utilized accordingly.
One final question: when making the assets in photoshop, as I was sizing them, I choose PPI according to the PPI of the screen the asset would be used on. So the 1x (iPhone 2G, 3G, 3GS) 1MBack.png has a PPI of 163. The 2x (iPhone 4, 4s) 1MBack#2x.png has a PPI of 326. Is this correct? I'm worried about this also, because I read someone writing to make assets in PPI 72. Please clarify this.
Set different name for Widescreen images(new iPhones) like name-retina
-(void)didMoveToView:(SKView *)view
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
CGSize result = [[UIScreen mainScreen] bounds].size;
if (result.height == 480) {//Old iPhone detected
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"1MBack"];//2G, 3G, 3GS
//By the way you have to set different sprite position for devices here
}else{//New iPhone detected
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"1MBack-retina"];//5, 5S, 6, 6Plus
}
}
}

Resources