7

GUI elements example image

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)

Community
  • 1
  • 1
Whirlwind
  • 14,286
  • 11
  • 68
  • 157
  • Have you ever tried scaling a node, based on device, when you first add it? Any subsequent animation (texture changes) will carry the preset scale factor. – sangony Mar 22 '15 at 23:18
  • Sure, and that is what I am trying to avoid if possible, but that's certainly one option to handle this. I am just into finding easier/automatic solution which will save me of many calculations/manually setting scale properties based on device etc. – Whirlwind Mar 23 '15 at 09:53
  • Are you scaling your scene at all, or does your scene and view have equal sizes (I.e no scaling). – Epic Byte Mar 23 '15 at 20:29
  • @EpicByte from what I've tested, same size for scene and view don't give desired effect like from 3rd picture. So the answer is no, if I understand you well, at first I didn't scaled anything. Scene was initialized with view.bounds.size. But later on, I tried to use something like CGSizeMake(375,667) when initializing scene in view controller and that gave me partially good results. I will update my question with that info later on tonight... – Whirlwind Mar 24 '15 at 17:57

2 Answers2

5

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.

Community
  • 1
  • 1
Skyler Lauren
  • 3,792
  • 3
  • 18
  • 30
  • Thanks for the answer Skyler, yeah right now it seems that something is wrong with iPhone 6+ simulator when it comes to atlases usage. The truth is that differences are not so much noticeable when using @2x assets, but it would be better if this was fixed because when high detail bitmap image is scaled, artefacts can occur. I tried solutions from those links, and many others, but even when using Texturepacker which properly generates atlases, or choosing correct atlas based on screen's scale factor, it seems that iPhone 6+ simulator only likes x2 images. – Whirlwind Mar 26 '15 at 11:29
  • To avoid artefacts during scaling, I plan to use dynamic image generation form vector. First, I create images using PaintCode. Then the corresponding code is exported to generate a view sized to the needs. At last a png file is created from this view then used on textures. – Dominique Vial May 23 '15 at 04:09
  • In fact, last version of PaintCode, 2.3.2 already bring the generating phase! – Dominique Vial May 23 '15 at 06:13
5

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

enter image description here

iPhone 5

enter image description here

iPhone 6

enter image description here

iPhone 6+

enter image description here

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];
}
sangony
  • 11,636
  • 4
  • 39
  • 55
  • 1
    Nice solution. The only issue would be that you have to use scaleFactorX and scaleFactorY for every position. You think a category would make this even easier? – Skyler Lauren Mar 26 '15 at 03:45
  • @sangony Do you mean to make all images with 3x suffix? Or just to make them at the sizes of 3x images and to use standard naming like for 1x? I like the solution because it seems that positioning and scaling is done properly and even on 4s everything looks as it should(no black bars). Only drawback is memory consumption because of 3x assets and little more coding, but I will test this as well to see the results. – Whirlwind Mar 26 '15 at 11:12
  • @Whirlwind - No 3x image suffix. Just create all images sizes as if they are meant to be used for 3x. The scaling in the code takes care of the rest without any noticeable loss of resolution. This also allows you to use a texture atlas with this method. – sangony Mar 26 '15 at 12:57
  • @SkylerLauren - There's bound to be some additional coding to implement this kind of compatibility. It would be good for Apple to come up with some background functionality to automate the process but I'm not holding my breath for that one. If it were not for having to include the iPhone 4 series, only a single scale factor per device would be needed instead of 2 (x,y). It's the iPhone 4 length that requires an additional y factor to be included. What do you mean by a "category" to make it easier? – sangony Mar 26 '15 at 13:01
  • @sangony Ok, I get it. I've tested this and it's a workable solution so I will upvote it until I make final decision what to use. I have to see how much complexity this will add when I use actions. I am thinking maybe to use Skyler's solution, but to handle 4s in the way you handle it.The best solution would be that Apple allowed developer to declare supported devices by screen size, but that would probably make some another issues(maybe commercial) or who knows what. – Whirlwind Mar 26 '15 at 13:36
  • I was going to put together a category and was going show you, but I ran across a logic problem in your solution. In your example you said to create all assets at 3x size but not to use the @3x extension. If it was scaling correctly your player guy (300 x 300) would be at (100 x 100) points and would not fill up the screen like it is in the screen shots. Unless I miss understood. – Skyler Lauren Mar 27 '15 at 02:38
  • I ended up with with Skyler's solution + custom logic for handling 4s . For readers: Sangony's solution is one possible way to solve this problem, especially with category usage like pointed above. – Whirlwind Mar 28 '15 at 01:59
  • @sangony. Nice solution. To improve it I suggest using PaintCode to generate PNG file from vector picture. Thus you avoid the scaling operations. – Dominique Vial May 23 '15 at 06:16