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
UIViewsand 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
UIViewControlleris 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.
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:
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.
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]];):
GamePlayLayer* gamePlayLayer = [GamePlayLayer node];
controller = [[CCNodeController controller] retain];
controller.doesAutoRotate = YES;
controller.isOverlayingDeviceCamera = YES;
[controller runSceneOnNode: gamePlayLayer];
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
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.
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
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
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
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
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.
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.
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
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
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
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.
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
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
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.
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.