AndEngine for Android Game Development Cookbook
上QQ阅读APP看书,第一时间看更新

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.

  1. This step is optional. We point the BitmapTextureAtlasTextureRegionFactory class to the folder in which our graphical images are located. The factory is pointed to the assets/ folder by default. By appending gfx/ to the default base path of the factory, it will now look in assets/gfx/ for our images:
    BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
  2. 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" or BitmapTextureAtlas, 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);
  3. Once we have BitmapTextureAtlas to work with, we can now create our ITextureRegion objects and place them onto specific locations within the BitmapTextureAtlas texture. We will use the BitmapTextureAtlasTextureRegionFactory class, which helps us with binding our PNG images to a specific ITextureRegion object as well as define a position to place the ITextureRegion object within the BitmapTextureAtlas 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);
  4. The final step is to load our ITextureRegion objects into memory. We can do this in one call to the BitmapTextureAtlas atlas which contains the said ITextureRegion 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:

How it works…

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.

BuildableBitmapTextureAtlas

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.

TiledTextureRegion

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:

TiledTextureRegion

Note

A real sprite sheet should not have outlines around each column and row. They are in place in the previous image to display how a sprite sheet is divided up into equal segments.

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.

Compressed textures

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.