AndEngine doesn’t provide a lot of drawing features. For most games, the artwork is created as a series of bitmaps, and drawing complicated graphics at runtime is rarely necessary. AndEngine gives us a way to draw both Lines and Rectangles.
Line
In addition to the variables that Line inherits from Entity, it has a second position (the end- point of the line) and a line width. These parameters can be seen in the Line constructors:
Line(final float pX1, final float pY1, final float pX2, final float pY2) Line(final float pX1, final float pY1, final float pX2, final float pY2,
final float pLineWidth)
Figure 5.1 Entity simplified class diagram Entity Layer Line Scene Shape áAsbtractà IShape áInterfaceà SplashScene CameraScene RectangularShape áAsbtractà BaseRectangle áAsbtractà BaseSprite áAsbtractà Sprite TiledSprite AnimatedSprite Rectangle
ptg999
Sprites 89
If you don’t pass in a pLineWidth, the value defaults to 1 pixel. The first set of
coordinates is taken as the “position” of the Line, and the last set of coordinates is the “other end” of the Line. Attributes such as color and transparency are handled by the Entity superclass.
Rectangle
AndEngine also provides a Rectangle drawing primitive that has the following constructors:
Rectangle(final float pX, final float pY, final float pWidth, final float pHeight) Rectangle(final float pX, fina float pY, final float pWidth, final float pHeight,
final RectangleVertexBuffer pRectangleVertexBuffer)
Here, the X and Y values mark the upper-left corner of the Rectangle; the width and height are self-explanatory. Once again, color and transparency are handled by the superclass, and the Rectangle is drawn with a fill that corresponds to them (to draw an outline rectangle, you draw four lines).
The optional RectangleVertexBuffer parameter can be used to improve the drawing speed if a bunch of Rectangles are needed, or it can be used to distort the displayed rectangle. These capabilities are an OpenGL topic that is beyond the scope of this book, but if you’re interested, Google “OpenGL vertex buffer”; you should find plenty of online reference material.
Sprites
Looking toward the bottom of Figure 5.1, we see AndEngine provides three different kinds of Sprites:
n
n A Sprite uses a single texture, extracted from a TextureRegion. n
n A TiledSprite chooses a texture, taken from a regular array of textures in a
TiledTextureRegion.
n
n An AnimatedSprite is a subclass of TiledSprite, whose texture changes at a
regular frame rate to show an animation.
Textures
Before we get into Sprites and all their variations, we need to develop some background about the way AndEngine treats textures. If you haven’t done much graphics programming, it’s helpful to understand that textures are just bitmaps that are “painted” onto objects like Sprites as they are displayed. AndEngine stores collections of textures in memory as instances of the Texture class. There is a class Texture, and a singleton TextureManager manages all the Textures for your game.
ptg999
Chapter 5 Drawing and Sprites 90
Each Texture can have multiple TextureRegions inside it, which identify bitmaps in the Texture. AndEngine has two fundamental types of TextureRegions:
n
n A TextureRegion usually contains one image, with a unique height and width.
An example TextureRegion image is shown in Figure 5.2.
n
n A TiledTextureRegion usually contains more than one image, where each image
has the same height and width. The images are organized as an array of tiles that can be referenced by their position in the array. An example TiledTexture- Region image is shown in Figure 5.3.
Texture
A few constructors are supplied to create a new Texture: Texture(final int pWidth, final int pHeight)
Texture(final int pWidth, final int pHeight, final ITextureStateListener pTextureStateListener)
Texture(final int pWidth, final int pHeight, final TextureOptions pTextureOptions)
Texture(final int pWidth, final int pHeight, final TextureOptions
pTextureOptions, final ITextureStateListener pTextureStateListener)
All of them create a blank canvas with the given dimensions into which you can load textures for your Sprites. The dimensions of the Texture—that is, pWidth and pHeight—must be powers of 2 (32, 64, 128, …), and the dimensions must be large
enough to hold all of the textures you intend to load into the Texture. If these values
Figure 5.3 TiledTextureRegion image
ptg999
Sprites 91
are not powers of 2, AndEngine will throw an IllegalArgumentException. If you try to load a bitmap that doesn’t fit in your Texture, you will also get an exception. Either type of exception will force your game to close, if it is not caught, with a log message being entered in LogCat.
The optional TextureStateListener parameter, in the second and fourth
constructors, is triggered when a texture is loaded into the Texture, or unloaded, or when an error occurs during loading. We won’t use that feature in our work here, but if you’re interested, there is an example for using it, ImageFormatsExample.java,
included in the AndEnginesExample source.
The optional TextureOptions parameter controls the way OpenGL displays the
textures. We will often use the default, but the following options are also available:
n n TextureOptions.NEAREST n n TextureOptions.BILINEAR n n TextureOptions.REPEATING n n TextureOptions.REPEATING_BILINEAR n
n TextureOptions.NEAREST_PREMULTIPLYALPHA (the default) n n TextureOptions.BILINEAR_PREMULTIPLYALPHA n n TextureOptions.REPEATING_PREMULTIPLYALPHA n n TextureOptions.REPEATING_BILINEAR_PREMULTIPLYALPHA
Each of these options sets the OpenGL texture filters according to patterns defined in TextureOptions.java, which is part of the AndEngine sources. Describing the effect of each option is an OpenGL topic beyond the scope of this book, but the differences are real, especially as you scale or rotate textures. If you are concerned about the details of texture rendering, I encourage you to read up on the topic. In particular, an excellent discussion of texture rendering can be found at http://www .opengl.org/wiki/Texture.
For most of the examples in the book we will use BILINEAR_PREMULTIPLYALPHA, which asks OpenGL to use the following properties:
n
n GL_LINEAR: use linear interpolation in each (x, y) direction to determine the
color of pixels when magnifying or minimizing a texture.
n
n GL_CLAMP_TO_EDGE: textures don’t wrap around, so clamp-normalize texture
coordinates to [0.1].
n
n GL_MODULATE: combine textures by multiplying them. n
n Use premultiplied alpha blending, which usually creates more realistic
combinations of textures when they overlap.
TextureRegionFactory
So far, all we’ve done is to create a blank storage area for textures. To load images into the Texture, we use TextureRegionFactory. We’ve been using this approach all
ptg999
Chapter 5 Drawing and Sprites 92
along to create textures for the Sprites in V3, but now we want to look at all of the capabilities that are possible. TextureRegionFactory knows how to create Texture- Regions and TiledTextureRegions from three types of sources:
n
n Assets: bitmap files stored under the assets folder in your game project. This is
the method we’ve been using so far.
n
n Resources: drawable files stored as resources under the res folder of your game
project.
n
n TextureSources: a more generic name for drawable resources and assets. The
factory methods for resources and assets are implemented with these methods, and they might also be useful if you’re reusing a TextureSource.
A method is available for each of these sources, and for each type of texture region (tiled or not). Another set is available for BuildableTextures, which we’ll discuss in the next section. Here are the basic methods for the previously mentioned sources:
TextureRegion createFromAsset(final Texture pTexture, final Context pContext, final String pAssetPath, final int pTexturePositionX, final int pTexturePositionY) TiledTextureRegion createTiledFromAsset(final Texture pTexture,
final Context pContext, final String pAssetPath, final int pTexturePositionX, final int pTexturePositionY, final int pTileColumns, final int pTileRows)
TextureRegion createFromResource(final Texture pTexture, final Context pContext, final int pDrawableResourceID, final int pTexturePositionX, final int pTexturePositionY) TiledTextureRegion createTiledFromResource(final Texture pTexture,
final Context pContext, final int pDrawableResourceID, final int pTexturePositionX, final int pTexturePositionY, final int pTileColumns, final int pTileRows)
TextureRegion createFromSource(final Texture pTexture,
final ITextureSource pTextureSource, final int pTexturePositionX, final int pTexturePositionY)
TiledTextureRegion createTiledFromSource(final Texture pTexture, final ITextureSource pTextureSource, final int pTexturePositionX, final int pTexturePositionY, final int pTileColumns, final int pTileRows) The parameters for these methods are defined as follows:
n
n TexturepTexture: the Texture you’re loading into. n
n ContextpContext: the Activity Context for the current Activity. n
n StringpAssetPath: the filename for the asset, referenced to the assetPath,
which is the assets folder by default. (The setAssetPath() method is discussed later in this chapter.) AndEngine currently knows how to load PNG, JPG, and BMP images.
ptg999
Sprites 93
n
n pDrawableResourceID: the integer assigned to the resource in R.java, usually
referenced as R.drawable.<resourcename>.
n
n ITextureSourcepTextureSource: the TextureSource to be loaded. n
n intpTexturePositionX,intpTexturePositionY: the location on the
Texture where the image should be loaded. It identifies the position for the upper-left corner of the image, and the Texture must be large enough to hold the image at that position. Images should not overlap, unless you’re doing some- thing strange.
n
n intpTileColumns,intpTileRows: for TiledTextureRegions, the number
of columns and rows in the tiled image.
TextureRegionFactory also contains a useful method for setting the base asset path to something beneath the assets folder. This method is helpful if you keep your
images in separate subfolders, and you want to avoid typing the whole path name for every image:
voidsetAssetBasePath(finalStringpAssetBasePath)
The parameter is the partial path name, and it must end in “/”. If it doesn’t, or if it’s zero length, an exception will be thrown.
Example: Creating a TextureRegion from an Asset
We used setAssetBasePath() and TextureRegionFactory.createFromAsset()
in Chapter 4 when we set up the TextureRegions for Level 1 of the V3 game. Listing 5.1 is an excerpt from that code showing the normal pattern for creating a Sprite from an image file in the assets folder.
Listing 5.1 Level1Activity.java Excerpt Using TextureRegionFactory.createFromAsset()
... @Override publicvoidonLoadResources(){ /*LoadTextures.*/ TextureRegionFactory.setAssetBasePath("gfx/Level1/"); mLevel1BackTexture=newTexture(512,512, TextureOptions.BILINEAR_PREMULTIPLYALPHA); mLevel1BackTextureRegion= TextureRegionFactory.createFromAsset( this.mLevel1BackTexture, this,"Level1Bk.png",0,0); mEngine.getTextureManager().loadTexture( this.mLevel1BackTexture); ... }
ptg999
Chapter 5 Drawing and Sprites 94
We start out by setting the asset base path to "gfx/Level1/" because that’s the way
we’ve chosen to structure the assets subfolder. All of the graphics are located in sub-
folder gfx, and the graphics for each level will placed in a separate subfolder under gfx.
Remember to include the final “/”; if you omit it, AndEngine will throw an exception. We then create a Texture big enough to hold the background for Level 1. The background is a PNG file, 480 × 320 pixels in size. The next highest power of 2 is 512 in each case, so we create the Texture as a 512 × 512 pixel space. I’ve chosen a TextureOption of BILINEAR_PREMULTIPLYALPHA in imitation of the examples.
We then create the TextureRegion from the PNG file stored under assets and
add it to the Texture at position (0, 0). If we were loading other TextureRegions into this Texture, we would load them at positions that did not conf lict with the back- ground Texture.
Finally, we ask the singleton TextureManager to load the Texture into its cache of Textures. Now our new Texture will be available for our game.
Example: Creating a TextureRegion from a Resource
There is one big advantage to creating TextureRegions from resources, rather than assets. As you may know, Android defines three screen resolutions (hdpi, mdpi, and ldpi) and manages graphic resources for them, so as to manage the problem of working with different screen resolutions. When you place images in res/drawable-hdpi, res/drawable-mdpi, and res/drawable-ldpi and subsequently reference them in an Android application (as R.drawable.<filename>), Android will use the image that most closely matches the screen geometry of the device your application is running on. That capability can be a big help in managing screen geometries.
Listing 5.2 shows an example of using resources instead of assets for the obstacle textures in Level1Activity.java.
Listing 5.2 Level1Activity.java Excerpt Modified to Use createFromResource()
packagecom.pearson.lagp.v3; ... @Override publicvoidonLoadResources(){ /*LoadTextures.*/ mLevel1BackTexture=newTexture(512,512, TextureOptions.BILINEAR_PREMULTIPLYALPHA); mLevel1BackTextureRegion= TextureRegionFactory.createFromResource( this.mLevel1BackTexture,this,R.drawable.level1bk, 0,0); mEngine.getTextureManager().loadTexture( this.mLevel1BackTexture); ... }
ptg999
Sprites 95
To make this process work, you first have to populate the res/drawable-xdpi
folders with images suitable for those resolutions. The filenames have to be all lowercase (“level1bk.png” in this case), and the method takes a reference to the resource—that is, “R.drawable” plus the filename with no extension. The images need to be PNGs or JPGs (Android can deal with GIFs, but AndEngine cannot—PNG should be your first choice).
Example: Creating a TextureRegion from a Vector (SVG) Source
An extension to AndEngine allows you to render SVG vector graphics files at runtime. This is a very useful addition to the basic game engine, particularly for games that may be run on high-definition (HD) screens. When the vector file is rendered at runtime, the rendering can be optimized for the available screen resolution without any com- promise in the graphics’ appearance. Typically, if you render SVGs at development time, you create at least three bitmap images—one for low-resolution screens, one for mid-resolution screens, and one for high-resolution screens.
To use the extension, you need to load its .jar (Java archive) file from the following website:
http://code.google.com/p/andenginesvgtextureregionextension
Place the .jar file in the lib folder in your AndEngine project, right-click on the .jar filename, and choose Build Path > Add to Build Path from the pop-up menu. Once
the addition is available, you can load textures from SVG graphics as shown in Listing 5.3.
Listing 5.3 Level1Activity.java Excerpt Using SVG Graphics
... @Override publicvoidonLoadResources(){ /*LoadTextures.*/ mLevel1BackTexture=newTexture(512,512, TextureOptions.BILINEAR_PREMULTIPLYALPHA); mLevel1BackTextureRegion= mLevel1BackTextureSource=newSVGAssetTextureSource( this,"svg/hatchet40.svg",1.0f); TextureRegionFactory.createFromSource( this.mLevel1BackTexture, this.mLevel1BackTextureSource,0,0); mEngine.getTextureManager().loadTexture( this.mLevel1BackTexture); ... BuildableTexture
The preceding set of methods works well for building TextureRegions and loading them into Textures. If you’ve actually used these methods, however, you probably noticed one drawback: You have to tell TextureRegionFactory specifically where you want each image loaded into the Texture.
ptg999
Chapter 5 Drawing and Sprites 96
Suppose you have three images to load:
n n One.png: 50 × 121 pixels n n Two.png: 78 × 324 pixels n n Three.png: 233 × 43 pixels
You have to figure out how you want to pack these images into a Texture, and then carefully compute coordinates and dimensions to come up with the parameters for the TextureRegionFactory calls. That’s kind of a pain—so AndEngine provides a set of methods that do the math for us. We used a BuildableTexture and these methods in Chapter 4, within Level1Activity.java. Listing 5.4 is a part of that file that
illustrates how these methods are used.
Listing 5.4 Level1Activity.java Excerpt Using BuildableTexture
packagecom.pearson.lagp.v3; ... @Override publicvoidonLoadResources(){ /*LoadTextures.*/ ... mObstacleBoxTexture=newBuildableTexture(512,256, TextureOptions.BILINEAR_PREMULTIPLYALPHA); mBoxTextureRegion= TextureRegionFactory.createFromAsset(mObstacleBoxTexture, this,"Obstaclebox.png"); mBulletTextureRegion= TextureRegionFactory.createFromAsset(mObstacleBoxTexture, this,"Bullet.png"); mCrossTextureRegion= TextureRegionFactory.createFromAsset(mObstacleBoxTexture, this,"Cross.png"); mHatchetTextureRegion= TextureRegionFactory.createFromAsset(mObstacleBoxTexture, this,"Hatchet.png"); try{ mObstacleBoxTexture.build( newBlackPawnTextureBuilder(2)); }catch(finalTextureSourcePackingExceptione){ Log.d(tag, "Spriteswon’tfitinmObstacleBoxTexture"); } this.mEngine.getTextureManager().loadTexture(this .mObstacleBoxTexture); } ... }
ptg999
Sprites 97
The constructors for BuildableTexture are exact analogs of the constructors listed earlier for Texture, and they have the same constraints (i.e., must have power of 2 dimensions, must be big enough to hold all the textures). The createFrom … methods are also exact analogs to their previously mentioned counterparts, with the Texture parameter replaced with a BuildableTexture, and no coordinate parameters.
The main difference becomes apparent after you’ve created all of the TextureRegions. Before you make use of the BuildableTexture, you must build the TextureRegions into it by calling the BuildableTexture’s build method, using a builder class such as Black- PawnTextureBuilder (the only builder provided right now, but that could change). If the build succeeds, the TextureRegions are all packed into the BuildableTexture and you can access them by name. If the build does not succeed (e.g., the textures don’t all fit in the allocated space), you can catch the exception, as shown in the excerpt in Listing 5.3.
Another Way to Build Textures
Perhaps you don’t want to build your Textures at runtime. If so, you might want to use the popular tool called Zwoptex for building Textures (or sprite sheets, as they are sometimes called). You can’t import a Zwoptex sprite sheet directly into AndEngine, but you can use it to arrange images and identify what those images’ coordinates should be. Zwoptex comes in two f lavors:
n
n An older, web-based (Adobe Flash) version is still freely available but is no lon-
ger supported (http://zwoptexapp.com/flashversion).
n
n The supported, more fully featured version, which runs on only Mac OS X
(10.6 or later), is available for a small fee (http://zwoptexapp.com/buy). Either version can be used to automatically combine images into a single, larger sprite sheet, or Texture. The result is a sprite sheet image file and an XML list of coordinates. AndEngine does not yet know how to import the image and list (perhaps someone will have added that capability by the time you read these words), but you can look at the XML list and easily find the coordinates needed for createFromAsset()
without having to do the calculations yourself.
As an example, I’ve used Zwoptex Flash to combine the images for the Obstacle Box in Level 1 of V3. Figure 5.4 shows the resulting sprite sheet. Listing 5.5 provides the relevant part of the resulting XML file.
ptg999
Chapter 5 Drawing and Sprites 98
Listing 5.5 Level1Activity.java Excerpt Using BuildableTexture
<?xmlversion="1.0"encoding="UTF-8"?> <!DOCTYPEplistPUBLIC"-//Apple//DTDPLIST1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plistversion="1.0"> <dict> <key>texture</key> <dict> <key>width</key> <integer>256</integer> <key>height</key> <integer>128</integer> </dict> <key>frames</key> <dict> <key>Bullet.png</key> <dict> <key>x</key> <integer>45</integer> <key>y</key> <integer>1</integer> <key>width</key> <integer>11</integer> <key>height</key> <integer>33</integer> <key>offsetX</key> <real>0</real> <key>offsetY</key> <real>0</real> <key>originalWidth</key> <integer>11</integer> <key>originalHeight</key> <integer>33</integer> </dict> <key>Cross.png</key> <dict> <key>x</key> <integer>1</integer> <key>y</key> <integer>1</integer> ... </dict> </dict> </dict> </plist>
ptg999
Sprites 99
For each image, the pX and pY coordinates are given as the values of the keys
x and y, respectively. You can easily use Zwoptex to illustrate what the assembled Texture will look like, to identify the coordinates for each image, and to ensure the