Advertisement
  1. Game Development
  2. Game Design
Gamedevelopment

Harness the Power of Palette Mapping to Customize Game Characters

by
Difficulty:AdvancedLength:LongLanguages:

In this tutorial I'll show you how to use AS3's PaletteMap feature to create a character customization interface for your next game.


Why Palette Mapping?

In the past, whenever I wanted to attempt dynamic asset coloring Google searches inevitably led me to the ColorTransform class. ColorTransform works fine for modifying solid colors, but if you're trying to color a complex piece of art the result is typically not what you want.


With palette mapping we can analyze each pixel of an asset and change the color appropriately to retain the highlights and shading of the original image. It's not immediately clear from the documentation for paletteMap how to make use of it, so that's what I'm going to cover here.

Let's get started.


Step 1: Create a Character Image

The first step is to create your game character if you don't already have one you'd like to use. I'm going to be using a bitmap image for mine, but I'll show you how you can use a Flash vector image later in the tutorial.

My character is a happy walking cupcake:



Step 2: Layout Animation Frames (Optional)

You can skip this step if you just want to work with a still image.

My character has a four frame walk cycle animation. To make it easier to work with, I'm going to put all four frames of animation into one image. It's important that all the frames of the animation are aligned vertically and spaced evenly horizontally.


Keep track of the pixel dimensions of the single animation frame, you'll need it later. My frames are each 76 x 82 pixels.


Step 3: Separate the Character Into Colorable Layers

In Photoshop (or other image editor) separate the parts of your character that you'd like to colorize from the rest of the character. In this tutorial I'm going to be coloring the character body and hair, but you can separate as many different elements as you want.

I've decided to color my character's legs the same color as his hair, so I can put the legs and hair together on the same layer.



Step 4: Convert Layers to Grayscale and Save

Convert the hair and body layers into grayscale. In Photoshop you can do this quickly by selecting the layer and hitting Command-Shift-U (the shortcut for Image > Adjustments > Desaturate).


Save each element as a separate PNG with 24 bit transparency.



Step 5: Set Up Your Flash Document

Now that your character is ready it's time to move to Flash.

Create a new Flash document 600 x 400 at 30fps.


Set the document class to 'Main'.



Step 6: Import the Character Images into Flash

Import your hair and body images into the Library. Set the hair image to export as 'HairBMD', and the body image to export as 'BodyBMD'. These are the class names that we'll use to reference the images in our code.



Step 7: Create the Main Document Class

Create a new ActionScript file named 'Main.as' and save it in the same folder as your Flash file. This will be our document class.

Paste in this code to create the empty shell of the class:


Step 8: Add Variables to the Main Document Class

Add these variable declarations to the top of your new class:

Here we're adding two constants, HAIR and BODY to hold indexes to map to our hair and body images. This makes it easy to add other colorable elements later (SHIRT, HAT, etc). For now we'll just stick with these two.

Next we create an array called _originalImages and fill it with bitmaps for our hair image and our body image. Notice that when you instantiate your image from the library it is a BitmapData object, so we have to send it as an argument to a new Bitmap object to create a bitmap.

We also create a second array called _coloredImages. This will hold the bitmaps that we colorize.

(I'm using underscores in my variable names to indicate they are protected variables—not public.)

In the next block we create three related variables. _compositeSprite will be a container where we layer all our colorable images to create the composite image. _compositeBMD is a BitmapData object into which we will draw the data from the Sprite. Lastly, we'll use _compositeBitmap to display the composite image on the screen.


Step 9: Composite the Character Images on the Screen

Let's get the character up on the screen.

Add this function to the Main class:

Here we instantiate both the _compositeSprite and the _compositeBMD objects. We use the width and height of our body image for the BitmapData object. We also instantiate it with the transparency flag set to true.

Next, we instantiate the Bitmap image with the _compositeBMD BitmapData, and then add it to the stage. Note that we can create the Bitmap and add it to the stage before adding data to the BitmapData. As we edit the BitmapData, the Bitmap will automatically update. Pretty cool, right?

For now we'll add the hair and body images from the _originalImages array to the _compositeSprite

All that's left then is to draw the _compositeSprite into the _compositeBMD. This will update the _compositeBitmap which is already on the stage.


Step 10: Test

Add a call to the setUp function in the constructor of the Main class:

Test your movie and you should see your composited character images show up on the screen. If not, you should stop here and figure out what you missed before going any further. Things are about to get a little crazy...



Step 11: Create the Colorizer Class

Create a new ActionScript file called 'Colorizer.as'. Paste this code inside to create the base of our Colorizer class:

This class will contain the code that does all the palette mapping to colorize our images. We're putting it in a separate file so it will be easy to reuse on other projects. Save this file in the same folder as your Flash file.


Step 12: Add the getColoredBitmap() Function

Add this big function to your Colorizer class:

This is going to be our workhorse function. We'll go through each part of in the next few steps.


Step 13: The getColoredBitmap() Function Explained

Let's start with the first few lines of the function:

Line 11: We declare this function as public static. public allows other classes to access this function, and static means that we can use this function without having to instantiate the class first. So we can call this function directly by using Colorizer.getColoredBitmap() instead of having to create a new instance of Colorizer and calling the function on that instance.

Also on line 11: Our function accepts two parameters. The first, bmp, will be the Bitmap image that the function will colorize. The second, color, will be the color value used to color the bitmap.

Lines 13–15: We need 3 arrays to use paletteMap—one for each color channel. Here we're just declaring and instantiating each one.

On to the next section:

This is where things start to get a little tricky. We need to separate our color value (the one passed in as the color parameter) into the values for each color channel that make up the color. We do that by using bit shifting and bit masking to extract the value of each channel from the color value.

Each variable will hold a number from 0–255 to represent the value for that specific color channel in our color (eg: 50 Red, 200 Green, 255 Blue would be a light blue color).

I'm going to try to explain how bit shifting works in the next step. If you already understand this (or don't care), you can skip to step 15 to continue walking through the getColoredBitmap() function.


Step 14: Understand Bit Shifting and Masking to Extract Color Channels

Color values are stored as unsigned integers.

Typically we specify colors in hex:

RR GG BB

Where each group of two digits represent the color value for an individual channel from 00 (0) to FF (255).

But for this explanation it's helpful to think of the colors as their binary representations, like this:

RRRRRRRR GGGGGGGG BBBBBBBB

In binary, each color channel is represented by a series of 8 bits (0 or 1).

To access a single channel from this long string of bits we need to SHIFT them to the right and/or MASK out the unwanted bits. We do this by using the right shift operator (>>) and the bitwise AND operator (&).

Getting the red channel is easy. We just shift the entire sequence to the right by 16 bits (>> 16). We're left with the 8 red bits as the 16 blue and green bits get shifted into never never land.

To get green we shift by 8 bits (>> 8) to get rid of blue, but then we're left with RRRRRRRR GGGGGGGG. How do we get rid of those red bits?

The bitwise AND operator is used to combine two binary numbers. Each bit of both numbers are compared. If BOTH bits are 1, then the corresponding bit of the result will be 1. If EITHER bit is 0, then the result bit will be 0.

color: 11101110 11101110

mask: 00000000 11111111 (0xFF in hex)

result of &: 00000000 11011101

So by using AND with the mask 0xFF, we can zero out those bits from the red channel and we're just left with the green channel.

To get the blue channel, we don't need to do any shifting, we just have to mask out the red and green bits using AND again (& 0xFF).

That's a very quick overview of a complex topic. If you want a more complete explanation check out this page by Wojciech Sierakowski.


Step 15: Back to the getColoredBitmap() Function

Now we're going to start filling our three color channel arrays (redArr[], greenArr[] and blueArr[]) with values.

PaletteMap works by analyzing the color of each pixel in a BitmapData object. It breaks that color value into separate color channels (like we did in the previous step). It then uses each channel value as an index to look up a new value in the corresponding array we provide.

So if the red value of a pixel is 50, paletteMap will look up the value we put at redArr[50] and assign that to the red channel of the new color for the pixel. It will then do the same for the green and blue channels.

To make use of this for colorizing we need to map the values we put into the color channel arrays to the brightness values in our grayscale image. This means we'll start with 0 in all channels (black). At index 128 (halfway to 256), we want each channel to map exactly to our color. And at index 255 we want all the values to be 255 (white). We want a smooth transition between those three for all the values in between. This way when paletteMap looks up the value for a neutral gray (128 R, 128 G, 128 B) it will find the values for our color. If it looks up a value for a darker gray (50 R, 50 G, 50 B) it will find the values for a darker version of our color. And if it looks up a value for a lighter gray (200 R, 200 G, 200 B) it will find the values for a lighter version of our color. This way we'll still retain all the highlights and shading of the original image.


To achieve this, we're going to fill our color channel arrays in two separate passes. The first pass will calculate values from black (0) to our target color. The second pass will calculate values from our target color to white (255).

Here's the code for the first pass (still in the getColoredBitmap function):

Line 26: We're starting a loop for the variable i from 0 to 128. In each iteration of the loop we'll be adding a single value to each of our three color channel arrays.

Line 30: To calculate the value to be pushed into our red channel array we take the redVal value that we calculated above and divide it by 128 and then multiply by i. As we go through our loop this will give us values from 0 to redVal. To put this value into the red channel, we have to bit shift it back to the left 16 bits (<< 16). Remember we shifted to the right by 16 to extract the red value above, so we just do the reverse here.

Lines 31 & 32: We do the same thing for greenVal and blueVal. Notice we only have to shift the green value back 8 bits, and we don't need to shift the blue value at all.

Now for the second pass:

This section is doing pretty much the same thing as the previous section, but now we're adding values ranging from our target color to white (255).

Line 42:For the redVal we find the difference between 255 (the maximum value) and redVal. Then we divide by 128 and multiply by j just as we did above. Now we add the result to redVal because we're moving from that value to white in this section. Lastly we shift it back into the red channel (<< 16).

Lines 43 & 44:We do the same for greenVal and blueVal.

Just a bit more to go:

Lines 47 & 48: This creates a new Bitmap called coloredBMP and assigns the Bitmap that got passed in to the function to it. This seems unnecessary at this point, but we'll be adding some functionality later that will make this make sense.

Line 50: Create a variable to hold the BitmapData object of coloredBMP.

Line 54: Here we're finally applying the paletteMap. Since we're calling paletteMap on bmd, this is the BitmapData instance that will receive the data from the paletteMap. We're also using it as the first argument, which is where the paletteMap will get its source data from. The second argument is a Rectangle that defines how much of the source image we want to use. We want the entire thing, so we create a Rectangle of the same width and height as our bitmap, bmp. The third parameter is a Point object that specifies an offset for the destination of the data. We want ours to match up exactly to the source image, so we pass in a new point at 0, 0. Finally, for the last three arguments we pass in our three color channel arrays, redArr[], greenArr[] and blueArr[].

Line 56: Lastly, we return the newly colored Bitmap from the function.


Step 16: Add the colorImage() Function

Ok, now we need to actually use the Colorizer class. Go back to the Main class and add this function:

This function accepts an integer as an ID to specify which image to color. This will be our BODY or HAIR ID. The second parameter is the color value we want to color our images.

The first line calls our fancy new getColoredBitmap() function in the Colorizer class. Remember this function returns the colored bitmap, so we're storing that in the _coloredImages array at the index specified by imageId.

Next we add the colored bitmap to the display list of the _compositeSprite.

Finally, we draw the entire contents of _compositeSprite to _compositeBMD. Remember, the Bitmap associated with _compositeBMD will automatically update when we draw the new image to the BitmapData.


Step 17: Call the colorImage() Function

Go back and take a look at the setUp() function (in the Main class):

Notice that in setUp() we're adding images to _compositeSprite and drawing them to the composite BitmapData. We don't need to do this anymore since we're doing the same thing in the colorImage function.

Replace those three lines with two calls to colorImage():

These two calls will set the default colors for our character. You can change that color value to whatever you want the default colors for your character to be.


Step 18: Test the Colorizer

Test your movie now and you should see a colorized version of your character on screen.



Step 19: Create a User Interface for Character Customization

Now design a user interface for your character customizer in your Flash file.

Mine will be very simple. It has an area for body color choices, an area for hair color choices, an area to display the character composite image, and an area to display the animated character.


In your Flash file all these areas will be empty, but you should make a note of the pixel positions where you'd like the elements to appear so we can add them to the screen in the proper positions.


Step 20: Add Interface Element Locations

Add these constants to the Main class to specify the locations of the user interface elements we'll be adding to the screen programatically:

Here we're just storing the X and Y locations (as Point objects) to specify the top left corner of our interface elements. We also create an integer constant to store the amount of space between each of the color buttons.

We're putting these in constants for two reasons. First, it makes it easier to read the code that uses these numbers. If we just put x = 30 somewhere it's easy to forget where that number comes from and what it stands for. x = BODY_BUTTONS_LOC.x is much less ambiguous. Also, having them all in one place like this makes them easier to edit, rather than having to search around to find the individual numbers scattered throughout our code.


Step 21: Position the Composite Bitmap

Now that we have the positions for all our elements let's go back and use them to position the composite bitmap.

Add these two lines to the setUp function:


Step 22: Design the ColorButton MovieClip

Now we'll build the button elements for our user interface.

Create a new MovieClip symbol named ColorButton. In the Symbol Properties set the Class to 'ColorButton'.


Draw a rounded rectangle for the background of the button. I made mine 160 x 25, with a gradient gray fill.

Create a dynamic text field called 't_label' to display the color name. Leave a little room on the left side of the button for a color swatch. And don't forget to embed the font.



Step 23: Create the ColorButton Class

Now we'll create the class file for our new button.

Create a new ActionScript file named 'ColorButton.as' and save it in the same folder as your Flash file.

Add this code to create the empty shell for the class:

This class has two public variables. One holds the color value associated with the button, and one holds the ID of the image it will color (the HAIR, or BODY integer ID).

mouseChildren = false ensures that mouse events will be dispatched from the button and not its children (like the text label or the color swath). buttonMode = true makes Flash display the hand cursor when mousing over the button.

We're using a MovieClip instead of a Button symbol so we can embed a dynamic text field.


Step 24: Add the setColor() Function

Add this function to the ColorButton class:

This function accepts the color value, the color name (for the label), and the image ID. It stores the color and the image ID in the public variables for those values.

Next, it sets the text label to the color name. I want my labels to be all caps, so I'm using the toUpperCase() function to convert them to upper case.

Lastly, it calls a function called createColorSwatch() with the color value. This function will draw a little swatch of color on the button. We'll create that function next.


Step 25: Add the createColorSwatch() Function

Add this function to the ColorButton class:

This function creates a Sprite, called swatch, draws a small rectangle on it in the color we specified, and adds the Sprite to the button's display list. You can get fancier with this function if you want to display your color swatch differently. I'm going to keep mine simple.


Step 26: Add the List of Colors

Back in the Main class, add four arrays to hold the color values and color names for hair and body colors:

The _bodyColors[] and _hairColors[] arrays are the lists of color values we'll use for coloring. The _bodyColorLabels[] and _hairColorLabels[] arrays are the names for the colors. These are the labels that will appear on the color buttons.

Make sure to keep them in order, so you get the right name matched up to each color.


Step 27: Create the Button List

Add this loop to the end of the setUp function in the Main class:

Here we loop through the colors in the _bodyColors[] array.

For each color value we create a new ColorButton and send it the color value, color name, and image ID using the setColor() function.

We set the button positions using the position constants we set up earlier, and add a mouse event listener to listen for clicks to the button.

Lastly we add the new button to the display list.

Now add a second loop for the hair colors:

This loop is almost exactly the same, but this time we're creating the buttons for the hair colors.


Step 28: Add the Button Event Handler

In the last step we added mouse event listeners to each button. Now we need to add the function that will handle those events. Add this function to the Main class:

This event handler function only has two lines. First we just get the reference to the button that was clicked using the target property of the mouse event. Then we call the colorImage() function using the buttons's public properties for imageId and color.

Now whenever a color button is clicked, the corresponding image will be colored with the selected color.


Step 29: Test

Test it out and see how it works.


Notice anything funny? The coloring works fine the first time, but after that the images don't seem to colorize properly. Take a look at our Colorizer class and see if you can figure out what's wrong.

Currently, the getColoredBitmap() function colors the image we provide and then returns that colored image. This works fine until we want to color the image again. Since the original, grayscale image was colored, when we try to use the paletteMap() function on it again, the colors aren't mapped properly and we end up with unpredictable results.

To solve this, we need a method to colorize a copy of the image and keep the original untouched.

We'll create a function that does just that in the next step.


Step 30: Add the getBitmapCopy Function

Add this function to the Colorizer class:

This function takes any DisplayObject, copies it and returns a Bitmap copy.

If the supplied DisplayObject is a Bitmap, then we just use the clone() method to create a copy of the BitmapData.

If it's any other kind of DisplayObject, then we create a new BitmapData object and use the draw method to create a Bitmap copy of the DisplayObject.

Lastly, we return the new Bitmap.

This function is a little more complex than it needs to be. We could cut out all the DisplayObject stuff and just have it accept a Bitmap and return a cloned copy. But remember at the beginning when I said you'd be able to use this Colorizer on vector assets from Flash as well as Bitmaps? Well, this is how you'd do that. You can use this getBitmapCopy() function to create a Bitmap copy of your Flash asset and then use that Bitmap in the same way we'v been using our Photoshop-created bitmaps.


Step 31: Implement the getBitmapCopy() Function

Add a new parameter to the getColoredBitmap() function called copyBMP:

This is a boolean parameter that we can use to specify if we want to create a copy of the bitmap or color the original image.

Further down in the getColoredBitmap() function change this line:

To this:

Now, if the copyBMP flag is set to true, we'll use getBitmapCopy() to create a copy of the bitmap to color. Otherwise, we just color the original as we were doing before. This keeps things flexible in case we wanted to colorize the original image for whatever reason.

The last thing we need to do to implement this is to change our call to getColoredBitmap() to use the flag. We're making the call in the colorImage() function in the Main class. Add the true flag, like this:


Step 32: Test Again

So now that problem is fixed and the images should colorize properly. But there's another problem...


You might notice that after you change the colors several times, the edges of the the character start to get chunky and pixelated. This is because instead of replacing the colored image each time, we're actually just plopping a new one on top of everything. We have to do two things to fix this problem.


Step 33: Update the colorImage Function

Add these lines the the beginning of the colorImage function in the Main class:

That will check to see if the image we're coloring is already a part of the _compositeSprite. If so, we'll remove it before adding the new one.

That takes care of part of the problem, but we also need to make sure we're clearing the composite BitmapData before we draw the composite Sprite into it again. Add this line right before the draw() call at the end of the function:

This just fills the composite BitmapData with transparent pixels—clearing it before we draw into it again.

Your colorImage function should now look like this:


Step 34: Test Again

Everything should finally work now. If it does, you can give yourself a pat on the back. If not, you've got a bit of debugging to do.

If you're only interested in coloring a static image then you can stop here. After the character customization process is complete, you can use the _compositeBitmap that was created as the character graphic in your game. This is nice because you have the custom character saved in a single bitmap. You don't have to run the coloring code to recreate the character every time you want to display it on screen.

In the rest of the tutorial I'm going to show you how to animate the character frames. If that interests you then please read on...


Step 35: Set up Variables for Animation

We need to add some variables to the Main class so we can animate our character. Add these constants:

You'll need to edit the constants to match the size of your character. Remember the frame size is based on the size of one of the frames on your character sheet, not the entire image size.

Now add these variables:

The _animatedBitmap is the image that we'll use to display the character animation. We'll use the _animationTimer to periodically swap out the bitmap data in _animatedBMD with the pixels for a new frame of the animation. We'll cover this in the next step.


Step 36: Add the updateAnimation() Function

Add this function to the Main class:

This function will get called from our _animationTimer, so it needs to accept a timer event.

In the first line we define a new Rectangle object. This Rectangle specifies the area of pixels that we want to copy from the composite bitmap. We want to copy the image for a single frame, so we use the FRAME_W and FRAME_H constants to define the width and height of the Rectangle. Multiplying the frame width by the _currentFrame variable gives us the X position of the Rectangle. The Y position is always 0, since all our frames are in a single row.

On the next line we copy the pixels from the _compositeBitmap BitmapData into the _animatedBMD BitmapData. We use the clipRect to define the area to copy, and a Point object to specify the location in the _animatedBMD where we want to paste them. In this case, we always want to paste them at 0,0.

Lastly, we increment the _currentFrame counter, and then mod it by the number of frames in our animation. This way the frame number will loop back around to 0 after it reaches the last frame.

Here's how this will work: every time the Timer fires it will copy a single frame's worth of pixels from the _compositeBitmap and paste them into the _animatedBMD. That updates the character image and creates the animation.



Step 37: Add the setUpAnimation() Function

Add this function to the Main class:

This function prepares everything for animation. First we create the BitmapData for the animation (using the frame size) and use that to create a new Bitmap. We position the Bitmap using our ANIMATED_BMP_LOC constant and add it to the display list.

Finally we create a new Timer to do the animation. You can adjust the speed at which the Timer runs. For my animation, 150 milliseconds feels about right.


Step 38: Add the startAnimation() Function

Add this function to the Main class:

Only two lines here. First add the event listener to the animation timer. Every time the timer ticks it will call our _updateAnimation function. Then we just tell the timer to start.


Step 39: Rock and Roll!

Now our animation system is ready to go. All that's left is to add the setup and start calls for the animation functions. Add them to the constructor in the Main class:

You should be ready to rock now. Give it a test and see if it works.


Step 40: Taking it Further

Now that you have your character customization system working, what changes will you need to make to get it into your own game?

The most obvious thing I think you might want to do is add more colorable parts. My character was very simple, but you could create a human character and let your users choose colors for skin, eyes, hair, shirt, pants, facial hair, etc. You could create a racing game where you can choose the color of your car, its racing stripes, wheels, and pit crew.

Typically you would have the character customizer at the beginning of the game. You can then save the colored Bitmap image and use it as the game character. If you have an animated character then you'll need to separate the animation code from the character customization part of your game (so you can animate the character during the game as well).

Also, I keep a copy of the Colorizer class in a folder of shared classes (not associated with any specific project). This makes it really handy to use in any project. I just import the class and call the static getColoredBitmap() function.


Conclusion

So there you have it. You now have a complete system for customizing and animating a character for your game using only one set of images. If you can utilize this properly in your game it can save you a ton of time by not having to create a bunch of art assets in different colors.

I hope you enjoyed the tutorial and I hope you find it useful.

As always please leave a comment and let me know what you think.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.