Truly Universal

In a previous post I discussed the approach that I used to make a Universal app for iPhone and iPad. That approached circumvented some problems others were having on the iPad 3 with cocos2d. Even though apps continued to run properly on the iPad 3 with that approach, we were not taking full advantage of the extra pixels.

The first thing we need to do is generate new high res assets. I am going to skip that step for now and assume you have higher resolution assets. Add the suffix "-hdpad" to the end of your high res assets or pick your own suffix and adjust the code below accordingly.

Next we need to define our Retina iPad suffix in ccConfig.h:

Then we need to edit CCFileUtils to make sure we use the "-hdpad" suffix when a Retina iPad is running our Universal app:

You will notice in the above code we are checking for the Retina iPad by checking to see if the content scale factor is equal to 4. If we load "-hd" assets and scale the points by 2 on the iPad, then we will need to double that for the Retina iPad.

We also need to update the utility function that cleans up the hd suffix:

Next we need to modify our enableRetinaDisplay flow we outlined in the previous post to enable Retina on the iPad 3.

The first thing we do is create a new enableRetinaDisplay:onPad method so we can choose not to enable the Retina Display on the iPad 3 if we don't have the assets to support it:

You will notice that the old enableRetinaDisplay method calls the new method but passes a default value of FALSE. This will keep our app working as is until we have our "-hdpad" assets ready.

The next step is to modify the content scale we will pass to setContentScaleFactor. Remember that we want to end up with a content scale factor of 4.

Notice that if we are not supporting the Retina iPad, then we set the scale exactly as we did before. If we are supporting the Retina iPad, then we set the new scale equal to the scale times the point scale factor which is set to 2 in makeUniversal. Hey Maw, look at my handy chart:

Next we have to take care of the line that saved us from having Retina iPad headaches in the first place:

Referencing out handy chart, you will see that __ccPointScaleFactor is not one. This means that __ccContentScaleFactor is ignored and we use one instead. We need to generalize this line so that it still works under the old case, but will also handle the new scenario:

Now we will still end up with a scaleFactor of 1 on the non-Retina iPad and on the Retina iPad if we did not enable Retina iPad mode. If Retina iPad mode was enabled, then we end up with a scale factor of 2 which enables retina mode on the device.

Here are all the changes to CCDirectorIOS.m in one handy gist:

Now you have a universal app that supports the full resolution of the iPad 3.

Asset Addendum

Preparing your asset pipeline to fit into the hd, hdpad, sd world will take a bit of work. If you have vector or pixel art, then your job is easier but no one wants to export every asset three times. Some of the common tools like Texture Packer will likely build in support for this in the future. In the interim you can probably get by with a few command line scripts.

Caveat: The modifications in this post were done to cocos2d 0.99.5. There are minor differences between the supplied code and the changes you should make to cocos2d 1.1.

Caveat Emptor: You aren't actually buying anything but use at your own risk anyway. There will likely be tweaks and adjustments to this code in the future.

Note: If you are building a stand alone iPad version, then I recommend you use the latest updates in the develop branch for cocos2d 1.1. They have already added Retina iPad support. This code provides a simple way to create a Universal app.

Recommendation: If you haven't had them before, I recommend you go buy some tomatillos and try them on your next taco in place of tomato. Prepare yourself for a surprising and unique flavor.

More Pixels Equals More Panic

The March 2012 Apple Keynote brought no real surprises. The new iPad (hereinafter iPad 3 despite protestations) has a Retina Display with a whopping 1.572864 million pixels. I have some worries about whether the device will have enough oomph to push those pixels, but we will burn that bridge when we get to it.

Like a good little Apple dev, I downloaded the newest XCode and iOS Simulator to test out my apps. The rest of the cocos2d community was doing the same and that is when the excrement came in contact with the bladed cooling device. Devs started to report that their apps were showing a black screen on the iPad 3 simulator. More reports were coming in of other strange behavior.

I had only tested out my current project when I first heard the buzz. It hadn't shown any problems but I was worried about my apps that were already for sale. I fired up every project and tested them one by one. They all behaved exactly as expected, but why? What was the difference between my apps and all of the apps that were having problems?

The Issue

The root of the problem was that the iPad 3 was behaving exactly as it should. Universal cocos2d applications that support the Retina Display on the iPhone often have this snippet of code in the app delegate:

The enableRetinaDisplay method attempts to turn on the Retina Display if the hardware supports it. Turns out that the iPad 3, with its Retina Display, activates the Retina Display when asked to do so. The problem is that since the rest of cocos2d and our apps aren't expecting this to happen on an iPad all sorts of craziness happens.

The good news is that Apple didn't go crazy and that things are behaving as one might expect. The bad news is that we have code built around an assumption that is now wrong. So why did my code work? Clearly I must not have had that handy snippet of Retina enabling code from above. WHAT?!?! I did? Oh, something else must be going on then.

Pixel Pushers

The solution to the problem was a bit of an accident. I wanted to create universal apps that took advantage of the iPad's resolution without needing new assets. With careful asset management and a few hacks it is easy to use your Retina iPhone assets on the iPad. This helps you reduce your app's footprint and take advantage of additional pixels. DOUBLE RAINBOW!

The method that I use is partially outlined in this forum post. It adds a makeUniversal method to CCDirector which you call immediately after instantiation of the director in your app delegate. Here is the code:

This method sets the __ccPointScaleFactor to 2. This sets the scaling between points and pixels. This also causes an escape to be skipped in enableRetinaDisplay display which causes the content scale factor to be updated to 2. The content scale factor is used by cocos2d to determine if the "-hd" assets should be used.

This has another side effect due to a check in updateContentScaleFactor which is indirectly called by the enableRetinaDisplay method:

If __ccContentScaleFactor is 2, then the method forces the display's scaleFactor to 1. This means that we don't actually activate the Retina Display even though we call enableRetinaDisplay. We are now safely using iPhone Retina assets on all (existing) models of the iPad. WOOOOOOOT!

But All Those Pixels!

You are right, this does not take advantage of all the extra pixels that the iPad 3 has available. You will need higher resolution assets to fully take advantage of the new iPad, so we need another approach. We can go over that in another post. (Edit: In fact, I just wrote up a post on how to make a truly universal iOS application here.)