Working with different types of textures
Getting to know how to manage textures should be one of the main priorities for every game developer. Of course, it's possible to build a game while only knowing the basics of texturing, but down the road that can very well lead to performance issues, texture bleeding, and other unwanted results. In this recipe, we're going to take a look at how we can build textures into our games in order to provide efficiency, while reducing the possibility of texture padding issues.
Getting ready
Perform the Know the life cycle recipe given in this chapter, so that we've get a basic AndEngine project set up in our IDE. Additionally, this recipe will require three images in PNG format. The first rectangle will be named rectangle_one.png
, at 30 pixels wide by 40 pixels in height. The second rectangle named rectangle_two.png
, is 40 pixels wide by 30 pixels in height. The final rectangle is named rectangle_three.png
, at 70 pixels wide by 50 pixels in height. Once these rectangle images have been added to the project's assets/gfx/
folder, continue on to the How to do it... section.
How to do it…
There are two main components involved when building a texture in AndEngine. In the following steps, we will be creating what is known as a texture atlas that will store three texture regions out of the three rectangle PNG images mentioned in the Getting ready section.
- This step is optional. We point the
BitmapTextureAtlasTextureRegionFactory
class to the folder in which our graphical images are located. The factory is pointed to theassets/
folder by default. By appendinggfx/
to the default base path of the factory, it will now look inassets/gfx/
for our images:BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
- Next, we will create
BitmapTextureAtlas
. The texture atlas can be thought of as a map which contains many different textures. In this case, our "map" orBitmapTextureAtlas
, will have a size of 120 x 120 pixels:// Create the texture atlas at a size of 120x120 pixels BitmapTextureAtlas mBitmapTextureAtlas = new BitmapTextureAtlas(mEngine.getTextureManager(), 120, 120);
- Once we have
BitmapTextureAtlas
to work with, we can now create ourITextureRegion
objects and place them onto specific locations within theBitmapTextureAtlas
texture. We will use theBitmapTextureAtlasTextureRegionFactory
class, which helps us with binding our PNG images to a specificITextureRegion
object as well as define a position to place theITextureRegion
object within theBitmapTextureAtlas
texture atlas we'd created in the previous step:/* Create rectangle one at position (10, 10) on the mBitmapTextureAtlas */ ITextureRegion mRectangleOneTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBitmapTextureAtlas, this, "rectangle_one.png", 10, 10); /* Create rectangle two at position (50, 10) on the mBitmapTextureAtlas */ ITextureRegion mRectangleTwoTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBitmapTextureAtlas, this, "rectangle_two.png", 50, 10); /* Create rectangle three at position (10, 60) on the mBitmapTextureAtlas */ ITextureRegion mRectangleThreeTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBitmapTextureAtlas, this, "rectangle_three.png", 10, 60);
- The final step is to load our
ITextureRegion
objects into memory. We can do this in one call to theBitmapTextureAtlas
atlas which contains the saidITextureRegion
objects:mBitmapTextureAtlas.load();
How it works…
In AndEngine development, there are two main components we will use in order to create textures for our projects. The first component is known as BitmapTextureAtlas
, which can be thought of as a flat surface with a maximum width and height that can store sub-textures within its width and height boundaries. These sub-textures are called texture regions, or ITextureRegion
objects in AndEngine to be specific. The purpose of the ITextureRegion
object is to act solely as a reference to a specific texture in memory, which is located at position x and y within a BitmapTextureAtlas
atlas. One way to look at these two components is to picture a blank canvas, which will represent a texture atlas, and a handful of stickers, which will represent the texture regions. A canvas would have a maximum size, and within that area we can place the stickers wherever we'd like. With this in mind, we place a handful of stickers on the canvas. We've now got all of our stickers neatly laid out on this canvas and accessible to grab and place wherever we'd like. There is a little bit more to it as well, but that will be covered shortly.
With the basics of BitmapTextureAtlas
and ITextureRegion
objects out of the way, the steps involved in creating our textures should now make more sense. As mentioned in the first step, setting the base path of the BitmapTextureAtlasTextureRegionFactory
class is completely optional. We are simply including this step as it saves us from having to repeat saying which folder our images are in once we move on to creating the ITextureRegion
objects. For example, if we were not to set the base path, we'd have to reference our images as gfx/rectangle_one.png
, gfx/rectangle_two.png
, and so on.
In the second step, we are creating our BitmapTextureAtlas
object. This step is pretty straightforward as we must simply specify the Engine's TextureManager
object which will handle the loading of textures, as well as a width and height for the texture atlas, in that order. Since we're only dealing with three small images in these steps, 120 x 120 pixels will be just fine.
One important thing to keep in mind about texture atlases is to never create excessive texture atlases; as in do not create an atlas that is 256 x 256 for holding a single image which is 32 x 32 pixels for example. The other important point is to avoid creating texture atlases which are larger than 1024 x 1024 pixels. Android devices vary in their maximum texture sizes and while some may be able to store textures up to 2048 x 2048 pixels, a large number of devices have a maximum limit of 1024 x 1024. Exceeding the maximum texture size will either cause a force-closure on startup or simply fail to display proper textures depending on the device. If there is no other option and a large image is absolutely necessary, see Background stitching in Chapter 4, Working with Cameras.
In the third step of this recipe, we are creating our ITextureRegion
objects. In other words, we are applying a specified image to the mBitmapTextureAtlas
object as well as defining where, exactly, that image will be placed on the atlas. Using the BitmapTextureAtlasTextureRegionFactory
class, we can call the createFromAsset(pBitmapTextureAtlas, pContext, pAssetPath, pTextureX, pTextureY)
method, which makes creating the texture region a piece of cake. In the order the parameters are listed from left to right, the pBitmapTextureAtlas
parameter specifies the texture atlas which we'd like the ITextureRegion
object to be stored in. The pContext
parameter allows the class to open the image from the gfx/
folder. The pAssetPath
parameter defines the name of the specific file we're looking for; example, rectangle_one.png
. And the final two parameters, pTextureX
and pTextureY
, define the location on the texture atlas in which to place the ITextureRegion
object. The following image represents what the three ITextureRegion
objects would look like as defined in step three. Note that the positions are consistent between the code and image:
In the previous image, notice that there is a minimum gap of 10 pixels between each of the rectangles and the texture edge. The ITextureRegion
objects are not spaced out like this to make things more understandable, although it helps. They are actually spaced out in order to add what is known as texture atlas source spacing. What this spacing does is that it prevents the possibility of texture overlapping when a texture is applied to a sprite. This overlapping is called texture bleeding. Although creating textures as seen in this recipe does not completely mitigate the chance of texture bleeding, it does reduce the likelihood of this issue when certain texture options are applied to the texture atlas.
See the Applying texture options recipe given in this chapter for more information on texture options. Additionally, the There's more... section in this topic describes another method of creating texture atlases, which completely solves the texture bleeding issue! It is highly recommended.
There's more…
There is an abundance of different approaches we can take when it comes to adding textures into our game. They all have their own benefits and some even have negative aspects involved.
The BuildableBitmapTextureAtlas
object is a great way to implement ITextureRegion
objects into our texture atlases without having to manually define positions. The purpose of the BuildableBitmapTextureAtlas
texture atlas is to automatically place its ITextureRegion
objects onto the atlas by applying them to the most convenient coordinates. This approach to creating textures is the easiest and most efficient method as it can become time-consuming and sometimes even error-prone when building large games with many texture atlases. In addition to BuildableBitmapTextureAtlas
being automated, it also allows for the developer to define transparent padding to the texture atlas sources, removing any occurrence of texture bleeding. This was one of the most prominent visual issues in AndEngine's GLES 1.0 branch as there was no built-in method for supplying padding to the texture atlases.
Using a BuildableBitmapTextureAtlas
atlas differs slightly from the BitmapTextureAtlas
route. See the following code for this recipe's code using a BuildableBitmapTextureAtlas
atlas instead:
/* Create a buildable bitmap texture atlas - same parameters required * as with the original bitmap texture atlas */ BuildableBitmapTextureAtlas mBuildableBitmapTextureAtlas = new BuildableBitmapTextureAtlas(mEngine.getTextureManager(), 120, 120); /* Create the three ITextureRegion objects. Notice that when using * the BuildableBitmapTextureAtlas, we do not need to include the final * two pTextureX and pTextureY parameters. These are handled automatically! */ ITextureRegion mRectangleOneTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBuildableBitmapTextureAtlas, this, "rectangle_one.png"); ITextureRegion mRectangleTwoTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBuildableBitmapTextureAtlas, this, "rectangle_two.png"); ITextureRegion mRectangleThreeTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBuildableBitmapTextureAtlas, this, "rectangle_three.png"); // Buildable bitmap texture atlases require a try/catch statement try { /* Build the mBuildableBitmapTextureAtlas, supplying a BlackPawnTextureAtlasBuilder * as its only parameter. Within the BlackPawnTextureAtlasBuilder's parameters, we * provide 1 pixel in texture atlas source space and 1 pixel for texture atlas source * padding. This will alleviate the chance of texture bleeding. */ mBuildableBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 1, 1)); } catch (TextureAtlasBuilderException e) { e.printStackTrace(); } // Once the atlas has been built, we can now load mBuildableBitmapTextureAtlas.load();
As we can see in this code, there are some minor differences between the BuildableBitmapTextureAtlas
and the BitmapTextureAtlas
atlases. The first main point to note is that when creating our ITextureRegion
objects, we no longer have to specify where the texture region should be placed on the texture atlas. The second minor change when using the BuildableBitmapTextureAtlas
alternative is that we must call the build(pTextureAtlasBuilder)
method on mBuildableBitmapTextureAtlas
before we call the load()
method. Within the build(pTextureAtlasBuilder)
method, we must provide a BlackPawnTextureAtlasBuilder
class, defining three parameters. In this order, the parameters are pTextureAtlasBorderSpacing
, pTextureAtlasSourceSpacing
, and pTextureAtlasSourcePadding
. In the previous code snippet, we will remove the likelihood of texture bleeding in almost all cases. However, in extreme cases, if there is texture bleeding, then simply increase the third parameter, this will help to alleviate any issues.
A tiled texture region is essentially the same object as a normal texture region. The difference between the two is that a tiled texture region allows us to pass a single image file to it and create a sprite sheet out of it. This is done by specifying the number of columns and rows within our sprite sheet. From there, AndEngine will automatically divide the tiled texture region into evenly distributed segments. This will allow us to navigate through each segment within the TiledTextureRegion
object. This is how the tiled texture region will appear to create a sprite with animation:
Let's assume that the previous image is 165 pixels wide and 50 pixels high. Since we have 11 individual columns and a single row, we could create the TiledTextureRegion
object like so:
TiledTextureRegion mTiledTextureRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(mBitmapTextureAtlas, context,"sprite_sheet.png",11,1);
What this code does is it tells AndEngine to divide the sprite_sheet.png
image into 11
individual segments, each 15 pixels wide (since 165 pixels divided by 11 segments equals 15). We can now use this tiled texture region object to instantiate a sprite with animation.
In addition to the more common image types (.bmp
, .jpeg
, and .png
), AndEngine also has built-in support for PVR and ETC1 compressed textures. The main benefit in using compressed textures is the impact it has on reducing the load time and possibly increasing frame rates during gameplay. On that note, there are also disadvantages in using compressed textures. ETC1, for example, doesn't allow for an alpha channel to be used in its textures. Compressed textures may also cause a noticeable loss of quality in your textures. The use of these types of textures should be relevant to the significance of the object being represented by the compressed texture. You most likely wouldn't want to base your entire game's texture format on compressed textures, but for large quantities of subtle images, using compressed textures can add noticeable performance to your game.
See also
- Creating the resource manager in this chapter.
- Applying texture options in this chapter.