How To Create a HUD Layer with Cocos2D

Bogdan Vladu
Learn How to Create a HUD with Cocos2D!

Learn How to Create a HUD with Cocos2D!

A common question for beginners to Cocos2D is how to add a second layer to your scene to use as a Heads-Up Display, or HUD.

You might want to use the HUD to contain extra controls (like a joystick or buttons) or you might want to use it to display status information (like lives or score).

In this tutorial, we’re going to take a Cocos2D game that has been made with a single scene and single layer, and add a second layer to use as a HUD.

This tutorial is for beginners to Cocos2D who have gone through the How To Make a Simple Game with Cocos2D tutorial, or have equivalent knowledge.

Heads up – tutorial incoming!

Getting Started

Start by downloading the sample game that we will be using for this tutorial.

If you’re curious, this game was made in this and that tutorial, but I don’t recommend those tutorials for beginners.

Open the project in Xcode and run it. You’ll see this is a simple game where you tap to move, or double tap to jump. Your goal is to avoid the lasers, and make it to the end!

Example game used as starting point for this tutorial

If you get hit by a laser as you play the game, you’ll notice that you take a hit – however there is nothing on the screen to show you your lives.

What’s worse – when you win or lose nothing happens except for a sound effect – there’s no way to retry!

Never fear – HUD to the rescue!

Why You Need a HUD

There may be some of you out there that are thinking, “HUD?! I don’t need no stinking HUD! I’ll just add the labels directly to the layer!”

So before we go any further, let’s give that a try and see what happens.

Open up ActionLayer.mm and add the following code right after the +(id)scene method:

- (void)restartTapped:(id)sender {
 
    // Reload the current scene
    CCScene *scene = [ActionLayer scene];
    [[CCDirector sharedDirector] replaceScene:[CCTransitionZoomFlipX transitionWithDuration:0.5 scene:scene]];
 
}
 
- (void)showRestartMenu:(BOOL)won {
 
    CGSize winSize = [CCDirector sharedDirector].winSize;
 
    NSString *message;
    if (won) {
        message = @"You win!";
    } else {
        message = @"You lose!";
    }
 
    CCLabelBMFont *label;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        label = [CCLabelBMFont labelWithString:message fntFile:@"Arial-hd.fnt"];
    } else {
        label = [CCLabelBMFont labelWithString:message fntFile:@"Arial.fnt"];
    }
    label.scale = 0.1;
    label.position = ccp(winSize.width/2, winSize.height * 0.6);
    [self addChild:label];
 
    CCLabelBMFont *restartLabel;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        restartLabel = [CCLabelBMFont labelWithString:@"Restart" fntFile:@"Arial-hd.fnt"];    
    } else {
        restartLabel = [CCLabelBMFont labelWithString:@"Restart" fntFile:@"Arial.fnt"];    
    }
 
    CCMenuItemLabel *restartItem = [CCMenuItemLabel itemWithLabel:restartLabel target:self selector:@selector(restartTapped:)];
    restartItem.scale = 0.1;
    restartItem.position = ccp(winSize.width/2, winSize.height * 0.4);
 
    CCMenu *menu = [CCMenu menuWithItems:restartItem, nil];
    menu.position = CGPointZero;
    [self addChild:menu z:10];
 
    [restartItem runAction:[CCScaleTo actionWithDuration:0.5 scale:1.0]];
    [label runAction:[CCScaleTo actionWithDuration:0.5 scale:1.0]];
 
}

This is some basic code to display a label to the screen that says “You Win” or “You Lose”, and a button underneath that says “Restart”. When the restart button is tapped, it creates a new instance of the ActionLayer switches to it.

This code is not relevant to this tutorial so I’m not going to go over it in more detail here. If you’re unsure how this works, check out our Learning Cocos2D Book or some of the other tutorials on this site.

Next, make the following modifications to call this new method:

// At bottom of loseGame
[self showRestartMenu:NO];
 
// At bottom of winGame
[self showRestartMenu:YES];

Compile and run, and see if it works. Move over so the level scrolls a bit, and let yourself die by getting shot. You should see something like the following (or maybe no menu at all!)

Menu not displayed correctly due to not using HUD

See how the menu is offscreen?

This is happening because this game follows the player by moving the layer to keep the player centered – however the label and menu gets scrolled along with it because it’s a child of the layer!

What we really want is to have a separate layer so that we can freely move this layer around without having to worry about elements that should always stay visible on the screen, like this menu.

Creating the HUD

Let’s create a new CCLayer for this HUD and hook things up so that the ActionLayer can communicate with the HUD layer.

Go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter NSObject for Subclass of, click Next, name the new class HUDLayer.mm (note the .mm extension, that is intentional), and click Save.

Open HUDLayer.h and replace it with the following:

#import "cocos2d.h"
 
@interface HUDLayer : CCLayer {
}
 
- (void)showRestartMenu:(BOOL)won;
 
@end

This is just a basic subclass of CCLayer, and we predeclare the showRestartMenu.

Next, switch to HUDLayer.mm, and move the restartTapped and showRestartMenu methods you added to ActionLayer.mm earlier into this file. Also add an #import to the top of the file:

#import "ActionLayer.h"

Now let’s hook this up to the ActionLayer. Open up ActionLayer.h and make the following changes:

// Add to top of file
#import "HUDLayer.h"
 
// Add inside @interface
HUDLayer * _hud;
 
// Add after @interface
- (id)initWithHUD:(HUDLayer *)hud;

We’re importing the new layer here, and creating an instance variable so we can keep a reference to it. We’re also modifying our initializer to take the HUDLayer as a parameter.

Next switch to ActionLayer.mm and make the folllowing changes:

// Replace scene method with the following
+ (id)scene {
    CCScene *scene = [CCScene node];
 
    HUDLayer *hud = [HUDLayer node];
    [scene addChild:hud z:1];
 
    ActionLayer *layer = [[[ActionLayer alloc] initWithHUD:hud] autorelease];
    [scene addChild:layer];
 
    return scene;
}
 
// Replace beginning of init with the following
- (id)initWithHUD:(HUDLayer *)hud
{
    if ((self = [super init])) {
        _hud = hud;
        // Rest of method...
 
// In loseGame, replace call to showRestartMenu with the following
[_hud showRestartMenu:NO];
 
// In winGame, replace call to showRestartMenu with the following
[_hud showRestartMenu:YES];

Here we modify the static method that creates the scene to create two layers – the HUDLayer and the ActionLayer – and add them as children. It passes the HUDLayer as a parameter to the ActionLayer so the ActionLayer can easily access it and call methods on it.

Note that it adds the HUDLayer with a z-order of 1, so it appears on top of the ActoinLAyer.

The init method now takes the HUDLayer as a parameter, and stores a reference in an instance variable so it can be accessed later on. And now we can call the showRestartMenu on the HUDLayer!

Compile and run, and try it out… now the menu should be properly centered, even if you scroll!

Menu properly centered on HUD

Adding a Lives Label

Now that we have our HUD in place, let’s add one more feature – displaying the remaining lives!

Open up HUDLayer.h and make the following changes:

// Add inside @interface
CCLabelBMFont * _statusLabel;
 
// Add after @interface
- (void)setStatusString:(NSString *)string;

Then switch to HUDLayer.mm and replace init with the following:

- (id)init {
 
    if ((self = [super init])) {
 
        CGSize winSize = [CCDirector sharedDirector].winSize;
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
            _statusLabel = [CCLabelBMFont labelWithString:@"" fntFile:@"Arial-hd.fnt"];
        } else {
            _statusLabel = [CCLabelBMFont labelWithString:@"" fntFile:@"Arial.fnt"];
        }
        _statusLabel.position = ccp(winSize.width* 0.85, winSize.height * 0.9);
        [self addChild:_statusLabel];        
    }
    return self;
}
 
- (void)setStatusString:(NSString *)string {
    _statusLabel.string = string;
}

This just creates a label in the upper right, and creates a method that can be used to set the display string.

Let’s use it! Switch over to ActionLayer.mm and replace updateLives with the following:

- (void)updateLives {
    [_hud setStatusString:[NSString stringWithFormat:@"Lives: %d", _lives]];
}

Simple eh – we just call the new method we wrote on the HUDLayer! Note it’s good practice to have the HUD layer not really know anything about the state of the game – it just displays what the game tells it to.

Compile and run, and now you have your lives displayed!

Lives displayed in label on HUD

Where To Go From Here?

Here is the example project with all of the code from the above tutorial.

At this point, you know how to create extra layers to serve as a HUD (or for other purposes) and easily send messages to them from your main layer! You can add anything you want to the HUD just like your main layer.

If you have any questions or comments, please join the forum discussion below!

User Comments

32 Comments

[ 1 , 2 , 3 ]
  • it's me, you know wrote:I'm trying to do something similar as the previous poster, putting controls in the hud layer. However somehow the control buttons don't work - the touches aren't caught. I'm using CCControlButton from https://github.com/YannickL/CCControlExtension. In the game layer (HelloWorldLayer) I catch touches anywhere on screen to make my in-game character jump.

    I figure it's an issue with the CCControlExtension. I've got this working with CCMenu etc now. But would really like to get CCControlButton working too... anyone know how??
    I posted this issue to its github: https://github.com/YannickL/CCControlEx ... /issues/26
    it's me, you know
  • i'm a bit late to the party, but did you solve your problem with the touches not being caught? i'm trying to do something similar but with UILongPressGestureRecognizer, any help's appreciated!
    banarne
[ 1 , 2 , 3 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Dani Arnaout
  • Tim Mitra

... 53 total!

Update Team

... 14 total!

Editorial Team

  • Alexis Gallagher

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!