Box2D coordinate system concepts: PTM_RATIO and retina display - ios

Recently I published a question regarding this topic and I received a useful answer, but my experimentation points me in a different way that I don’t understamd.
From the answer is clear that we should use the same PTM_RATIO for retina and non-retina devices. However we may double it from iPhone to iPad if we want to show the same portion of the world. In my case I used 50 for iPhone and 100 for iPad because Box2d simulations works better if the bodies are between 0.1 and 10m and the main sprite is about 2m.
I used Physics Editor to build the fixtures using GB2ShapeCache without success for retina devices. Then I decided to feed Box2D coordinates directly and I reached strange conclusions that I would like to clarify.
I created a debug method (independent from any sprite) to draw a single line from 1/3 of screens height to 1/3 of screens wide.
- (void)debugGround
{
// iPad: 1024x768
// iPhone: 480x320
CGSize winSize = [CCDirector sharedDirector].winSize; // unit is points
b2EdgeShape groundShape;
b2FixtureDef groundFixtureDef;
groundFixtureDef.shape = &groundShape;
groundFixtureDef.density = 0.0;
b2Vec2 left = b2Vec2(0, winSize.height/3/PTM_RATIO);
b2Vec2 right = b2Vec2(winSize.width/3/PTM_RATIO, winSize.height/3/PTM_RATIO);
groundShape.Set(left, right);
groundBody->CreateFixture(&groundFixtureDef);
}
If Box2D takes coordinates in points and converts them dividing by PTM_RATIO, the result should be the same for iPhone and iPad retina and non retina.
The result for iPad non retina is as expected:
But for iPhone retina and iPad retina, the fixtures are doubled!!
The most obvious correction should be divide by 2, this means dividing by CC_CONTENT_SCALE_FACTOR.
I managed to make it work for all devices refactoring the code to:
- (void)debugGround
{
CGSize winSize = [CCDirector sharedDirector].winSize;
b2EdgeShape groundShape;
b2FixtureDef groundFixtureDef;
groundFixtureDef.shape = &groundShape;
groundFixtureDef.density = 0.0;
b2Vec2 left = b2Vec2(0, winSize.height/3/PTM_RATIO/CC_CONTENT_SCALE_FACTOR());
b2Vec2 right = b2Vec2(winSize.width/3/PTM_RATIO/CC_CONTENT_SCALE_FACTOR(), winSize.height/3/PTM_RATIO/CC_CONTENT_SCALE_FACTOR());
groundShape.Set(left, right);
groundBody->CreateFixture(&groundFixtureDef);
}
I also managed to display correctly the lower platforms dividing by the scale the vertex, the offsets and anywhere I use PTM_RATIO to convert to Box2D coordinates.
It is supposed I shouldn’t use CC_CONTENT_SCALE_FACTOR by any means to multiply positions because GL functions already take this into consideration.
Can anyone clarify this behavior? In which concepts I’m wrong?
I hope this helps the community to understand better Box2D coordinate system.

you misunderstood: GL functions (this includes ccDraw* functions!) require multiplication with content scale factor because GL works on pixel resolution, whereas UIKit views and cocos2d nodes use point coordinates.

Related

Constraining proportions of GUI elements in Spritekit game

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];
}

Tool or technique for examining retina pixels iOS?

I am working on an iOS app that requires very precise drawing and would like to have some way of visually inspecting what, exactly, is being drawn to each (physical) pixel on my iOS device screen. This would be similar to the Pixie app dev tool on OS X, but for iOS -- instead of simply blowing up and anti-aliasing the screen, it would show a very clear grid of each and every pixel, and what shades/colors are being drawn to those pixels.
Does anyone know of such a tool or technique?
Here's a screenshot from Pixie on OS X on my Retina MacBook that shows the kind of output I'm looking for. You can clearly see, for example, that the designers specified 1 point (which spans two retina pixels) for the "minus" sign in the yellow minimize icon.
Assuming that you are using Quartz to do your drawing to a UIView you can draw on pixel boundaries and not on point boundaries by using CGContextScaleCTM. Here is a rough outline of what you would do to do this with a screen shot of your app. You could also have the user take a screen shot of a different app and then import it into yours.
-(void)drawRect:(CGRect)rect
{
UIView* rootView = <GET_YOUR_ROOT_VIEW>;
//You will probably want to change rect so you don't get distortion
//This assumes that this view is the same size as the screen
[rootView drawViewHierarchyInRect:CGRectMake(0,0,rect.size.width*8, rect.size.height*8, afterScreenUpdates:YES];
CGContextRef cxt = UIGraphicsGetCurrentContext();
//Assumes this is #2x retina. You should check the contentScale to be sure
//and change accordingly
CGContextScaleCTM(ctx, 0.5, 0.5);
//Since we made the screen shot be 8x bigger the pixels
//on an #2x device are in increments of 4
for (int x = 0; x < rect.size.width*8; x+=4)
{
//Draw a line at x to ctx
}
for (int y = 0; y < rect.size.height*8; y+=4)
{
//Draw a line at y to ctx
}
}
I am sorry that I don't have time to actually write and test this code myself, so there is probably a few little issues with it. But this should get you going in the right direction.
Also, since you are blowing up the image you actually don't need to change the scale with CGContextScaleCTM, you just need to draw your lines at the right intervals.

different screen sizes with corona sdk

I am making my first iphone game using corona sdk and would like it to run on as many devices as possible (phones + tablets). However I am not sure how to deal with different screen sizes and resolutions. I developed my game for the iPhone 5 using corona simulator and it works fine on that device. When I tried it on lower resolution devices like the iPhone 4 I get 2 black rectangles on each side. I tried creating 2 different backgrounds with different resolutions and added this in my config.lua:
imageSuffix = {
["#2x"] = 2
}
However this does not seem to change anything... I am not sure what height and width I should set in content in the config.lua file and what heights and widths I should set for the backgrounds. I am sorry if these questions are stupid, I am just starting.
Thanks in advance!
It sounds like you need to fully read up on config files and dynamic scaling.
The question is a little to broad as such I suggest you read this article about "the ultimate config/modernizing the config".
Some screens are wider while others are more narrow. If we take
resolution out of the equation, its easier to visualize the screens.
Corona makes it easy to take resolution out of the picture using
Dynamic Scaling. With Dynamic Scaling, you can use a common set of
screen coordinates and Corona will automatically scale the text and
graphics for different resolution screens. It can scale upwards or
downwards depending on your starting point. It also can substitute
higher resolution images when it needs to scale up. This is all
managed by a Lua file in your project folder called config.lua.
Since available resolutions vary considerably, it’s helpful to use the
same scale for each device. It doesn’t matter if you’re on an iPhone
3GS at 320×480 or a Retina iPad at 1536×2048, the location (0,0)
represents the top-left corner and (320,480), in vertical portrait
mode, is the bottom-right corner. The screen center is (160,240).
Each point, in this case, is one pixel on a lower-resolution device
like the 3GS, which has a native screen resolution of 320×480, while
each point is four pixels on a Retina iPad. Don’t worry about the math
— Corona will handle it for you.
Source: http://coronalabs.com/blog/2012/12/04/the-ultimate-config-lua-file/
This goes through the creation of a config file that fully utilize the dynamic scaling and image scaling.
local aspectRatio = display.pixelHeight / display.pixelWidth
application = {
content = {
width = aspectRatio > 1.5 and 320 or math.ceil( 480 / aspectRatio ),
height = aspectRatio < 1.5 and 480 or math.ceil( 320 * aspectRatio ),
scale = "letterBox",
fps = 30,
imageSuffix = {
["#2"] = 1.8,
["#4"] = 3.6,
},
},
}

Does sprite-kit make images larger?

I'm working on a sprite kit game and I can't figure out why it won't scale my image the way I want it to. The game is in the landscape orientation. This is the code I'm having a problem with:
SKSpriteNode *Pathway = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"path.png"] size:CGSizeMake(568, 220)];
Pathway.zPosition = -1.0;
Pathway.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
Pathway.name = #"Pathway";
[self addChild:Pathway];
Basically I want the texture to be like this on the screen:
But for some reason, even when I change the size of the texture (568, 220), it scales the image and doesn't fit on the screen.
I tried using [Pathway setScale:0.7]; which was close to the size I was looking for, but I need to to be exactly 568 x 220. How come it keeps distorting my image even when I'm setting its size of 568 x 220?
If more info is needed please let me know, I think this should suffice.
I'm pretty sure you are testing this on device with Retina display.
Rename path.png to path#2x.png and use [SKTexture textureWithImageNamed:#"path"] instead of [SKTexture textureWithImageNamed:#"path.png"]. This should solve you problem.
Read Apple's tutorial Supporting High-Resolution Screens In Views for more info.
Using setScale directly scales content proportional and thus resulting in the sprite that follows same aspect ratio as it is before scaling.
Try setting xScale and yScale properties to scale irrespective of aspect ratio

Adaptive Positioning Based on iOS Device

I made an iPhone game a few months ago, and am now trying to port it as a universal app to both the iPad and iPhone 5 with Cocos2D. I was wondering if there was a simple-ish way to determine where an object should be placed based on the device running the game.
I could use if statements to figure out which device the game is running on, so when I get the correct sized images for the device I could have separate positions for each object, but it seems like there would be a maths formula which would allow me to use a lot less code. Obviously something like a full screen background is very simple, because it just needs to be centred with:
[background setPosition:CGPointMake(screenSize.width/2,screenSize.height/2)];
I haven't a clue how to adapt a button that would be X = 144 & Y = 330 on the old 3.5inch, 640 by 960 resolution iPhone to an iPad or iPhone 5 resolution.
I'm willing to use a more recent version of iOS if it will make my life easier, but because I'm not using any of Apple's objects I don't know if that is possible.
Maybe this isn't even possible because the button will be different sizes for the iPhone and iPad version, but I thought I would ask.
yeah, i am usually facing the same problem,
but if it is just a static objects placement
i would have relative coordinates instead of absolute for every object
and then use screen sizes to place them correctly
so you might want to use a function like:
-(CGPoint) relativeToScreen:(CGPoint) p {
return ccp(screenSize.width * p.x, screenSize.height * p.y)
}
where 0.0 <= p.x =< 1.0 and the same for p.y
and don't forget about your anchorPoint, because the node position is based on it as well
and i hope you have discovered that cocos2d already does image choosing instead of you,
you just have to set right suffixes for your images: -hd, -ipad, -ipadhd
For iphone5 resolution, I position hud buttons relative to the screen dimensions. Very similar to what you are doing for the background. So for example, a pause button I want in the top left I would position like this:
[pauseButton setPosition:CGPointMake(0.0f + 30.0f, screenSize.height - 50.0f)];
For ipad it gets really tricky. The lazy way which I have implemented is to play around with the content scale factor and zoom everything up and have "dead" borders to compensate for the ipad's screen ratio. Not the best, but at least you can re-use all the same assets for the ipad.

Resources