cocos2d and UIViewControllers

[bw_floater]

[bw_download title=”Download”]

[download id=”3″]

[download id=”4″]

[/bw_download]

[bw_paypal_donation amount=”10″]

[/bw_floater]

This cocos2d framework package is a wonderful framework for working with OpenGL ES within iOS. Since it focuses on high performance OpenGL ES code, for the most part cocos2d bypasses Apple’s Cocoa UIViewController hierarchy. This makes sense, since within iOS, OpenGL ES usually plays out on a single UIView, and typically in a single display orientation, so there’s usually not much to control.

However, there are occasions where the functionality provided by UIViewController can be quite useful. In particular, the following functionality is best handled by adding a UIViewController into the mix:

  • Auto-rotation – in some games, or other OpenGL ES applications, it makes sense to change the orientation of the application display to match changes in the orientation of the physical device. This means that when the user flips the device from landscape to portrait mode, or back again, the application display will adjust itself accordingly. The auto-rotation works on cocos2d nodes directly and does not rotate the underlying iOS Cocoa UIView. If you are interested in combining cocos2d nodes with UIViews and having them auto-rotate together, please review cocos2d support for that here.
  • Device camera overlay – an increasingly popular use for iOS devices is to overlay a game or app view on top of the image visible through the device’s camera. This is known as augmented reality, and provides the user with a combined view of the real world overlaid with images generated by the application. For this use, a UIViewController is mandatory, since it controls the merging of the real-world and generated image streams.

Here at The Brenwill Workshop, some of our iOS applications actually use both of these features, and we’ve developed a small framework that can easily add the capabilities above to any cocos2d application. This article describes the design and use of this framework.

Examples

Since a picture is worth a thousand words, let’s start with sample screenshots from the example game, showing the automatic rotation of the game layer when the device orientation is changed, and the game layer overlaying the device camera image:

Game layer in portrait orientation with colored backdrop

Game layer in portrait orientation with colored backdrop.

Game layer in landscape orientation with colored backdrop

Game layer rotated to landscape orientation. The player and sunshine button have moved to their correct positions relative to the new orientation.

Game layer overlaying the device camera image

Game layer overlaying the device camera image.

Although it is not shown here, the two features are compatible, of course, and the device camera overlay will also rotate along with changes in the device orientation.

Notice that, in the rotation from portrait to landscape orientation, the player image on the left has moved to the vertical center of the new orientation and the sunshine button in the lower right corner has stayed on-screen and in the same position relative to that corner. The game layer subclass is automatically notified of changes in the device orientation to give it a chance to adjust the positions of its child nodes to suit the new orientation.

Quick Start

This framework is centered on two classes: a node controller, CCNodeController, and a controlled layer, ControllableCCLayer. Setting up the controller is simple, and is handled in the applicationDidFinishLaunching: method of your UIApplicationDelegate implementation, during normal cocos2d startup. Add the following code at the end of this method (replacing the existing line: [[CCDirector sharedDirector] runWithScene: [[CCScene node] addChild: myLayer]];):

[objc]
GamePlayLayer* gamePlayLayer = [GamePlayLayer node];
controller = [[CCNodeController controller] retain];
controller.doesAutoRotate = YES;
controller.isOverlayingDeviceCamera = YES;
[controller runSceneOnNode: gamePlayLayer];
[/objc]

That’s it! You now have a cocos2d application that will rotate itself automatically as the device is rotated, and display itself over the camera view if the device has a camera.

In the first line of code above, GamePlayLayer is your subclass of ControllableCCLayer. In the second line, you create and retain the controller in a controller instance variable you add to your UIApplicationDelegate class (don’t forget to release it in the dealloc method).

In the third and forth lines you tell the controller to automatically rotate the game layers and overlay those layers on top of the device camera. No need to worry about whether the device actually has a camera. The isOverlayingDeviceCamera property can only be set to YES if the device has a camera, and it will be ignored otherwise.

In the last line, the controller sets the GamePlayLayer as its controlled node, wraps it in a CCScene, and tells CCDirector to run the scene. This last line replaces the typical [[CCDirector sharedDirector] runWithScene: [[CCScene node] addChild: myLayer]]; line normally found at the end of the applicationDidFinishLaunching: method within a cocos2d application.

CCNodeController, ControlledCCNodeProtocol, and ControllableCCLayer

Central to this framework is the CCNodeController class, which is a subclass of UIViewController and provides the bridge to iOS to receive automatic notifications of changes in the orientation of the device and to control the device camera display. When using this framework, you will need to instantiate a CCNodeController, but you will usually have no need to subclass CCNodeController, unless you have very specialized requirements.

As the name suggests, UIViewController is designed to control a UIView, also from Apple’s Cocoa UIKit framework. Although cocos2d does make use of a UIView under the covers and the CCNodeController does in fact control that UIView, practical OpenGL rendering is handled by the cocos2d CCNode hierarchy. Therefore, to make the controller useful to cocos2d, we need a bridge between the controller and the CCNode hierarchy.

Enter the ControlledCCNodeProtocol protocol, which defines the requirements that a CCNode must support for it to be controlled by a CCNodeController. These requirements are actually relatively simple, amounting to tracking the controller itself via a controller property, and handling notification of changes in the device orientation through a deviceOrientationDidChange: method.

In Apple’s UIKit framework, although a UIViewController can control any UIView subclass, in practice controllers are almost always applied to control the top-most UIView within a window scene. Similarly, although a CCNodeController can control any CCNode subclass that supports ControlledCCNodeProtocol, in practice it is the main CCLayer at the level of the scene that is of most interest.

In recognition of this, the framework here includes a ControllableCCLayer class that adds the behaviour defined by ControlledCCNodeProtocol to CCLayer. This class is subclassed from CCColorLayer and supports initialization either with or without a background color. In your application, you derive subclasses of ControllableCCLayer instead of CCLayer, to allow your layers to rotate automatically and overlay the device camera.

ControllableCCLayer automatically resizes as it rotates, and there are hooks for the subclasses to latch onto to reposition child nodes if needed. It is also smart enough to know that if it has been configured with a colored background, it should not draw it when overlaying the device camera, but should present a transparent background instead.

Typically, your cocos2d application will have a number of scenes, each with a primary layer. Since cocos2d reuses a single UIView instance on which to draw scenes, you will need only a single CCNodeController instance. But as the layers are swapped in and out during game play by the CCDirector, the CCNodeController must be kept in sync so that it knows which layer is currently active. You can use the controlledNode property on the CCNodeController to set the active layer. Or, to make this synchronization task easier, your application can use runSceneOnNode:, a convenience method on CCNodeController, to start or swap out a layer in both the CCDirector and the CCNodeController simultaneously with a single call.

Auto-rotation on Device Orientation Changes

The doesAutoRotate property of the CCNodeController enables and disables automatic rotation of the controlled node. When this property is set to YES, the controller will start to listen for notifications of device orientation change from iOS and propagate those notifications to the cocos2d framework as they arrive. When this property is set to NO, the controller will stop listening for iOS notifications. To automatically rotate a ControllableCCLayer in your application, all you need to do is set this property to YES, and attach the layer to the controller, typically using the controller’s runSceneOnNode: method, as described in the previous section.

With the doesAutoRotate property active, when the CCNodeController is notified by iOS that the device orientation has changed, it informs the cocos2d framework of the change, and also calls the deviceOrientationDidChange: method on the controlled node.

ControllableCCLayer is the most commonly used controlled node class. In most cocos2d applications, layers cover most or all of the device screen and, when the device orientation changes, the contentSize of the layer should generally change to match the new orientation. The ControllableCCLayer property alignContentSizeWithDeviceOrientation controls whether the layer will automatically adjust its contentSize to align with the new orientation. In particular, if this property is set to YES, and the device orientation changes from portrait to landscape or vice versa, the contentSize of the layer will automatically change to its transpose. When changing between two landscape orientations (landscape-right or landscape-left) or two portrait orientations (normal or upside down), the contentSize will remain unchanged. This property is initially set to YES. However, if your application has a layer subclass that is smaller than full screen, you might find occasion to set this property to NO so that the contentSize (and shape aspect) will remain steady through rotations between portrait and landscape orientations.

To provide a hook for subclasses of the ControllableCCLayer to know when the contentSize has changed, the ControllableCCLayer method didUpdateContentSizeFrom: is called automatically whenever the contentSize of the layer changes. Your subclass can override this method to adjust the positions of any child nodes after a rotation. For example, to keep a button or label in, say, the center or the bottom-right corner of the layer after a change in the contentSize of the layer, the subclass can update the position of the button or label accordingly in didUpdateContentSizeFrom:.

Finally, note that the auto-rotation works on cocos2d nodes directly and does not rotate the underlying iOS Cocoa UIView. If you are interested in combining cocos2d nodes with UIViews and having them auto-rotate together, please review cocos2d support for that here.

Overlay the Device Camera View

The CCNodeController property isOverlayingDeviceCamera controls whether the controlled node will be displayed over the view of the device camera. Your application only need only toggle this value to cause the overlaying effect to be applied or removed. The CCNodeController takes care of coordinating all the activities needed to make it happen. This property cannot be set to YES if the device does not actually have a camera. Attempting to do so is safe, because the action is simply ignored and the controlled node will continue to behave as it does when not overlaying the device camera. To find out if this property can successfully be set, you can check the isDeviceCameraAvailable property of CCNodeController, or you can simply set the isOverlayingDeviceCamera property and then check its value to see if the change was successful. The value of the isOverlayingDeviceCamera property can be changed at any time, and it is up to your application to determine when, if ever, that should happen. For instance, you might change it upon a scene transition, or even during active game play via a user-controlled on-screen button.

Within iOS, applying or removing an overlay on the device camera is not a trivial activity. Although your application interacts only with the isOverlayingDeviceCamera property, a lot happens under the covers. One important aspect is that in order to display an overlay on the camera image, a second specialized UIViewController (UIImagePickerController) is created and displayed modally. This does not specifically affect the use of cocos2d, but may impact applications that combine cocos2d visual components with standard UIKit views and controls. It also impacts the visual flow of your game, since going from not overlaying to overlaying can take a few seconds and involves a mandatory display of a camera iris image by iOS.

Another important aspect of swapping the device camera overlay in and out is that the running scene must be stopped and restarted on each change between overlaying and not overlaying or back again. This is needed to ensure that active actions and touch events are cleaned up correctly between transitions. For the most part, this effect is typically transparent to the application. As part of the stopping and restarting of the scene, the onExit and onEnter methods on the controlled node are called just before and just after the switchover, respectively. Your controlled node can make use of these calls as hooks to know when the switchover occurs in order to update state accordingly. Typically this might include hiding some visual items and showing others. For example, when the device camera image is visible, you might hide a background image or skybox and show some camera controls.

Fantastic! Where Can I Get Me Some of That?

You can download the source code for this framework in the download area at the top-right of this article. It is distributed under an MIT license, which makes it free for you to use in your projects. However, if you find this code is useful, please remember to make a donation above to help us fund the ongoing development and support of frameworks such as this.

Once downloaded, unzip the file, and drag the extracted directory to your Classes group within your Xcode project. On the dialog that pops up when you do that, make sure both the Copy items into destination group’s folder and Recursively create groups for any added folders options are both selected.

You can also download a full Xcode project of a simple example game that demonstrates the use of node controllers for both auto-rotation and device camera overlay. Simply unzip the downloaded file to extract the full project directory. The game is based on an example developed by Ray Wenderlich and published by him as a tutorial. Our version also makes use of a framework of cocos2d UI Controls that we developed, and which also may be of interest to you for your cocos2d projects.

16 comments to cocos2d and UIViewControllers

  • […] Update: Bill Hollings wrote an interesting article regarding autorotation where he describes another approach. Includes sample code: cocos2d an UIViewControllers […]

  • This looks interesting as I’ve been playing with auto-rotation on the iPad.

    You mentioned in the comments in the linked article (Autorotation in v0.99.5) that this fromework “- does NOT support UIView rotations … so UIKit overlays are not auto-rotated”. It may be worth mentioning this here, since that is what I was hoping for, i.e. being able to add, say, a UITable to a cocos2d layer and have it all auto-rotate.

    Thanks for the postings.
    Cheers

  • Paul

    Hi Bill,

    I want to do something similar to the camera overlay, but rather than overlaying the camera, I was hoping to overlay a movieplayer view… does that seem reasonable, or is that not going to work?

    Thanks,

    Paul

    • Hi Paul…

      Sorry for the delay. I was out and about over the long weekend.

      I don’t think this framework is going to help you overlay video file playback. The camera picker controller is generally for recording only, not playback.

      However…you may be able to combine cocos2d with the standard movie playing UIViews. There are a few articles in the cocos2d forum that discuss combining cocos2d with UIViews. Also…riq has written about the subject a bit here and Frogblast here.

      Sorry I couldn’t be more help.

      Good luck…

      …Bill

  • Nick

    Hi Bill,

    This is a great tutorial! I was wondering if you were planning on updating it to coincide with the latest developments of Cocos2D? The auto-rotation is no longer an issue, and now there is a UIViewController (RootViewController).

    Thanks!

    Nick

  • Dragan

    Hi, I’m new to cocos3d and cocos2d, and my question is if there is a way to get the camera input image by image, because I need to perform face recognition and not just show it in the background of the object. Thanks!

  • 89hardy

    Hey Bill,
    amazing tut. Your link for full Xcode project of a simple ‘example game’ that demonstrates the use of node controllers for both auto-rotation and device camera overlay does not work. Please look into it.
    You’re doing great.
    Thanks.

  • […] cocos2d and UIViewControllers « The Brenwill Workshop As the name suggests, UIViewController is designed to control a UIView , also from Apple’s Cocoa UIKit framework. […]

  • eskimo1nyc

    This app compiles fine on my system (using Xcode 4.3.1 with latest cocos2d and 3d, iOS 5.1 and mac os 10.7.3). You will need to change your c/c++ compiler settings from GCC 4.2 to Apple LLVM compiler 3.1 (located under build options).

  • Apurv Shukla

    Hi Bill
    I am trying to load current CC3Scene in my game….unfortunately nothing is working for me.