Advertisement
  1. Code
  2. Effects
Code

Build an Efficient Flash DecalSheet System

by
Languages:

I came up with the idea of creating what I call Flash DecalSheets from the stickers that come with model airplanes and have been using "Decals" to skin my own Flash applications ever since. A DecalSheet is basically one large image (.JPG, .PNG, or .GIF) that gets cut up into smaller images called 'Decals', which are Bitmaps and can be used anywhere DisplayObjects would usually be used.

This technique is one of the most efficient ways of bringing lots of assets into a Flash application without relying on the Library (if you are using the Flash IDE) or the Embed tag (if you are using the Flex Compiler). Let's take a look at how to make a simple DecalSheet system.

With DecalSheets, you can reduce your application's memory footprint by consolidating smaller images into larger images. Any graphic you would embed in a class or place in an FLA's library can be stored in a single DecalSheet or they can be spread over multiple DecalSheets, depending on your needs. Since DecalSheets can be set up to only load when requested, you can load in application graphics exactly when you need them, cutting down the initial startup and load time. Finally, you can reskin your entire application by simply changing the BitmapData of your DecalSheets at run time.

This diagram illustrates how we take a single DecalSheet image and use coordinates to cut out a new Decal.

In this tutorial we are going to create 2 classes: the DecalSheet and the Decal. You define X, Y, Width, and Height coordinates to cut out graphics from the DecalSheet and it will return Decals. Decals are Bitmaps and can be used anywhere DisplayObjects would normally be used. What makes Decals special is that they retain a reference to the DecalSheet they get cut out from. When you update the BitmapData of the DecalSheet, all of the Decals cut out from that sheet will also update. This allows you to re-skin an entire application at run time by simply loading in new source images.

The following example shows our source DecalSheet on the left, and a SimpleButton on the right using Decals for each button state. When you click on the button, a new image is loaded in and its Bitmap data replaces the original skin in the DecalSheet. All of the Decals in the Simple Button will also update. The re-skinning is almost instantaneous!

At the end of the tutorial you will have the following .SWF:

Decal Sheet Source Images

Before we get started, make sure you have the following two images. These will be our DecalSheet sources which we will use to cut out button states from.


button_skin_a.png

button_skin_b.png

Step 1: Setting up the Doc Class

The first thing we are going to do is create our Main Doc class. I have already set up a simple class that will load in an image and add it to the display list.

When you run this class you should see our button_skin_a.png being displayed on the stage. Now we are ready to start creating our DecalSheet.

Step 2: Creating the DecalSheet Class

The DecalSheet extends the Bitmap Class. I have set up my classes in "com.flashartofwar" packages but you are free to set them up however you want. Create a new class called DecalSheet and paste in the following code:

Step 3: Testing the DecalSheet

Now that we have created our DecalSheet, let's make sure it can display images. We are going to do this by passing in the BitmapData of the .png we loaded earlier. Go back into the Doc Class and replace line 40, where we call addChild, with the following:

We are also going to have to import the DecalSheet class at line 3:

As well as setting up a variable to save our DecalSheet at line 16:

Now, when you compile, you should see the same image but now it is inside of the DecalSheet. Let's talk about how we can cut these graphics out.

Step 4: Storing Decals in the DecalSheet

At this point we have a simple DecalSheet class that extends the Bitmap Class. Our goal is to be able to define areas of the DecalSheet that can be cut out and turned into Decals. Before we can register decals we are going to need a place to store them.

Add the following property on line 8:

And import the Dictonary class on line 5:

As you can see this Dictonary is going to be the place were we can associate a Decal's names with its coordinates.

Step 5: Registering Decals

At its core, a Decal is really just a name and its X, Y, Width, and Height values that we use to cut out BitmapData from the DecalSheet. We are going to register this information with the following function on line 16.

Now you can associate a Decal's cutout coordinates to a name and a rectangle.

You will also need to import the Rectangle class at line 5:

Step 6: DecalSheet sample method

We are going to add the single most important function of the DeaclSheet, the sample() method. This function is what we will use to cut out BitmapData from the DecalSheet in order to create our decals. Let's put the following function on line 22 of the DecalSheet Class.

Also you will need to import the Matrix Class on line 5:

There is a lot going on in this function so let's go line by line through the process.

Here, we use the passed in Decal name to look up the registered Rectangle from the decalRectangles Dictonary.

Next we are going to create a Matrix to offset where we sample the BitmapData from.

Here we use the Rectangle's X and Y position to create the appropriate sample offset.

Now we have to create a new BitmapData to store our cut-out.

As you can see we use the Rectangle's width and height as the new dimensions, set the transparent parameter to true, and give the new BitmapData a background color of 0xffffff. By setting transparent to true and supplying a background color we will be able to correctly sample transparent .png images like the "button_skin" examples we are loading in.

Finally we need to draw the DecalSheets BitmapData into the new BitmapData Class instance, and apply the Matrix.

Now we have our composited bitmapData and we simply return the new BitmapData instance.

Step 7: Testing the sample method

Before we go any further we are going to want to do a few simple things to test our new DecalSheet sample method. Let's go back into our Doc Class and add the following function at line 49 after the onImageLoad method:

Here you can see we register each of the button states we are going to need later when we create our SimpleButton. You will need to import the Rectangle class on line 11:

... as well as adding the call for registerDecals on line 46, inside of the onImageLoad function after we add the DecalSheet to the stage.

Now we are going to create one last function at the bottom of our class, around line 54:

This function will be our main staging area for the rest of the demo. Right now we are creating a new Bitmap from the DecalSheet's "up" registeredDecal coordinates, offseting its x position and adding it to the stage.

We will call this function after the registerDecals call we added to the onImageLoad around line 46:

Now if we do a compile, we should see our DecalSheet image on the left, and our sampled Bitmap from the DecalSheet on the right. You can test out all of the Decals by changing "up" for "down" or "over". Now we have enough to start our Decal class.

Step 8: Creating the Decal Class

Just like the DecalSheet, the Decal will also extend the Bitmap Class. The Decal however will have a very specialized purpose and will rely on the DecalSheet to supply it's BitmapData instead of having it passed into the constructor. Create a new Class called Decal and paste in the following code:

So what is going on? As you can see we are changing the constructor's arguments from the original BitmapClass's. Our Decal is going to need to know its name (we use this to request BitmapData from the DecalSheet through the sample method) and we need to know the src DecalSheet the Decal was cut out from.

Going through the construction process, we pass in null to the super's BitmapData property along with any passed in values for pixelMapping and smoothing. Next we save a reference of the DecalSheet src in the deaclSheetSrc property. We then save the passed in name value in the inherited name property from the BitmapData. We use "this" to distinguish the difference between the passed in parameter and the name parameter of the Class's instance. Finally we call the refresh method.

The Decal's refresh method performs a simple task. It requests new BitmapData from its parent DecalSheet and sets it. This creates the display of the Decal. Braking out the logic to request BitmapData from the parent DecalSheet will play an important role later on when we begin to change the BitampData of the DecalSheet.

Step 9: Returning Decals from the DecalSheet

Before we can test that Decals works, we will want to add the ability to request Decals by name from the DecalSheet and have it return a Decal instance. We can do this by adding the following function in the DecalSheet after the registerDecal method around line 21:

Now we can request Decals from the DecalSheet by simply passing in the name of any registered Decals. You will notice this short-hand conditional. Basically, the first item is what we are testing. In this case we want to know if the supplied name has been registered with the decalRectangles Dictonary. The ? denotes what happens if it exists. Here we create a new Decal, give the same name that was passed into the getDecal function and supply a reference of the DecalSheet instance (this) to the Decal. The : denotes what happens if the supplied name was not found on the deacalRectangles Dictonary. We simply return null. Let's test this to make sure everything works.

Step 10: Testing the DecalSheet and Decal

We are now ready to test our Decal. To do this we will move back over to the Doc Class and replace decalSheetDemo method with the following code:

We will also need to import the decal class at line 3:

If you compile the class you should see the Decal on the right of the DecalSheet instance it was cut out from. So, what is the big deal? We had the same thing 4 steps ago with less code. Well, let me explain why this is a powerful way to bring assets into your Flash app.

Imagine you have a photo gallery. What if you had many sites all using the same photo gallery but you needed to brand each photo gallery based on the individual site it was being hosted on. Depending on how you create your photo gallery you may decide to create a .SWF with each button as an item in the library with a linkage ID. Or you can load each individual image one at a time at run time. For a long time I used both ways but always found it limiting to wait for one large .SWF to load up or many smaller images to load in until I came up with the DecalSheet system.

Now, I simply make a DeaclSheet image, define the coordinates for each button and I only have to manage one image and some cut out data. I usually put the cut out coordinates in an .XML file and now I can hand the image off to a designer who may know nothing about Flash but can create a new theme easily from a .PSD template. I know this sounds like a one-off example, but I use this system in every Flash site I build. We haven't even touched on the coolest feature yet!

What happens if you have to re-skin an application on the fly? You would have to reload all of the images and create new class instances or build logic into your components to be able to get the new assets to rebuild themselves. Re-skinning a Flash app built with Decals is as simple as changing the BitmapData of the DeaclSheet. Let me show you how.

Step 11: Changing DecalSheet BitmapData

We are going to need a way to tell all of the Decals cut out from a DecalSheet that the BitmapData has been changed and they need to be re-sampled. We can do this easily by overriding the set bitmapData method of the DecalSheet. Add the following method below the DecalSheet constructor on line 17:

... along with an import statement for Event at line 5:

Now that a new event is dispatched whenever the BitmapData of the DecalSheet is changed, we will need to listen for this in the Decal Class.

Step 12: Listening for DecalSheet Events

Now that the DecalSheet dispatches a change event when its BitmapData is updated, we can have the Decal listen for these events and resample its own BitmapData from the DecalSheet. Let's add the following 3 methods in the Decal class under the refresh function at line 27:

We also have to import the IEventDispatcher and Event classes on line 4:

Finally we will need to apply the listener to the Decal's parent DecalSheet by adding the following code at the end of the constructor at line 23 of the constructor:

Before we move on, I'd like to explain why I break up adding and removing event listeners into separate functions. Whenever I create classes I try to think about how I will extend off of them and also how I can break up logic into the smallest possible pieces. These 3 functions represent a core feature of the Decal and are probably the most important ones we would ever want to modify when extending this class. I also try to use Interfaces whenever possible. I explain this in more detail later on. As you can see we can easily add and remove the Change Event listeners and we call refresh when the Decal instance hears the appropriate event. In the next step we will build our SimpleButton and explore this new functionality.

Step 13: Creating a SimpleButton

Let's go back to the Doc Class and create a SimpleButton using all of the Decals we registered in the DecalSheet. Once again we are going to replace the decalSheetDemo function with the following code:

We also need to import SimpleButton on line 8:

So now we are setting up each of the Decals, creating a new SimpleButton instances, and passing in the Decals to the constructor. Since the SimpeButton uses DisplayObjects for each state, and our Decals extends the Bitmap Class, we can substitute our Decals anywhere DisplayObjects are used. Compile the Doc Class and check out the button. You will see that up, over and down Decals are displayed when the button changes state.

Step 14: Updating the DecalSheet's BitmapData

Now we are going to load-in our second button skin "button_skin_b.png" and replace the DecalSheet's BitmapData. Since the decals are listening for the Change Event from the DecalSheet, we will be able to reskin the SimpleButton without changing a single property on it.

This diagram illustrates how by changing the BitmapData of the DecalSheet we can broadcast an event to all children Decals to resample.

Let's get started by adding in the following event listener to the button on line 72 of the Doc Class after we add the button to the display list:

Once that is in place let's add the following three methods below decalSheetDemo function:

Finally we will need to import the MouseEvent on line 13:

What we have done here is added a Click Event listener to the SimpleButton. When we hear that click we start to load-in the new skin. Once the Skin is loaded, we type the loader's content as a Bitmap and pass its BitmapData into the DecalSheet's bitmapData setter. When you run this now and click the button you will instantly see the SimpleButton's skin and DecalSheet get updated to the new image. The change-over is instantaneous!

Now you have seen how we create a DecalSheet, register Decals, get Decals and skin a SimpleButton. We also go to reskin the button at run-time by simply loading in a new graphic. That about covers the power of using Decals to skin your Flash app. The following steps simply clean up the code we have written and add some extra functionality to help flesh-out the DecalSheet system.

Step 15: Delete Decal

We can't just leave our DecalSheet without the ability to delete decals we have registered, so let's add in a deleteDecal method after the registerDecal method:

Step 16: Get Registered Decal Names

It would probably be helpful to get a list of all the registered Decal names from a DecalSheet so let's add in Array to store just the Decal names. We will need to add the following property after decalRectangles around line 13:

And replace the registerDecal and deleteDecal methods with the following functions:

We can test this by running the following in the Doc Class:

Step 17: Detach a Decal from the DecalSheet

The connection between the Decal and its parent DecalSheet is incredibly powerful but sometimes we want Decals to be a little more independent. That is why we are going to add a detach method to the Decal Class after the onChange method:

Once the detach method is called, we remove the Event listeners as well as null out the reference to the DecalSheet src. This completely disconnects any relation to the parent DecalSheet.

Step 18: Creating a DecalSheet Interface

One of the fundamental concepts of building OO (Object Oriented) code and design patterns is to "Program to an Interface; not an implementation". Unfortunately, to fully explain this concept is out of the scope of this tutorial but that still doesn't mean we can't try to instill some best practices. If you have never used Interfaces before they are very simple. All an interface does is define a set of Public functions a class must contain. So, in our DecalSheet system we are going to create an IDecalSheet interface and type our Decal to it. This will keep our decals loosely coupled to the DecalSheet and allow the most amount of flexibility when extending our system.

To get started we will need to create a new Interface in the same package as our DecalSheet and Decal Classes. Here is the structure of the Interface:

So in our interface we are defining the most commonly used public functions that make up our DecalSheet. Also, take note that even our Interface can extend other interfaces. We are going to extend IBitmapDrawable and IEventDispatcher. This will allow our DecalSheet to do the same tasks as the Bitmap class and we will be able to dispatch and listen to events from it.

Now we need to tell the DecalSheet to implement this Interface. Go into the DecalSheet Class and replace the class definition around line 10 with the following:

If your Interface is in the same package as your DecalSheet you do not have to worry about importing the IDecalSheet interface.

Next we have to implement the interface in our Decal Class. Whereever we use the type DecalSheet, we will want to replace it with IDecalSheet:

Around line 9:

Around line 11:

Around line 32:

Around line 37:

Now our Decal is completely typed to the DecalSheet's Interface instead of the actual Class. We have also enforced that any Class that wants to use the Decal Class must implement all of the same public functions as the DeccalSheet.

Step 19: Clear Decals from the DecalSheet

The last feature we will add is the ability to clear a DecalSheet of Decals and disconnect any instantiated Decal that are linked to the DecalSheet. To get started let's add the following method to the DecalSheet:

Now when we call the clear method on the DecalSheet we dispatch a Deactivate Event and clear the Dictonary and Array. Now we need to add an Event listener to the Decal. In the Decal Class replace the addListeners and removeListeners function with the following:

We will also need to add the following method after the onChange function:

We can test that the all the Decals have been disconnected by calling clear method on the DecalSheet and then trying to change the DecalSheet's BitmapData. You will see that the Decals no longer update themselves.

Step 20: Extending the DecalSheet System

There are a lot of possibilities for extending the DecalSheet system. One interesting spinoff is to make TextField DecalSheet. You can easily create a DecalSheet Class that implements the IDecalSheet Interface but instead of using BitmapData would instead take a TextField and break it down into Decals. By using TextLineMetrics, Decals can be created by going line by line down a TextField or Character by Character. This is a great way to move text around the screen and avoid the distortion you sometime find when moving animating Dynamic TextFields.

PaperVision is also another excellent place to use DecalSheets. Imagine being able to update a 3D model on the fly! By setting Decals as the textures you can update the DecalSheet's BitmapData to change the image of your 3D models.

The End!

This was a simplified version of the DecalSheet system found in Flash Camoflauge, a graphics framework that I wrote. I have used this technique in my own projects for over a year now and don't think I can live without it. I hope you enjoy it as much as I do!

Download tutorial ActionScript file


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.