I need to display a few images in my IOS Application. What should I do so that the images display appropriately across all devices?
Do I have to set the size of the image manually based on the device? Please clarify.
You wouldn't use SpriteKit just to display images. You would load the images as UIImage and then create a UIImageView that you can place on the screen wherever and however you want and then you just assign the UIImageView your UIImage. UIImageView has a lot of properties you can set how images are displayed (e.g. if they are scaled and how they are scaled or if they are not scaled, how they shall be aligned within the viewable area, and so on). You can draw a UIImageView on top of a SpriteKit scene, that is no problem on iOS (on iOS everything is drawn by OpenGL ES or Metal anyway).
Of course you can also embed any image as a sprite if you like:
UIImages * img = ...;
SKTexture * tex = [SKTexture textureWithImage:img];
SKSpriteNode * sprite = [[SKSpriteNode alloc] initWithTexture:tex];
// If you don't use ARC, I'd add the following below:
// [sprite autorelease];
Now you can perfectly integrate it into the scene in whatever way you like an perfectly align it will all your other sprites. Yet if you just want to paint an image over the scene:
SKScene * scene = ...;
SKView * sceneView = scene.view;
UIImageView * imgView = [[UIImageView alloc] init];
imgView.image = img;
// Whatever content mode you prefer
imgView.contentMode = UIViewContentModeScaleAspectFit;
// Where shall it be placed and how big shall it be.
imgView.frame = CGRectMake(posX, posY, width, height);
// If you'd use this, it will cover the whole scene:
// imgView.frame = sceneView.frame;
// Add it on top of your scene
[[sceneView parent] addSubview:imgView];
// If you don't use ARC, don't forget to release it:
// [imgView release];
If you load an UIImage from your application bundle with [UIImage imageNamed:#"blah"] and the image exists in different resolutions for retina devices (blah.png, blah#2.png, blah#3.png), the system will automatically load the image it considers most suitable for the screen of the current device. This is nothing you have to deal with.
If you need to convert between scene coordinates and view coordinates, SKScene offers -convertPointFromView: and -convertPointToView: for this case. If the scene fills the whole screen, then these actually convert between scene and screen coordinates.
Even if devices have different resolutions, your scene can always have the same "virtual size". So you can always say that the scene is 400x300, no matter what the real screen resolution is. In that case placing a sprite of virtual dimension 200x150 at the virtual coordinates (100,75) will always center it on the screen, no matter what device or how big the screen really is (well, assuming that the SKSceneView really covers exactly the whole screne, of course). The size of a SKScene is just the coordinate system you want to have for layouting your game, it can be whatever you want it to be, it can be bigger or smaller than the real screen.
The scene is always drawn into a SKSceneView. The size of the SKSceneView is the real size of your scene in screen coordinates. So if you SKScene is 480x320 and the size of the SKSceneView is 1440x960, then moving a sprite one pixel in your scene will in fact move it 3 pixels on the screen. Yet if your SKScene is 1136x640, but your SKSceneView is only 586x320, then moving a sprite two pixels in your scene will only move it one pixel on screen. Your scene is always scaled up or down as required.
Personally I'd either stick with the same size across all devices or maybe just make two or three device classes but not adopt the game for every single device and every existing screen resolution.
There a lot of things to consider when dealing with images in SpriteKit, The short answer is you should be creating images in 1x, 2x, and 3x (background.png, background#2x.png and background#3x.png). You will get the best image quality if you do that.
As far as resizing images based on different devices that usually is done at the scene level. There are a lot of good SO questions out there that cover a lot of the questions you will have.
For example:
Dealing with different iOS device resolutions in SpriteKit
I recommend searching for "creating a universal app with SpriteKit".
Hopefully that answers your immediate question and helps get you started with the other questions you will have.
Related
I am developing game with SpriteKit for iPhone devices only.
In the scene I have buttons, which need to be positioned in the way to fill the height of the screen:
There should be no gaps between top and bottom edges, as well as from each other. The width can vary. Buttons contains vector ornaments, done in AI.
In order to achieve that, I thought I could use PDF asset, set to "single scale" and "Preserve Vector Data". Then I thought I can get slightly resized PNG file out of PDF and reposition then based on client area. However, it appears SKTextureNode doesn't take PDF as source. I get blurry image.
The code for loading image:
let texture = SKTexture(imageNamed: "BuyButton")
let node = SKSpriteNode(texture: texture, size: CGSize(width: 81*4, height: 165*4)) // PDF size is 81x165
This works just fine with UIImageView, where you can set any size and it will give you crisp image. I can guess that there is some magic routine somewhere in UIImageView, which forces to create new image of given size.
So my question is whether there is a way to engage this magic routine for SpriteKit?
In the screenspace of an iPhone/iPad, Apple uses points, which are typically half the actual resolution of the screen. My question is, is it possible to access the actual pixels themselves? For example, if i take a UIView, and make it have a size/location of 0,0,0.5,0.5 with a background color of red, i can't see it on the screen.
Just wondering if this is possible.
Thanks!
Sure it's possible.
The code you already have should be working (a UIView with a size of (0.5, 0.5)). I just ran it and captured this result from the simulator:
Yea. That's difficult to see. Let's zoom that in.
So yes, you can draw on-screen in smaller values than a single point.
However, to draw a single pixel, you'll want to be using a point value that is 1/scaleOfScreen (as not all devices have 2x displays). So, for example, you'll want your code to look something like this:
CGFloat scale = [UIScreen mainScreen].scale;
CGFloat pixelPointWidth = 1/scale;
UIView* v = [[UIView alloc] initWithFrame:CGRectMake(20, 20, pixelPointWidth, pixelPointWidth)];
v.backgroundColor = [UIColor redColor];
[self.view addSubview:v];
This will now create a UIView that occupies a single pixel on-screen.
Although, if you want to be doing a lot of pixel-perfect drawing, you should probably be using something lower level than a single UIView (have a look at Core Graphics).
However.
You may encounter some issues with this method when drawing on an iPhone 6 Plus. Because it's screen's scale differs from its nativeScale, it will first render your content in the logical coordinate space of 3x and then downsample to the actual screen resolution (around 2.6x).
This will most probably result in some pixel bleeding, where your 'pixel' view can be rendered in neighboring pixels (although usually at a reduced brightness).
Unfortunately, there is no easy way around this problem without using an even lower level API such as OpenGL or Metal, where you can circumvent this automatic scaling and then downsampling, and draw directly into the screen's actual coordinate space.
Have a look here for a nice little overview on how different devices render content onto their screens.
Have a look here for more info on how pixel bleeding can occur on the iPhone 6 Plus.
You can guess the pixels based on the point depending on the device resolution (in ppi) by multiplying a coefficient but you don't want to do this.
Also, in your exemple you did not state that you normalized the coordinates so basically you are trying to display a red box at the first pixel (top left) with a size of half a point, which is why you can't see it.
EDIT
To draw a red box you can use this sample code :
// Draw a red box
[[UIColor redColor] set];
UIRectFill(CGRectMake(20, 20, 100, 100)); // position (x : 20, y: 20) (still top left) and size (100*100 points)
I'm creating universal game for all iOS devices in portrait mode using Swift. In GameViewController I'm creating scene like this:
let scene = GameScene(size:CGSize(width: 1536, height: 2048))
scene.scaleMode = .AspectFill
Background image has resolution 1536x2048, and so with above scaleMode on iPad it's displayed in its full size, on iPhone 6 1152x2048 is displayed with sides trimmed. Works perfectly fine on all devices, and only one background image is needed. Problem is that if I call for size.width or self.frame.size.width it always returns 1536, even if the actual visible area is e.g. 1152.
How can I set SkSpriteNode's position relative to visible area, so that it'll be for example 50x50 from the corner on every device?
How can I set SkSpriteNode's position relative to visible area, so
that it'll be for example 50x50 from the corner on every device?
The "visible area" is simply the view.
So you can make your positions relative to the view, and not the scene. I actually do this a lot in my game, which is universal and runs on both OS X and iOS.
In my case I typically do this for the user-interface, so I might have a scaled scene but I want to set some positions not relative to the scaled scene but relative to the visible area (i.e. the view).
To do this, you can write a function that converts view coordinates to the corresponding scene coordinates.
Here is my function that I use. Note that I subtract my desired y-position from height of view so that I can treat (0,0) as the bottom-left like sprite-kit does instead of the top-left like UIKit does.
func convert(point: CGPoint)->CGPoint {
return self.view!.convert(CGPoint(x: point.x, y:self.view!.frame.height-point.y), to: self)
}
Here is an example of using this function:
self.node.position = convert(CGPoint(x: 50, y: 50))
This will always force the position of the node to be at (50,50) relative to the view (the visible portion of the screen) regardless of how your scene is scaled and sized.
I don't think this is really the best approach. You should creating the GameScene based on SKView's size
let scene = GameScene(size: self.skView.bounds.size)
I don't think you should be setting one universal size for every device. You need to let the device set the scenes dimensions based on the screen's resolution. Then you need to be creating different images based on the device. 2x, 3x, 2x~ipad etc..
This tutorial is a good place to start:
http://www.raywenderlich.com/49695/sprite-kit-tutorial-making-a-universal-app-part-1
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
I have a sprite, that I'd like to use as background image(using cocos2d).
CCSprite *abc =[CCSprite spriteWithImageNamed:#"background.png"];
abc.position = ccp(self.contentSize.width/2,self.contentSize.height/2);
First image is original, second one is a screenshot from the simulator. Resolution of the image is 640/1136. What should I do to fit all space of the screen normally? How schould I locate it?
The code you are using is correct.
The result you are getting is not what you expected because you probably loaded a Retina Image on a non Retina Screen.
Check out Cocos2d Naming conventions here for more info.
For the background image you chose of the sun, I am assuming that you want it to be always in the top right corner. (That makes more sense to me than centering it).
Now an elegant solution to accomplish this would be to get your image for the 4 inch screen that you have already created, and define a rule so that it's top left corner is always at the top left corner of the screen. For the 3.5 inch screens this would be clipped.
Now, first you want to define an anchor point as
_background.anchorPoint = ccp(1.0f, 1.0f);
This will tell Cocos to position your background relative to the top right corner.
Now you can go on and position it so that it is always at the top corner of the screen.
background.position = ccp(self.scene.bounds.size.width, self.scene.size.height);
This would be the standard and best way to do it. Results and benefits:
Works on 3.5 and 4 inch screens without needing specific image sizes
Simple, no unnecessary code and especially UI_INTERFACE_IDIOM testing
The way most everybody does it
Another way of positioning in the top right corner
You can also check out the new positionType property for CCNode in the reference. CCPositionUnitNormalized can help you define a positioning rule similar to saying position this to the 100% width and 100% height of the parent container. It would be something like this.
_background.positionType = CCPositionUnitNormalized;
_background.position = ccp (1.0f, 1.0f);
and have the same result if you prefer this syntax.
You can use either scaling the image to fit the device height or just use separate image for the 4 inch iPhone and 3.5 inch
For Scaling
abc.scaleY = winSize.height/abc.contentSize.height;
For Specific image
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
if([[UIScreen mainScreen] bounds].size.height == 568)
{
// iPhone 5 add different image
}
else
{
// add 3.5 screen image
}
}
I am unsure what "self" is referencing, but I assume it is a layer. It appears you are trying to center the image on the screen, or you need to offset it based on the size of the screen. If so you should get the screen size as it will allow you to place the image properly no matter what the resolution of the screen is. I am using Cocos2d 2.1.
CGSize winSize = [CCDirector sharedDirector].winSize
This is the winSize in points. You can also get the winSize in pixels:
CGSize winSizeInPixels = [CCDirector sharedDirector].winSizeInPixels;
Use whatever works best for you. You can then center the image with the following, for example:
abc.position = ccp(winSize.width / 2, winSize.height / 2);
Regardless of whether or not you are trying to center the image, knowing the screen size will allow you to place the image based on that screen size so that it appears properly.
Obviously the size of the image and whether or not it fills the screen must be addressed.
I hope this helps.