cocos2d UI Controls

[bw_floater]

[bw_download title=”Download”]

[download id=”11″]

[/bw_download]

[bw_paypal_donation amount=”10″]

[/bw_floater]

This cocos2d framework package includes several useful cocos2d user interface controls and frameworks, including:

  • Joystick – a flexible joystick for user control in two dimensions with a single finger
  • CCNodeAdornments – a framework for assigning adornments to CCNodes to temporarily change the appearance of the CCNode, such as scaling, fading, adding visual embellishments, etc. This is most useful when used with menu items to temporarily change the appearance of the menu item during user selection.

Joystick

For many games, controlling the action using a joystick is an integral and indispensable part of the game.

The joystick developed by us here is a subclass of CCLayer that contains two CCNode children, each of which is typically a CCSprite image. The thumb image is a smaller image that the user moves by touching and dragging. The second is an optional background image over which the thumb image is moved. The sizes of the thumb and background nodes can be set independently, or in the case of a joystick with no background image, the size of the Joystick node itself is set during initialization instead.

The following figure shows a Joystick control with a round green thumb and no background image controlling the player movement in a first-person game within a three-dimensional world. Like most first-person games, the player can be moved forward or backward and turn left or right by user movement of the joystick control.

Example of a Joystick control in use
A simple round green Joystick thumb, without a background image, controlling player motion in a simple first-person perspective game.

This is a visually trivial example of the Joystick control, stripped to the basics. In a real game, much more interesting thumb and background images would of course be applied to match the look and feel of the game.

To use the joystick, the user touches within the bounds of the Joystick node. Movement of the thumb image is constrained to the size of the Joystick node, however, the user may continue dragging outside the bounds of the joystick without losing control of the joystick. When the user lifts the finger from the screen, the joystick thumb will glide smoothly back to the center of the joystick (complete with a little bounce as it centers), just like a real physical joystick.

Creating a Joystick in your code is simple, as shown in the following, which sets up the Joystick shown above:

[objc]
CCSprite* jsThumb = [CCSprite spriteWithFile: kJoystickThumbFileName];
// jsThumb.scale = 0.80; // change thumb size if you like
joystick = [Joystick joystickWithThumb: jsThumb
andSize: CGSizeMake(kJoystickSideLength, kJoystickSideLength)];
joystick.position = ccp(kJoystickPadding, kJoystickPadding);
[self addChild: joystick];
[/objc]

If a Joystick with a background image is desired instead, just use a different creation method:

[objc]
CCSprite* jsThumb = [CCSprite spriteWithFile: kJoystickThumbFileName];
// jsThumb.scale = 0.80; // change thumb size if you like
CCSprite* jsBackdrop = [CCSprite spriteWithFile: kJoystickBackdropFileName];
joystick = [Joystick joystickWithThumb: jsThumb
andBackdrop: jsBackdrop];
joystick.position = ccp(kJoystickPadding, kJoystickPadding);
[self addChild: joystick];
[/objc]

The application can read the position of the Joystick thumb, relative to its resting position, at any time from either the velocity property or the angularVelocity property.

The velocity property returns a CGPoint reporting the relative position of the thumb in cartesian X-Y coordinates. The X-Y values returned range from -1 to +1, with zero being the center position. However, the magnitude of the velocity vector is clamped to unit length, so the set of possible points returned by the velocity property therefore covers the area of a circle of unit radius, centered on zero. This means, for instance, that pulling the thumb as far as it will go up and right will not take both X and Y each to a value of one at the same time. Instead, the thumb will stop when the radial distance from the origin is equal to one. This design provides a more natural range of values for controlling motion within a game.

The angularVelocity property returns a structure reporting the position of the thumb in an angular coordinate system, in terms of an angle plus a radius. The angle is given in positive or negative degrees from vertical (forward), and the radius ranges from zero to one. As with the velocity property, the set of points returned by the angularVelocity property therefore also covers the area of a circle of unit radius.

To add a Joystick to your game, download and install the cocos2d UI Controls framework code into your Xcode project as described below, then #import the Joystick.h file into your code where it is needed, typically into a custom CCLayer subclass file. The Joystick.h header file contains extensive documentation on the Joystick API and is your best source for understanding how to use it in your project.

CCNodeAdornments

Adornments are visual components that can be added to a subclass of CCNode to provide a visual appearance that can be activated and deactivated on demand. Familiar examples of this might be the icon tags associated with iOS app icons. Or within a cocos2d game, adornments can provide visual feedback when a user touches a game element, menu item, or other button-like nodes. The example shown below is the same scene shown in the Joystick description above, but in this case, the user is currently pressing the sunshine button (actually a CCMenuImageToggle) on the right. Doing so has activated the adornment and a yellow ring has faded in around the button. When the user releases the button, or drags his finger away from the button, the adornment will be deactivated, and the ring will fade away again.

Example of an adorned button
The sunshine button on the right is being pressed by the user and a yellow ring adornment has faded in around the button.

To be clear, this image shown here is not the alternate toggled image of the CCMenuItemToggle. Nor is it the selected image of a CCMenuItemImage, which cannot be made to fade in and out in an animated manner. The adornment is a separate image that appears over or under the basic images of the menu item. In fact, in the action above, once the user releases the button, the CCMenuItemToggle itself will toggle to a different image altogether to show the changed state. The adornment is displayed only during the transitional stage while the user is actually pressing the button.

Instead of a ring, the adornment image could also have been a semi-transparent colored “shine” image that faded in and out over the main button image as the user touches and releases the button.

Another type of adornment scales the adorned node when activated. For example, the button could be made to grow in size when touched, and shrink back again when released, with the scaling up and down being animated for effect. Should fading and scaling seem boring to you, other adornment effects can easily be developed should you have other interesting effects in mind (spinning, changing color, etc).

Using adornments is fairly straightforward. At the very basic level, an adornment can simply be added as a child to any CCNode, and then tracked, activated and deactivated manually by the application. But where adornments really become interesting is when a CCNode is made adornable so it can automatically activate and deactivate its adornment at the appropriate times.

To make a CCNode adornable, you first create a subclass of the CCNode and add the AdornableCCNodeProtocol protocol to it. In this framework, we’ve done that already for CCMenuItemToggle and CCMenuItemImage, and tied the activation and deactivation of its adornment to the selected and unselected actions of the adornable subclasses. So when an AdornableMenuItemToggle is selected by the user, its adornment is automatically activated, and when the menu item is unselected, the adornment is automatically deactivated. As described above, depending on the adornment type, when the user selects and unselects the menu item, this might expand and shrink the menu item, or might fade in and out a shine or ring on top of the menu item.

The adornments themselves are CCNodes that support the CCNodeAdornmentProtocol protocol. To support development of new adornments, we’ve included in the framework an abstract CCNodeAdornmentBase class, along with concrete CCNodeAdornmentOverlayFader and CCNodeAdornmentScaler classes to perform the overlay fading and node scaling I’ve been describing above.

Putting this into practice then, the scene displayed in the picture above, with the yellow adornment ring, was created with the following code in the custom CCLayer that forms the scene:

[objc]
// Toggle between two subitems: one for shutter button, one for sky button
CCMenuItem* camMI = [CCMenuItemImage itemFromNormalImage: kShutterButtonFileName
selectedImage: kShutterButtonFileName];
CCMenuItem* skyMI = [CCMenuItemImage itemFromNormalImage: kSkyButtonFileName
selectedImage: kSkyButtonFileName];
backdropToggleMI = [AdornableMenuItemToggle itemWithTarget: self
selector: @selector(changeBackdropSelected:)
items: camMI, skyMI, nil];
backdropToggleMI.position = ccp(self.contentSize.width – kBackdropToggleMenuInset, kBackdropToggleMenuInset);

// Instead of having different normal and selected images, the toggle menu item uses an
// adornment, which is displayed whenever an item is selected.
CCNodeAdornmentBase* adornment;

// The adornment is a ring that fades in around the menu item and then fades out when
// the menu item is no longer selected.
CCSprite* ringSprite = [CCSprite spriteWithFile: kButtonRingFileName];
adornment = [CCNodeAdornmentOverlayFader adornmentWithAdornmentNode: ringSprite];
adornment.zOrder = kAdornmentUnderZOrder;

// Attach the adornment to the menu item and center it on the menu item
adornment.position = backdropToggleMI.anchorPointInPixels;
backdropToggleMI.adornment = adornment;

CCMenu* bgMenu = [CCMenu menuWithItems: backdropToggleMI, nil];
bgMenu.position = CGPointZero;
[self addChild: bgMenu];
[/objc]

If you would rather see the “shine” adornment described above, replace lines 17-19 above with:

[objc firstline=”17″]
CCSprite* shineSprite = [CCSprite spriteWithFile: kButtonShineFileName];
shineSprite.color = ccYELLOW;
adornment = [CCNodeAdornmentOverlayFader adornmentWithAdornmentNode: shineSprite
peakOpacity: kPeakShineOpacity];
[/objc]

Or to have the menu item grow and shrink when pressed by the user, replace the original lines 17-19 above with:

[objc firstline=”17″]

adornment = [CCNodeAdornmentScaler adornmentToScaleUniformlyBy: kButtonAdornmentScale];

[/objc]

To add adornments to your project, download and install the cocos2d UI Controls framework code into your Xcode project as described below, then #import the CCNodeAdornments.h file into your code where it is needed. That header file also contains extensive documentation on the adornments API and is your best source for understanding the adornments framework. And once you get some experience with the framework, don’t be timid about extending the framework by inventing your own adornments!

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.

Credit Where Credit is Due

Our Joystick code was inspired by code donated to the cocos2d community by cocos2d user adunsmoor. The original inspirational code can be found here.


Posted

in

by

Comments

25 responses to “cocos2d UI Controls”

  1. Fantastic stuff! Thanks for sharing this!

    1. Thanks “Dad”!
      Remember to check back once in a while. As we develop new controls for our iOS apps, we’ll release them into this framework package and publish the updates here.

      …Bill

  2. Logan

    This is very useful thanks. I have been trying to figure out how to get a joystick working in my game but all of the existing tutorials were very old or didn’t work, or both.

  3. Sam

    Hello, on trying to use your code with the newest 4.1 sdk, I’ve run into an issue that gives me the following error. Error: ‘isTouchEnabled’ undeclared is what it tells me. I literally imported it into a new cocos chipmunk project and hit build and this is what comes up. If I comment it out, the program builds fine. I’m an extremely new programmer and am trying to build a game with cocos/your joystick.

  4. Hi Sam…

    The instance variable isTouchEnabled is declared in CCLayer, which is the superclass of Joystick. It’s even used in the HelloWorld example when you create a new project, so you can see how it’s used when you create a brand new Chipmunk project from the template.

    What verison of cocos2d are you using?

    …Bill

  5. Sam

    I am running cocos2d v0.99.5 beta. I realize that it is used in the HellowWorld example, which is why I thought it was strange that it would be giving a compile error the second I imported it into the project, just wanted to see if there was anything I was missing before I went around and tried to change things around myself.

  6. Hi Sam…

    I’m not sure why you’re having trouble.

    Just to make sure, I created a new cocos2d 0.99.5-beta Chipmunk project from the cocos2d 0.99.5-beta Chipmunk templates, followed the directions above to download, extract, and drop the Controls directory into the project. Then, in the HelloWorldScene.m file, I added:[objc]#import "Joystick.h"[/objc] in the import section at the top of the file and the following code to the end of the init method:

    [objc]
    CCSprite* jsThumb = [CCSprite spriteWithFile: @"Icon.png"];
    CCSprite* jsBackdrop = [CCSprite spriteWithFile: @"Default.png"];
    Joystick* joystick = [Joystick joystickWithThumb: jsThumb andBackdrop: jsBackdrop];
    joystick.position = ccp(20.0, 20.0);
    [self addChild: joystick];
    [/objc]

    It compiled and ran just fine.

    The only difference that I can see is that I used iOS SDK 4.02, rather than your use of 4.1, but that should not matter, since it’s the cocos2d code that you indicated is not working.

    …Bill

  7. Hi Sam,

    I love your Joystick class! it’s working fine on my app.

    But unfortunately I still can’t figure out how to relate the joystick (velocity, angularVelocity) to my sprite’s movement… @@

    Could you please provide sample code ? Thanks!!

    1. Hi Hagen…

      Thanks for your comments!

      It’s best to think of the velocity as just that, a velocity. So typically you would multiply the joystick velocity components (x or y) by the cocos2d update time to add a degree of movement to your sprite or object. In the example above, it’s controlling the rotation of a 3D camera, so it uses code like:
      [objc]
      static const CGPoint kPlayerMotionScale = { 60.0, 4.0 };
      CGPoint playerMotion = ccpCompMult(joystick.velocity, kPlayerMotionScale);
      if(playerMotion.x) {
      sio2TransformRotateZ(camTform, -playerMotion.x * dt);
      }
      [/objc]
      where kPlayerMotionScale represents a scaling factor that basically sets the sensitivity of your joystick relative to sprite movement. The scaled joystick velocity is then multiplied by the cocos2d update time, dt, from the scheduled update: (ccTime) dt method of your CCLayer. The result is then fed into a function that rotates the camera by a certain number of degrees around the Z-axis (the camera function itself is from SIO2, but that part is really immaterial).

      If you’re controlling a typical cocos2d node or sprite, you could use the result of playerMotion.x * dt and playerMotion.y * dt to add to the sprite’s current position property X and Y values:
      [objc]
      static const CGPoint kPlayerMotionScale = { 60.0, 4.0 };
      CGPoint playerMotion = ccpCompMult(joystick.velocity, kPlayerMotionScale);
      CGPoint playerMovement = ccpMult(playerMotion, (CGFloat)dt);
      mySprite.position = ccpAdd(mySprite.position, playerMovement);
      [/objc]
      where, again, kPlayerMotionScale represents a scaling factor that basically sets the sensitivity of your joystick relative to sprite movement.

      If, like our camera example above, you’re using the joystick X velocity to control the rotation of the sprite, you would add the playerMotion.x * dt value to the current rotation of the sprite:
      [objc]
      mySprite.rotation = mySprite.rotation + playerMotion.x * dt;
      [/objc]

      I hope that helps.

      …Bill

  8. Fredrik Ludvigsen

    Hi! Thank you so much for sharing this!
    I had to change [objc]isTouchEnabled = YES[/objc] to [objc]self.isTouchEnabled = YES[/objc] to make it work, but it seems to be ok now.

  9. Does this work with regular Cocos2D templates? I keep getting an error that Joystick was undeclared.

    1. Hi Tate…

      There shouldn’t be a problem. It’s been used a number of times.

      What error are you getting?

      …Bill

    2. I fixed it never mind…

  10. Alejandro Diaz

    Hello Bill Hollings;

    Do you know if i can use cocos 2d to make a first person game? can you give me a tutorial or something?

    Thanks for your help;

    Alejandro

    1. Hi Alejandro…

      You can use cocos2d to create many interactive games in 2D. You can use cocos3d to create games in 3D, including first-person shooter type games.

      Tutorials on cocos2d are here. An intro to cocos3d is here.

      …Bill

  11. Fredrik Ludvigsen

    Hi!
    I just got a little bit burned by the retina display.

    Here’s a patch for you,

    Fredrik Ludvigsen


    diff --git a/Classes/Controls/Joystick.m b/Classes/Controls/Joystick.m
    index 5a90715..ed870f9 100755
    --- a/Classes/Controls/Joystick.m
    +++ b/Classes/Controls/Joystick.m
    @@ -67,7 +67,7 @@
    thumbNode = aNode;
    [self addChild: thumbNode z: 1];
    self.contentSize = size;
    - [thumbNode setPosition: self.anchorPointInPixels];
    + [thumbNode setPositionInPixels: self.anchorPointInPixels];

    delegate = dlg;
    }
    @@ -82,7 +82,7 @@
    NSAssert(bgNode, @"Backdrop node must not be nil");
    if( (self = [self initWithThumb: aNode andSize: bgNode.scaledSize andDelegate:dlg]) ) {
    // Position the background node at the center and behind the thumb node
    - [bgNode setPosition: self.anchorPointInPixels];
    + [bgNode setPositionInPixels: self.anchorPointInPixels];
    [self addChild: bgNode z: 0];

    delegate = dlg;
    @@ -150,7 +150,7 @@
    // position of the thumb node to track the users movements, but constrained
    // to the bounds of a circle inscribed within the Joystick contentSize.
    -(void) trackVelocity:(CGPoint) nodeTouchPoint {
    - CGPoint ankPtPx = self.anchorPointInPixels;
    + CGPoint ankPtPx = ccpMult(self.anchorPointInPixels, 1 / CC_CONTENT_SCALE_FACTOR());

    // Get the touch point relative to the joystick home (anchor point)
    CGPoint relPoint = ccpSub(nodeTouchPoint, ankPtPx);
    @@ -190,7 +190,7 @@
    angularVelocity = AngularPointZero;
    [thumbNode runAction: [CCEaseElasticOut actionWithAction:
    [CCMoveTo actionWithDuration: kThumbSpringBackDuration
    - position: self.anchorPointInPixels]]];
    + position: ccpMult(self.anchorPointInPixels, 1.0f / CC_CONTENT_SCALE_FACTOR())]]];
    }

    @end

    1. Thanks, Fredrick.

      What was the problem that you encountered?

      …Bill

  12. Giancarlo Arias

    Hi,
    I tried your code, and works brilliantly. My concern is about retina. Maybe I am doing something wrong, but it seems the joystick image position does not match the actual position to which you can control it.

    Thanks for the code!

  13. Giancarlo Arias

    Hi,

    I didn’t notice at first that Bill’s comment was about the retina issue. This fixed my issue.
    Thanks guys!

  14. Giancarlo Arias

    Hi Again,

    The issue now is that velocity are reversed when on the non retina and retina version 🙁

    1. Hi Giancarlo & Fredrik…

      My apologies.

      I’ve been heads-down with developing cocos3d, and I’ve let this Controls code base get somewhat out of date.

      The download link in the top right above contains a new version that corrects the Retina issue. It has been tested as part of the demo projects in the last several cocos3d distributions.

      …Bill

  15. Surender

    Is there a way that i can use logoSmash from cocos2d in chipmunk within my application.
    and how can i use the animations of cocos2d in UIViewController.

  16. Roddy

    Hi,

    Is the example source code shown above available for download?

    1. Roddy

      Have found the issue where the virtual joystick was disappearing after being touched.

      Joystick.m:setContentSize

      travelLimit = ccpMult(ccpSub(ccpFromSize(self.contentSize), ccpFromSize(thumbNode.scaledSize)), 0.5);

      The content size is subtracted from the scaled content size; where the scale is 1.0 (which is pretty common) the result is 0 and the joystick thumb disappears.

      Had anyone else come across this?

      Have removed the subtraction from the scaled value, but not sure why it is there in the first place?

      Thanks,

  17. ath0

    It should be added to the descriptions that when you’re making a joystick without a background, you must make sure the size of the object is greater than the size of the thumb. This may seem obvious but wasn’t to me, and if the size is wrong, the joystick thumb will simply disappear, and the velocity vector will report NaNs.