My apps…

Space Harvest Begin

All-Seeing Interactive is a tiny web design and software company based in London, UK.

Monday 24 May 2010

Space Harvest for iPad, and tips for scaling the display with cocos2d for iPhone

Just submitted to the app store this afternoon:

Picture of Space Harvest for the iPad

One of the first things I did on my stolen borrowed iPad was to play Space Harvest. I think the pixelated graphics are actually very well suited to the 2x scaling that the iPad applies to iPhone / iPod apps. The larger screen makes everything easier to see, and at twice the size, touch areas are twice as big, making the game a bit easier to play. Most importantly, the blockiness makes it feel more like something made 20 years ago, abeit in an alternate universe, which is the aesthetic I was originally aiming for. The only downside is that ugly black border at the edges of the screen.

In porting Space Harvest, I decided that rather than increasing the visible play area, I’d like to keep the graphics scaled up to twice their normal size. Here’s how I did it:

Modify layout

Given that I wanted to display the graphics at 2 times their normal size, I needed to set things up to render in an area 512x384 pixels (half the landscape resolution of the iPad).

The first step was to modify the visual layout to make use of all the extra screen real-estate. To make things easy, I added a couple of methods to CCDirector to return my virtual screen size:

- (float)screenWidth
{
	if ([self runningOniPad]) {
		return [openGLView_ frame].size.height/2.0f;
	} else {
		return [openGLView_ frame].size.height;
	}
}
- (float)screenHeight
{
	if ([self runningOniPad]) {
		return [openGLView_ frame].size.width/2.0f;
	} else {
		return [openGLView_ frame].size.width;
	}
}

Confusingly, the width of the GL view is actually the height - this is because of the way cocos2d sets up the view in portrait mode (it performs rotation in the CCDirector method applyLandscape).


Once I’d done this, all I needed to do was to position buttons and other objects based on the size of the virtual screen:

VolumePicker *picker = [VolumePicker picker];
[picker setPosition:CGPointMake([[CCDirector sharedDirector] screenWidth]-126,
[[CCDirector sharedDirector] screenHeight]-40)];
[self addChild:picker];

Because we’re scaling up, the difference between the old screen size (480x320) and the new one (512x384) is actually quite small. For me, this meant I could simply reposition objects based on the new screen size. In your own software, there might be a lot more work involved. In my case, there were only two or three images that needed adjustment to work on iPad, but if your layouts depend on a certain screen ratio, you’ll need to redesign them.

Modify touch handling

The next step was to modify the touch handling to accept touches on the whole screen, but convert their position to the size of the virtual screen. I have only two types of touch handling in Space Harvest - using standard CCMenus, and manual handling in the main game scene.

Firstly, I tweaked CCMenu’s itemForTouch: method:

-(CCMenuItem *) itemForTouch: (UITouch *) touch
{
   CGPoint touchLocation = [touch locationInView: [touch view]];
   touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];

   if ([[CCDirector sharedDirector] runningOniPad]) {
   touchLocation = CGPointMake(touchLocation.x/2.0f, touchLocation.y/2.0f);
   }
...

Then, the layer that does all the touch handling in the main viewport:

-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
   NSSet *allTouches = [event allTouches];
   UITouch *touch = [touches anyObject];
   CGPoint location = 
[[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
   if (isiPad) {
      location =CGPointMake(location.x/2.0f, location.y/2.0f);
   }
...

If you’re doing touch handling in other ways, you’ll need to take care to convert the touch location wherever you need it.

Modify rendering

Finally, I needed to modify the way graphics are rendered to scale things up. My first thought was the use a transform on the layer of the GL view, but the documentation seems to suggest this is a bad idea, so I looked for something else.

Once again, cocos2d makes this superbly easy with a class called CCRenderTexture. This essentially allows you to render your scene directly into a texture. You can then turn this into a UIImage, though in our case, we simply need to scale it up and render it to the screen

I modified CCDirector’s mainLoop method like this:

...
   glPushMatrix();
   // By default enable VertexArray, ColorArray, TextureCoordArray and Texture2D
   CC_ENABLE_DEFAULT_GL_STATES();
   
   
   if (isiPad) {
      if (!renderTexture_) {
      
         // Create a render texture that is half the size of the screen
         renderTexture_ = [[CCRenderTexture renderTextureWithWidth:
         [openGLView_ frame].size.height/2 height:[openGLView_ frame].size.width/2]
         retain];
         
         // Scale it up to fill the screen
         [renderTexture_ setScale:2.0f];
         [renderTexture_ setPosition:
         CGPointMake([openGLView_ frame].size.height/2, 
         [openGLView_ frame].size.width/2)];
      }
      
      // Everything between this call and [renderTexture_ end]
      // will render into our rendertexture§
      [renderTexture_ begin];
   } else {
      [self applyLandscape];
   }
   /* draw the scene */
   [runningScene_ visit];
   if( displayFPS )
   [self showFPS];
#if CC_ENABLE_PROFILERS
   [self showProfilers];
#endif

   // Now, stop rendering into the rendertexture
   // and render the rendertexture itself to the screen
   if (isiPad) {
      [renderTexture_ end];
      [self applyLandscape];
      [renderTexture_ visit];
   }
	
   glPopMatrix();
   CC_DISABLE_DEFAULT_GL_STATES();
	
   /* swap buffers */
   [openGLView_ swapBuffers];	
}

I don’t suppose that this technique will be suitable for porting all iPhone OS games to iPad - I suspect most will benefit from making full use of the higher resolution. But I love how Space Harvest feels on the iPad - a decidedly retro game now feels even more authentic. :)

As a special treat, I even added an ‘anti-alias’ option to the iPad version (accessible via the pause menu) that lets you faithfully recreate the experience of playing DOS games on a cheap 14-inch fishbowl CRT monitor(i).

Watch this space

Space Harvest for iPad is a universal application, and will work on iPod Touch and iPhone too. If you already own Space Harvest, the upgrade will be available for the low price of FREE.

  1. Minus all the fiddling around with CONFIG.SYS and EMM386 to get it to run to begin with, that is.

Posted by Ben @ 2:15 PM