How To Mask a Sprite with Cocos2D 1.0

Sometimes in your games you might find it handy to display only a portion of a sprite. One way you can do this is by using a second image called a mask. You set the mask image to be white wherever you want the image to show up – and transparent everywhere else. Then you […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share

Sometimes in your games you might find it handy to display only a portion of a sprite.

One way you can do this is by using a second image called a mask. You set the mask image to be white wherever you want the image to show up – and transparent everywhere else.

Then you can use the technique we’ll cover in the tutorial to combine the mask and the original image so that only the part you want comes through!

You’ll find this handy to accomplish a ton of interesting effects – such as creating a puzzle piece from a sprite, cutting out someone’s head to put it on a funny body, or to create a neat image frame effect – like we’ll be covering in this tutorial!

This tutorial will showing you how to mask a sprite in Cocos2D 1.0 using a handy class that comes with Cocos2D called CCRenderTexture.

This tutorial assumes you have familiarity with Cocos2D. If you are new to Cocos2D, you should check out some of the other Cocos2D tutorials on this site first.

Getting Started

Start up Xcode, go to File\New\New Project, choose iOS\cocos2d\cocos2d, and click Next. Name the new project MaskedCal, click Next, choose a folder to save the project in, and click Create.

Next, download the resources for this project and drag the folder to your Xcode project. Make sure that “Copy items into destination group’s folder (if needed)” is checked, and click Finish.

We’ll start things off with some jazz. Open up AppDelegate.m and make the following changes:

// Add to top of file
#import "SimpleAudioEngine.h"

// At end of applicationDidFinishLaunching, replace last line with the following 2 lines:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"TeaRoots.mp3" loop:YES];
[[CCDirector sharedDirector] runWithScene: [HelloWorldLayer sceneWithLastCalendar:0]]; 

This plays some cool music made by Kevin MacLeod and calls a new initializer we’re about to write.

Next open up HelloWorldLayer.h make the following changes:

// Add new instance variable
int calendarNum;

// Replace the +(CCScene*) scene declaration at the bottom with the following:
+ (CCScene *) sceneWithLastCalendar:(int)lastCalendar;
- (id)initWithLastCalendar:(int)lastCalendar;

In this scene we’re going to display a random calendar image (we have three to choose from). Here we’re storing the calendar number we’re displaying, and we modify the initializers to take the last calendar number shown as a parameter (so we can include some logic to not show the same calendar twice in a row).

Then switch to HelloWorldLayer.m and make the following changes:

// Replace +(CCScene *) scene with the following
+(CCScene *) sceneWithLastCalendar:(int)lastCalendar // new
{
    CCScene *scene = [CCScene node];
    HelloWorldLayer *layer = [[[HelloWorldLayer alloc] 
        initWithLastCalendar:lastCalendar] autorelease]; // new
    [scene addChild: layer];	
    return scene;
}

// Replace init with the following
-(id) initWithLastCalendar:(int)lastCalendar
{
	if( (self=[super init])) {
        
        CGSize winSize = [CCDirector sharedDirector].winSize;
        
        do {
            calendarNum = arc4random() % 3 + 1;
        } while (calendarNum == lastCalendar);
        
        NSString * spriteName = [NSString 
            stringWithFormat:@"Calendar%d.png", calendarNum];
        
        CCSprite * cal = [CCSprite spriteWithFile:spriteName];
        
        // BEGINTEMP
        cal.position = ccp(winSize.width/2, winSize.height/2);        
        [self addChild:cal];
        // ENDTEMP
        
        self.isTouchEnabled = YES;
	}
	return self;
}

// Add new methods
- (void)registerWithTouchDispatcher {
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self 
        priority:0 swallowsTouches:YES];
}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    CCScene *scene = [HelloWorldLayer sceneWithLastCalendar:calendarNum];
    [[CCDirector sharedDirector] replaceScene:
        [CCTransitionJumpZoom transitionWithDuration:1.0 scene:scene]];
    return TRUE;
}

This is just some basic Cocos2D code to display a random calendar image in the center of the screen. It also contains some basic logic so whenever you tap the screen, it makes a new scene with a neat bouncy transition.

Compile and run to try it out, and you should see a random calendar made by my lovely wife displayed each time you tap:

An image that is not masked

Now that we have a decent shell for our app, let’s implement the masking effect!

Masking and OpenGL Blend Modes

If you look at Art\CalendarMask.png in an image editor, you’ll see it looks like this:

A white and transparent mask image

We’re going to use this to mask our calendar image so it looks like a floaty piece of paper rather than a simple square. Wherever the image is transparent, we don’t want the image to show up, but wherever the image is opaque we do want it to show up.

To accomplish this, we’re going to use OpenGL blending.

If you remember back to the How to Create Dynamic Textures with CCRenderTexture tutorial, we discussed OpenGL blending modes, and I pointed out a great online tool that you can use to visualize what various blend modes mean.

To accomplish the effect we want, we’re going to take the following strategy:

  1. We’re going to first draw the mask sprite, with the src color (the mask) set to GL_ONE and the destination color (an empty buffer) to GL_ZERO. So basically we’ll plop down the mask as-is.
  2. We’ll then draw the calendar sprite, with the src color (the calendar) set to GL_DST_ALPHA. This is a fancy way of saying “look at the alpha value of what is currently in the buffer (the mask). Wherever it’s opaque, let the calendar show through, but where it’s transparent, don’t show anything!” The dst color (the mask) is set to GL_ZERO, so the existing mask drawn earlier goes away.

Awesome! You might think that you can just draw these two sprites one after another with Cocos2D and setting the blend modes appropriately and you’d be done – but you’d be wrong.

The problem is the above blending algorithm runs into problems if there’s something underneath the sprite you’re drawing – like a background, or another sprite. This is because it makes the assumption that the only thing in the image buffer after step 1 is the mask – no other image data.

So we need some way to have a “blank slate” when drawing this masked texture. And this is where our friend CCRenderTexture comes to the rescue!

Masking and CCRenderTexture

CCRenderTexture is a class that lets you draw to an offscreen buffer.

It is handy for a lot of reasons – you can use it to take screenshots of your game, efficiently cache user drawing, dynamically create sprite sheets at runtime, or what we’re going to use it for in this case – to help us mask a sprite.

To use CCRenderTexture, you take the following steps:

  1. Create the CCRenderTextuer, specifying the width and height of the texture in pixels.
  2. Call begin to initialize drawing to the CCRenderTexture.
  3. Issue OpenGL commands to draw stuff – but it will go to the CCRenderTexture instead of the screen.
  4. Call end when you are done, and CCRenderTexture will now have a sprite property with a texture you can use (but note it’s flipped upside down). Alternatively, you can add the CCRenderTexture directly as a child if you want.

Don’t freak out about step 3 – since you’re using Cocos2D, you usually don’t have to issue the OpenGL commands manually yourself! If you want to draw a node, all you have to do is call [myNode visit] and it will run all the appropriate OpenGL commands for you!

The only trick is the node needs to be positioned in the render texture’s coordinates. 0,0 is the bottom left of the render texture, so make sure that you position everything inside that space appropriately.

OK you’re probably sick of me blabbing on and are eager to see some code. I aim to please – let’s dive back in!

Contributors

Over 300 content creators. Join our team.