Cocos2D: Using HD images for iPad in a Universal App

Introduction

This is my first tutorial, and I am proud to make it be about writing Objective C code for the iOS and Cocos2D. Since the debut of the iPad, there has been much talk on the Cocos2D forums about how to code one Universal app that will work on the iPhone/iPod touch (resolution: 320×480), the iPhone4 Retina Display (resolution: 640×960), and iPad (resolution: 1024×768). As it stands, Cocos2D 0.99.5 to 1.0-rc accommodates for iPhone and Retina display resolutions automatically by appending the filename with ‘-hd’. So, if you have a background file named, background.png that works for 320×480 resolutions, all you have to do is create a larger version of the file named, background-hd.png. See here. The second issue of how Cocos2D accommodates for iPhone and Retina display is in positioning. Since version 0.99.5, Cocos2D automatically does this using points instead of pixels. So, in the normal display, the normal display, setting the position to ccp(100, 100) is automatically translated to ccp(200, 200) in the Retina display. The issue remains regarding how to accommodate specifying the images and the correct positioning on the iPad. In my research on how to do this, I came across the following discussions on the Cocos2D forums: All of these discussions were excellent in helping me understand the problem, and presented various solutions, but none of them worked for me. Even when I got the HD images to work, the positioning was all off. So, I decided to come up with my own solution, and I wanted to share it with everyone. My goal was to reuse the HD images that I made for the Retina Display, and center my app in the screen, so that the entire app had a black border.

Code Setup

First I created a header file to maintain my constants, such as file names. This header file was called Constants.h. I also created another header file that contain macros to detect and handle the different device details. I called this file DeviceSettings.h. [sourcecode language=”objc” title=”DeviceSettings.h”] #import <UIKit/UIDevice.h> /* DETERMINE THE DEVICE USED */ #ifdef UI_USER_INTERFACE_IDIOM() #define IS_IPAD() (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) #else #define IS_IPAD() (NO) #endif /* NORMAL DETAILS */ #define kScreenHeight 480 #define kScreenWidth 320 /* OFFSETS TO ACCOMMODATE IPAD */ #define kXoffsetiPad 64 #define kYoffsetiPad 32 #define SD_PNG @".png" #define HD_PNG @"-hd.png" #define ADJUST_CCP(__p__) \ (IS_IPAD() == YES ? \ ccp( ( __p__.x * 2 ) + kXoffsetiPad, ( __p__.y * 2 ) + kYoffsetiPad ) : \ __p__) #define REVERSE_CCP(__p__) \ (IS_IPAD() == YES ? \ ccp( ( __p__.x – kXoffsetiPad ) / 2, ( __p__.y – kYoffsetiPad ) / 2 ) : \ __p__) #define ADJUST_XY(__x__, __y__) \ (IS_IPAD() == YES ? \ ccp( ( __x__ * 2 ) + kXoffsetiPad, ( __y__ * 2 ) + kYoffsetiPad ) : \ ccp(__x__, __y__)) #define ADJUST_X(__x__) \ (IS_IPAD() == YES ? \ ( __x__ * 2 ) + kXoffsetiPad : \ __x__) #define ADJUST_Y(__y__) \ (IS_IPAD() == YES ? \ ( __y__ * 2 ) + kYoffsetiPad : \ __y__) #define HD_PIXELS(__pixels__) \ (IS_IPAD() == YES ? \ ( __pixels__ * 2 ) : \ __pixels__) #define HD_TEXT(__size__) \ (IS_IPAD() == YES ? \ ( __size__ * 1.5 ) : \ __size__) #define SD_OR_HD(__filename__) \ (IS_IPAD() == YES ? \ [__filename__ stringByReplacingOccurrencesOfString:SD_PNG withString:HD_PNG] : \ __filename__) [/sourcecode] The idea is to have a set of macros that handle the checking for iPad and substitute the correct filename and coordinates (points/pixels) on the screen. Here is the constants file: [sourcecode language=”objc” title=”Constants.h”] /* TEXTURE FILES */ #define kSpriteTexture1 SD_OR_HD(@"GoodGuy.png") #define kSpriteTexture2 SD_OR_HD(@"StageBoss.png") /* FIXED POSITIONS */ #define kSomePosition ADJUST_CCP( ccp(200, 100) ) [/sourcecode]

Textures

So, from looking at the Constant.h file, you can see that we just need to define the texture in the SD_OR_HD() macro, and if it is the iPad, you will have GoodGuy-hd.png. If it is the iPhone/iPod touch, Cocos2d will load GoodGuy.png, and if it is iPhone4 Retina Display or iPad, Cocos2D will load GoodGuy-hd.png. [sourcecode language=”objc” title=”SomeCodeFile.m”] CCSprite *goodGuy = [CCSprite spriteWithTexture:[[CCTextureCache sharedTextureCache] addImage:kSpriteTexture1]]; goodGuy.position = kSomePosition; [self addChild:goodGuy]; CCSprite *stageBoss = [CCSprite spriteWithTexture:[[CCTextureCache sharedTextureCache] addImage:kSpriteTexture2]]; [stageBoss setPosition: ccp(0, ADJUST_Y( kScreenHeight*0.45 ))]; [self addChild:stageBoss]; [/sourcecode] For the positioning on the goodGuy sprite, you will have the following result coming from using:
  • iPhone/iPod touch: ccp (200, 100)
  • iPhone4 Retina Display: ccp(400, 200)
  • iPad: ccp(464, 232)
The way that I compensate for the iPad is to add 64 to the width and 32 to the height. This is because with the iPad, I gain 128 extra pixels in width and 64 extra pixels in height, if in Portrait orientation. So, I adjust everything half of the extra pixels, since Cocos2D takes the origin from the bottom left hand side of the screen.

Labels

Now, in the DeviceSettings.h file, I also have HD_PIXELS and HD_TEXT. I use these to adjust changes to coordinate and to the size of text used in Cocos2D, such as labels and menu items. So, for example, to move a sprite down by 60 pixels on iPhone and (the equivalent) 120 pixels on the iPad and Retina Display, I would type: [sourcecode language=”objc” title=”Example 1=using 2=code 3=in 4=a 5=class”] id action = [CCMoveBy actionWithDuration:1.0f position:ccp(0, HD_PIXELS( -60.0f ))]; [goodGuy runAction:action]; [/sourcecode] As for the font sizes, you can use HD_PIXELS, which is the most accurate method, but I often find the text to seem too big, so I use HD_TEXT instead, which multiplies the font size by 1.5, instead of 2.0, as with HD_PIXELS.

Spritesheets and BMFonts

This concept can also be applied to loading Spritesheets and BMFonts. You can make Spritesheets with Texture Packer, and you can make BMFonts with Glyph Designer. Basically, add the following to the DeviceSettings.h file: [sourcecode language=”objc” title=”DeviceSettings.h”] /* SD/HD Font file */ #define SD_FNT @".fnt" #define HD_FNT @"-hd.fnt" /* SD/HD Spritesheet plist */ #define SD_PLIST @".plist" #define HD_PLIST @"-hd.plist" #define SD_HD_FONT(__filename__) \ (IS_IPAD() == YES ? \ [__filename__ stringByReplacingOccurrencesOfString:SD_FNT withString:HD_FNT] : \ __filename__) #define SD_HD_PLIST(__filename__) \ (IS_IPAD() == YES ? \ [__filename__ stringByReplacingOccurrencesOfString:SD_PLIST withString:HD_PLIST] : \ __filename__) [/sourcecode] Using this code is simple. Here’s the code: [sourcecode] /* BMFont example */ NSString *string1 = NSLocalizedString(@"This works!", @""); CCLabelBMFont *notice1 = [CCLabelBMFont labelWithString:string1 fntFile:SD_HD_FONT(@"myFontFile.fnt")]; notice1.position = ADJUST_XY( kScreenWidth*0.7f , kScreenHeight*0.4f ); /* Spritesheet example */ //1. (Pre)Load spritesheet [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:SD_HD_PLIST(@"mySpritesheet.plist")]; //2. Get the image inside the spritesheet as a CCSprite // Note: mySpriteImage.png is in mySpritesheet.plist and mySpritesheet.png CCSprite *mySprite = [CCSprite spriteWithSpriteFrameName:@"mySpriteImage.png"]; [/sourcecode] This ends the tutorial. I hope my code is self explanatory.

14 comments

I am kind of new to this macro thing. So if you use >> notice1.position = ADJUST_XY( kScreenWidth*0.7f , kScreenHeight*0.4f ); Does it sets the value by itself, I have used it but I don’t see anything. Does it return the value so it will be applied? Thanks
I have a game in cocos2d 0.99 with retina enabled in the appdelegate. I load a background image for a main menu but on launch it appears about 1/8 of the screen smaller. This leaves a black edge on the iPad 1 screen. I go to my level1 or whichever, finish the game and upon return to the main menu then the background image appears right sized and positioned. Ever have this happen?
Hey, just wanted to say that this tutorial worked GREAT for me, and was amazingly cool. However, I’m attempting to update my app to work with the Retina iPad, and not having any luck; I’ve had to revert to not supporting the retina display. So consider this another “vote” for an updated version of this tutorial… It would be very much appreciated by the likes of me! But if you can’t get to it, no worries — I still think this is great work!
Our App doesn’t work on iPad3 with this. We only have a black screen (sound and interactions working although), is there a solution?
I have been using a similar approach, and can appreciate that your code is cleaner. But won’t you have problems when using this code with the new Ipad 3 in retina mode? Won’t you need to double the size of the -hd files and offsets to support ipad retina?
Hi very good tutorial. How would I edit this to make it compatible with the new iPad resolution? Thank you
Very very very nice tutorial. I’ve learned a lot! Just a little question… about the ipad offsets. Since the default cocos2D orientation is landscape I have corrected the offsets with: /* OFFSETS TO ACCOMMODATE IPAD */ #define kXoffsetiPad 32 #define kYoffsetiPad 64 With this correction if I place a sprite using ADJUST_CCP(ccp(0,0)) i can see it at the center of the screen. Am I missing something? Thanks!
Thanks Michael. I know a lot of people find this tutorial very useful. My original question was due to me missing the fact that you were using the iPad as 960 x 480. Makes sense in many cases and definitely simplifies some things.
@Tom. Yep. That’s the idea. That is what I did on Match 4 Rainbow. So, it allowed me to have one two sets of textures, one for SD (320×480) and one for HD (640×960). So, it allowed me to use the larger more detailed textures that were made for Retina display to be used on the iPad.

Leave a Reply