A vital tool in any game developer's repertoire is the ability to swap color palettes. From The Legend of Zelda on NES to Halo on the Xbox, palette swapping is a simple yet effective visual cue that can stretch a limited amount of art.
In the following example you learn how to palette swap using layers. We are using an animated baseball player for this example.
Please refer to the project RecipeCollection01
for full working code of this recipe.
For this recipe you will need an image manipulation program. I recommend the free and easy to use GIMP.
The first thing we will do is draw the sprite and the colorable areas:
Draw your texture with all dynamically colorable areas left blank. In your image editing program your texture should look something like the following:
Create a new layer and color a specific area white. In this example we are coloring his uniform (legs and shirt) white:
Hide the other layer and save that white-only layer as a separate texture.
Repeat this for any other separately colored sections.
Once we have our textures we can write some code:
@implementation Ch1_PaletteSwapping -(CCLayer*) runRecipe { //Create a nice looking background CCSprite *bg = [CCSprite spriteWithFile:@"baseball_bg_02.png"]; [bg setPosition:ccp(240,160)]; bg.opacity = 100; [self addChild:bg z:0 tag:0]; /*** Animate 4 different fielders with different color combinations ***/ //Set color arrays ccColor3B colors1[] = { ccc3(255,217,161), ccc3(225,225,225), ccc3(0,0,150), ccc3(255,255,255) }; ccColor3B colors2[] = { ccc3(140,100,46), ccc3(150,150,150), ccc3(255,0,0), ccc3(255,255,255) }; ccColor3B colors3[] = { ccc3(255,217,161), ccc3(115,170,115), ccc3(115,170,115), ccc3(255,255,255) }; ccColor3B colors4[] = { ccc3(140,100,46), ccc3(50,50,50), ccc3(255,255,0), ccc3(255,255,255) }; //Animate fielders with colors [self animateFielderWithColors:colors1 withPosition:ccp(150,70)]; [self animateFielderWithColors:colors2 withPosition:ccp(150,200)]; [self animateFielderWithColors:colors3 withPosition:ccp(300,200)]; [self animateFielderWithColors:colors4 withPosition:ccp(300,70)]; return self; } -(void) animateFielderWithColors:(ccColor3B[])colors withPosition:(CGPoint)pos { //The names of our layers NSString *layers[] = { @"skin", @"uniform", @"trim", @"black_lines" }; //Number of layers int numLayers = 4; for(int i=0; i<numLayers; i+=1){ NSString *layerName = layers[i]; ccColor3B color = colors[i]; //We need each plist, the first frame name and finally a name for the animation NSString *plistName = [NSString stringWithFormat:@"fielder_run_%@.plist", layerName]; NSString *firstFrameName = [NSString stringWithFormat:@"fielder_run_%@_01.png", layerName]; NSString *animationName = [NSString stringWithFormat:@"fielder_run_%@", layerName]; //Add plist frames to the SpriteFrameCache [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:plistName]; //Get the first sprite frame CCSpriteFrame *firstFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:firstFrameName]; //Create our sprite CCSprite *sprite = [CCSprite spriteWithSpriteFrame:firstFrame]; //Set color and position sprite.position = pos; sprite.color = color; //Create the animation and add frames CCAnimation *animation = [[CCAnimation alloc] initWithName:animationName delay:0.15f]; for(int i=1; i<=8; i+=1){ CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"fielder_run_%@_0%i.png",layerName,i]]; [animation addFrame:frame]; } //Run the repeating animation [sprite runAction:[CCRepeatForever actionWithAction: [CCAnimate actionWithAnimation:animation]]]; //Finally, add the sprite [self addChild:sprite]; } } @end
By drawing the swappable layers under the main layer (the black outline) we cover up any imprecision in the coloring. This technique is slightly more difficult for art that doesn't use a thick black outline like the drawings shown in the preceding section.
Keeping your iOS app below a certain size on the disk is always a good idea. This technique is fairly easy on your disk space as the swappable textures take up only a small amount of space due to easy PNG compression of simplistic textures.
Unfortunately the size of a texture in memory is determined by its pixel size. So, if you are palette swapping large animated textures you might run into memory consumption issues. Memory consumption for a palette swapped texture equals the normal memory size times the number of palettes to swap.
When animating a palette swapped texture the CPU time used by the animation routine will also be multiplied by the number of swappable layers. This is usually fairly inconsequential as animation takes up very little CPU time as it is.