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
Related
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.
Is it possible to create a custom UIView that will be rendered on for example 1500 x 1500 pixels? I can easily create an image from a UIView and save it, my problem starts when I'm using an iPhone 5 and the image I save will be 640 x 640 px (the UIView is a square) because 640 pixel is the maximum width of it's screen resolution. So when I use the same image on a iPhone 6 Plus it will be pixelated because its screen width is 1080 x 1080 px, therefore I need to create the images that looks good on every screens. How could I manage this? Or how could I make a UIView that will be the same on every device?
In this case the result will be different based on the type of the retina screen:
CGRect newFrame = self.fullView.frame;
newFrame.size.width = 1500;
newFrame.size.height = 1500;
[self.fullView setFrame:newFrame];
I have an idea, that I would use if statements to figure out the current device and use different sizes for different devices, but a cleaner solution would be amazing. However I'm not sure that it would be the proper
logic so I welcome any other solution.
I'm saving the UIView as a UIImage with UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0); so the saving method won't decrease the size.
Not sure exactly what you're trying to do but you should Auto Layout to make the view fit the correct screen size.
Also, if you want to have a specific image for each screen size you should use Asset Catalog where you can create an image set with the will load a different image for each device.
In learning SpriteKit so I'm making a scene with 1 buttons. I do not understand the way positions work. What I read was that 0,0 was the bottom left corner and the default anchor point is in the middle of each button (0.5,0.5). The scene is scene.scaleMode = .AspectFill. If I write:
self.playButton.position = CGPointMake(self.playButton.size.width/2,CGRectGetMidY(self.frame))
self.addChild(self.playButton)
I get what is in the screenshot below.
If I set the playbutton.position.x to be self.playbutton.size.width/2 then I would assume it would be on the left edge of the screen. Not weirdly somewhat off the screen. I've read the documentation about size for a couple of days now and I still can't seem to understand it. I've also explicitly set:
self.size.width = 768
self.size.height = 1024
Meaning the scene size width and height. Also I have "Use Auto Layout" in the main storyboard checked.
If you change your scene's scale mode to .AspectFit you'll be able to quickly see what the problem is. The resolution you're setting for your scene is a completely different aspect ratio from that of your device. In fact, when I answered your last question, I assumed that your usage of the 768x1024 resolution meant that you were developing for an iPad, for which this is the correct resolution.
On an iPhone with a 4 inch screen, you should be setting the scene's size to be 320x568, and 320x480 for 3.5 inch screens. This can however be simplified by adding this line to the creating of your scene in your view controller. It will set the scene's size to the size of the view controller's view.
scene.size = skView.bounds.size
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.
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.