The Ultimate Cocos2D Project: Libraries

On March 4, 2011, in cocos2d, Kobold2D, by Steffen Itterheim

The Ultimate Cocos2D Project is: Kobold2D!

Put simply: Kobold2D is designed to make Cocos2D developers more productive.

Original Post

Last week I wrote that I’m Building The Ultimate Cocos2D Xcode Project. In today’s weekly update I wanted to give you some more details on the use of libraries in that project.

Cocos3D included

So there happens to be a Cocos3D now. Rather than being part of the Cocos2D distribution, it’s an extension project. Guess what that means? Right, installing Cocos3D means fumbling with the dreaded install-templates.sh script (see this Cocos3D tutorial). Of course the first user reactions were: how do I install it? Installation failed, what am I doing wrong? And so on …

The Ultimate Cocos2D Project wouldn’t be ultimate if it didn’t include Cocos3D out of the box. And unmodified of course, as with all included libraries I want to make it as simple as possible to replace one library version with another. Once you get to half a dozen of included libraries, maintaining them all can become a hassle, so the very least I can do is to make it easy for everyone to upgrade specific libraries.

Obviously: Cocos2D included

Of course Cocos2D is also included as a static library as opposed to cluttering your project with all of its source files. Xcode project references make it very convenient to add external code and keeping it seperate. I’ve described the process in detail in my Cocos2D Xcode Project tutorial but since then I’ve learned a couple more things about how to make this even better.

For example, I no longer include cocos2d-iphone directly, instead there’s a seperate Xcode project in between so that I have full control over build settings (using XCConfig files) and make it possible to build both iOS and Mac OS targets in the same Xcode project. I will also include the current version of Cocos2D in the download because my goal is to make everything work out of the box.

No fumbling with install scripts, no additional downloads necessary, no need to modify any Xcode build settings – including developer certificates and header search paths. Build configurations for Ad Hoc and App Store release builds are also included, which will create .IPA and .ZIP files for you ready for Ad Hoc distribution respectively upload on iTunes Connect.

Popular libraries included

Now let’s get to the juicy part. Early on I realized that Cocos2D users often needed (or wanted) to include other libraries. Some of them have become so popular among the Cocos2D crowd that they could as well be part of the official distribution. Alas, they’re not. That’s a service I want to provide.

Often those libraries require special and non-obvious steps to successfully add them to an existing project. All too often those steps are either undocumented, untested, hard to follow, refer to outdated versions of Xcode, iOS SDK, etc. and generally require technical expertise of project configuration and compiler settings.

This is all taken care of for you. Here’s the list of libraries that are already included in the Ultimate Cocos2D Xcode Project:

That is quite a list. All you need to do to use these libraries is to either enable them in code or merely include the header file and start using them. If you worry that all these libraries will bloat your App, rest assured that Xcode is very clever: if you don’t actually make use of a static library (eg don’t include any of its header files), it will not be linked with your App and not waste any space or performance. I verified that.

Update policy

These are a lot of libraries to keep up to date. I plan to make about 4-8 point releases each year, usually triggered by a major (speak: non-beta) release of Cocos2D. If updating other libraries justifies an update depends on the library’s importance and the significance of the update.

Your libraries

Adding your own libraries to the project will be easy and the process will be documented. This will encourage code-sharing because your library will just work with other user’s project, it only needs to follow a few simple guidelines to become plugin-capable. This opens the door for better and tighter integration of 3rd party code into your projects. Even if you don’t intend to share your code, you’ll still benefit because your code will be easier to re-use and maintain.

Also, if you like you can make a request for a specific library or additional source code that should be included in the project, please leave a comment. I’ll see what I can do. :)

Another Cocos2D gem: ClippingNode

On January 18, 2011, in cocos2d, Programming, by Steffen Itterheim

I needed a way to clip the contents of a node to a specific area of the screen. The goal was to create a scrollable list of items and clipping the items at the top and bottom as they are being scrolled up and down on the screen. The list is of course longer than the screen is high. While you can achieve the same effect by drawing a sprite on top of the scrolling view, I was looking for a cleaner, more flexible and faster solution.

That’s where the glScissor command comes into play. Unfortunately the coordinates are always assuming portrait mode, so you have to rotate them to be able to use Cocos2D coordinates with glScissor. I did some research and found the solution for transforming glScissor coordinates but the rest of that code is too verbose (eg 9 lines instead of the 3 lines needed to transform the Landscape coordinates) and inefficient by needlessly transforming the coordinates every frame.

So I ended up making my own subclass of CCNode called ClippingNode whose children are only drawn within the clippingRegion CGRect (it’s in points). It takes into consideration device rotation and only adjusts the coordinates when either the device rotation changes (name:UIDeviceOrientationDidChangeNotification) or whenever the clippingRegion is updated. In addition the node sets its position at the lower left corner of the clipping region with the contentSize set to the clippingRegion size. By doing so children of the ClippingNode can access the clipping region without having to know that the parent is a ClippingNode class.

To use the ClippingNode, simply add it to your scene hierarchy, then add all other nodes (sprites, labels, etc.) which you want to clip to the ClippingNode. The ClippingNode children will only be drawn with whatever parts are inside the clippingRegion. All other nodes which you do not want to clip you just add to the scene hierarchy as usual. You can of course use two ClippingNodes side-by-side, for example to create a splitscreen view.

ClippingNode.h

[cc lang=”objc”]
#import
#import “cocos2d.h”

/** Restricts (clips) drawing of all children to a specific region. */
@interface ClippingNode : CCNode
{
CGRect clippingRegionInNodeCoordinates;
CGRect clippingRegion;
}

@property (nonatomic) CGRect clippingRegion;

@end
[/cc]

ClippingNode.m


#import "ClippingNode.h"

@interface ClippingNode (PrivateMethods)
-(void) deviceOrientationChanged:(NSNotification*)notification;
@end

@implementation ClippingNode

-(id) init
{
  if ((self = [super init]))
  {
    // register for device orientation change events
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationChanged:) 
                    name:UIDeviceOrientationDidChangeNotification object:nil];
  }
  return self;
}

-(void) dealloc 
{
  [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
  [super dealloc];
}

-(CGRect) clippingRegion
{
  return clippingRegionInNodeCoordinates;
}

-(void) setClippingRegion:(CGRect)region
{
  // keep the original region coordinates in case the user wants them back unchanged
  clippingRegionInNodeCoordinates = region;
  self.position = clippingRegionInNodeCoordinates.origin;
  self.contentSize = clippingRegionInNodeCoordinates.size;

  CCDirector* director = [CCDirector sharedDirector];
  CGSize screenSize = [director winSize];

  // glScissor requires the coordinates to be rotated to portrait mode
  switch (director.deviceOrientation)
  {
    default:
    case kCCDeviceOrientationPortrait:
      // do nothing, coords are already correct
      break;
      
    case kCCDeviceOrientationPortraitUpsideDown:
      region.origin.x = screenSize.width - region.size.width - region.origin.x;
      region.origin.y = screenSize.height - region.size.height - region.origin.y;
      break;
      
    case kCCDeviceOrientationLandscapeLeft:
      region.origin = CGPointMake(region.origin.y, screenSize.width - region.size.width - region.origin.x);
      region.size = CGSizeMake(region.size.height, region.size.width);
      break;
      
    case kCCDeviceOrientationLandscapeRight:
      region.origin = CGPointMake(screenSize.height - region.size.height - region.origin.y, region.origin.x);
      region.size = CGSizeMake(region.size.height, region.size.width);
      break;
  }
  
  // convert to retina coordinates if needed
  region = CC_RECT_POINTS_TO_PIXELS(region);
  
  // respect scaling
  clippingRegion = CGRectMake(region.origin.x * scaleX_, region.origin.y * scaleY_, 
              region.size.width * scaleX_, region.size.height * scaleY_);
}

-(void) setScale:(float)newScale
{
  [super setScale:newScale];
  // re-adjust the clipping region according to the current scale factor
  [self setClippingRegion:clippingRegionInNodeCoordinates];
}

-(void) deviceOrientationChanged:(NSNotification*)notification
{
  // re-adjust the clipping region according to the current orientation
  [self setClippingRegion:clippingRegionInNodeCoordinates];
}

-(void) visit
{
  glPushMatrix();
  glEnable(GL_SCISSOR_TEST);
  glScissor(clippingRegion.origin.x + positionInPixels_.x, clippingRegion.origin.y + positionInPixels_.y,
      clippingRegion.size.width, clippingRegion.size.height);
  
  [super visit];
  
  glDisable(GL_SCISSOR_TEST);
  glPopMatrix();
}

@end