Friday, April 15, 2011

Universal Mac/iOS menus with Cocos2D

Hello! 
In this article I would like to share my experience in developing universal menus using Cocos2D for iPhone (SD + Retina), iPad & Mac.

First time, iTraceur was developed only for the iPhone. Some time after i ported it for the iPad, then iPad version was ported to Mac and finally i added all of these changes and Retina support in iTraceur 1.3 for iOS.



iTraceur is my first game for the iPhone and my first Cocos2D application. So often development process was not optimal and i stepped on a variaty of rakes. By the way, i used Cocos2D 0.7.2 at the beginning and added some crutches to the engine. Later I was able to get rid of most, although some are still there - you can see my fork of Cocos2D  (By the way there might be something useful that I have not published in a separate repository yet).


In iTraceur versions from 1.0 to 1.2.2 i used one file (MenuScreens.m) for all menus in the game. All positions was hardcoded and all elements was Sprites, grouped in SpriteSheets as belonging to different menu screens. I should mention, that all textual elements in menus was created from images and i didn't use fonts. So in the process of adding new menu items i had to rebuild sprite sheets and change code to reposition elements, each time recompiling it for tests. 
That was soo stupid. It was pretty inconvenient. ;)


In the process of porting to the iPad i had to replace all the graphical menu resources.
Thank God I had the sense to draw silhouettes, buttons and other complex graphics in vector formats or higher resolution, so i didn't have to redraw everything - only scale and rearrange, but it was also quite troublesomework, and it took time.
For iPad version i also had to change the menu code. 

Not much, but it added some #ifdef's into already awkward file.


While working on iTraceur & iTraceur HD, i didn't use any SCM, and all new features and fixes from Cocos2D was added manually.
This added a litlle bit more crutches and ugliness to the code =)


For porting to the Mac, i had to update Cocos2D, cause 0.7.2 have no Mac OS X support. During this i started to throw out crutches. 
threw MenuScreens.m and wrote all the menus from scratch, using the previous file only as documentation about filenames of resources and features that i need to implement. This allowed me to make the menu compatible with both Mac and iOS.


Since the iPad's screen resolution is  pretty close to the resolution of 13 inch MacBook, I decided to use the same resources for new menus, using only the smart scaling and positioning to get a good picture at any screen resolutionAs a result, the same code (with the same resources) was used in 1.3, which is an Universal app. If Apple will make possible the universal application for Mac & iOS - I will not change the menus code, cause it's already universal.


Also in 1.3, I decided to add retina support - it was pretty easy because of the opportunities of Cocos2D. Many resources wasn't even duplicated for the hd quality, because they were created for the iPad, which has larger resolution than the Retina screen. (i.e. sitting traceur silhouette in iTraceur's main menu - i have only one image of it for old iPhones, 4Gen devices, iPad and Mac)


To create an Universal menu, I used:

  1. CCMenuAdvanced - CCMenu subclass, that sets it's contentSize based on bounding boxes of it's menu items. That allows you to freely manupulate the menu ( Also it have keyboard controls on the Mac , scrolling, priority property and maybe something else tasty ).
  2. Gimp as editor (By the way!  In Windows Gimp supports drag-n-drop image files - it creates new layer with size and name of image file dropped - very handy. It's sad, but this feature doesn't work on the Mac.).
  3. xcf2sprites (modification of xcftools) - as xcf parser.
  4. CCMenuEditor, as a loader of sprites.plist file.
Warning: xcf2sprites & CCMenuEditor are not something I would be proud of, and I use them in this article only because i don't have anything better  to illustrate the ideaFrom xcf file, you can get only the name of the layer, its position and size. But it's impossible to get rotation or scale ratio. So you're better use something else as an editor.
Before turning directly to the code, I would like to give some general guidelines that can help quickly and easily develop a menu that is independent of screen resolution and is suitable for Mac, iPhone, iPhone Retina & iPad:
  1. Do not hardcode positions/scale/etc in pixels/points.
  2. If you need something fixed - create individual CCNode for this with editor. 
  3. Do not use image for text - use labels instead. For custom fonts use CCLabelBMFont. (Also it's easier to localize text & fonts, than images)
  4. The same texture may have different size in points on different devices. Keep this in Mind, while scaling something. (I.e. DynamicTiledLevelNode or Cocos2D logo sprites in CCMenuAdvanced examples)
  5. When planning the game screens and menus do not think about the ideal (fixedsize of the screen, but think about min and max sizes. (By the way minimum size should be 800x600 or less, cause someone may use this resolution on Mac, really )
  6. Develop and test menus on Mac with kCCDirectorResize_NoScale and various window sizes.
  7. Let your resources will be ready for various resolutions (better to have them in a vector format, or a far superior resolution of the screen resolution)


When i developed menus I used the following approach ( may be it is even possible to call this pattern ) :
  1. [[CCDirector sharedDirector] winSize] != const, do not save it anywhere.
  2. Children nodes are created and added in init.
  3. At the end of init [self updateForScreenReshape] is called.
  4. updateForScreenReshape every time asks CCDirector for the winSize again (see point 1) and on the basis of this and other data positions and scales children.
  5. updateForScreenReshape is called each time, when winSize changes 
OK, i can't insert code snippet here on Blogger, so let's just go right to the GitHub Repo.

CCMenuAdvanced is located here. In the commits history, starting from 4th goes the development of demo app.  At the beginning it goes on Mac project, than ready code is builded for iOS, and retina support is added at the end.
That's all probably. Any comments, questions, corrections and additions are always welcome! ;)
I created this blog for sharing things like that, but Blogger is so harder to use, than markdown, so probably i should use GitHub wikis or something else instead. Also, i think that good code (esp. with commit history) is far better than blogposts. So likely i will not post here about FileDownloader and DynamicTiledLevelNode, and just will publish them with examples on GitHub.
If you're interested in any other details of iTraceur - feel free to ask, i would like to help.
P.S. At the moment of editing this post 1.3 is Waiting For Review. Current version in the AppStore (1.2.2) is not an Universal app and doesn't support retina.