- AndEngine for Android Game Development Cookbook
- Jayme Schroeder Brian Broyles
- 1920字
- 2021-08-05 18:19:51
Bringing a scene to life with sprites
Here, we come to the topic which might be considered to be the most necessary aspect to creating any 2D game. Sprites allow us to display 2D images on our scene which can be used to display buttons, characters/avatars, environments and themes, backgrounds, and any other entity in our game which may require representation by means of an image file. In this recipe, we'll be covering the various aspects of AndEngine's Sprite
entities which will give us the information we need to continue to work with Sprite
objects later on in more complex situations.
Getting ready…
Before we dive into the inner-workings of how sprites are created, we need to understand how to create and manage AndEngine's BitmapTextureAtlas
/BuildableBitmapTextureAtlas
objects as well as the ITextureRegion
object. For more information, please refer to the recipes, Working with different types of textures and Applying texture options in Chapter 1, AndEngine Game Structure.
Once these recipes have been covered, create a new empty AndEngine project with the BaseGameActivity
class, provide a PNG formatted image of any size up to 1024 x 1024 pixels in dimension, naming it as sprite.png
and place it in the assets/gfx/
folder of the project, then continue to the How to do it... section of this recipe.
How to do it…
Sprites can be created and applied to our Scene
object in just a few quick steps. We must first set up the necessary texture resources that the sprite will use, we must create the Sprite
object, and then we must attach the Sprite
object to our Scene
object. See the following steps for more detail:
- We will start by creating the texture resources in the
onCreateResources()
method of ourBaseGameActivity
class. Make sure themBitmapTextureAtlas
andmSpriteTextureRegion
objects are global variables, so that they can be reached throughout the various life cycle methods of our activity:BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/"); /* Create the bitmap texture atlas for the sprite's texture region */ BuildableBitmapTextureAtlas mBitmapTextureAtlas = new BuildableBitmapTextureAtlas(mEngine.getTextureManager(), 256, 256, TextureOptions.BILINEAR); /* Create the sprite's texture region via the BitmapTextureAtlasTextureRegionFactory */ mSpriteTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBitmapTextureAtlas, this, "sprite.png"); /* Build the bitmap texture atlas */ try { mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 1, 1)); } catch (TextureAtlasBuilderException e) { e.printStackTrace(); } /* Load the bitmap texture atlas into the device's gpu memory */ mBitmapTextureAtlas.load();
- Next, we will create the
Sprite
object. We can create and attach theSprite
object to theScene
object in either theonCreateScene()
or theonPopulateScene()
methods of our activity. The parameters to supply in its constructor include, in this order, the sprites initial x coordinate, initial y coordinate,ITextureRegion
object, and finally themEngine
object's vertex buffer object manager:final float positionX = WIDTH * 0.5f; final float positionY = HEIGHT * 0.5f; /* Add our marble sprite to the bottom left side of the Scene initially */ Sprite mSprite = new Sprite(positionX, positionY, mSpriteTextureRegion, mEngine.getVertexBufferObjectManager()); The last step is to attach our Sprite to the Scene, as is necessary in order to display any type of Entity on the device's display: /* Attach the marble to the Scene */ mScene.attachChild(mSpriteTextureRegion);
How it works…
As it might appear in the steps in the previous section, setting up the mBitmapTextureAtlas
and mSpriteTextureRegion
objects actually require more work than creating and setting up the mSprite
object specifically. For this reason, it is encouraged to complete the two recipes mentioned in the Getting started... section beforehand.
In the first step, we will create our mBitmapTextureAtlas
and mSpriteTextureRegion
objects, suitable to the needs of our sprite.png
image. Feel free to use any texture options or texture format in this step. It is a very good idea to get to know them well.
Once we've our ITextureRegion
object created and it's ready for use, we can move to step two where we will create the Sprite
object. Creating a sprite is a straightforward task. The first two parameters will be used to define the initial position of the sprite, relative to its center point. For the third parameter, we will pass in the ITextureRegion
object that we created in step one in order to provide the sprite with its appearance as an image on the scene. Finally, we pass in the mEngine.getVertexBufferObjectManager()
method, which is necessary for most entity subtypes.
Once our Sprite
object is created, we must attach it to the Scene
object before it will be displayed on the device, or we can attach it to another Entity
object which is already attached to the Scene
object. See the Understanding AndEngine entities recipe given in this chapter for more information on entity composition, placement, and other various must-know aspects of Entity
objects.
There's more...
No game is complete without some sort of sprite animation. After all, a player can only return to a game so many times before getting bored in a game where the characters slide around the screen without moving their feet, don't swing their weapon when attacking an enemy, or even when a grenade simply disappears rather than causing a nice explosion effect. In this day and age, people want to play games which look and feel nice, and nothing says, "Nice!", like buttery smooth animating sprites, right?
In the Working with different types of textures recipe in Chapter 1, AndEngine Game Structure, we'd covered how to create a TiledTextureRegion
object which allows us to import useable sprite sheets into our game as a texture. Now let's find out how we can use that TiledTextureRegion
object with an AnimatedSprite
object in order to add some animation to our game's sprites. For this demonstration, the code will be working with an image of 300 x 50 pixels in dimension. The sprite sheet can be something as simple as the following figure, just to get an idea of how to create the animation:
The sprite sheet in the previous figure can be used to create a TiledTextureRegion
object with 12 columns and 1 row. Creating the BuildableBitmapTextureAtlas
and TiledTextureRegion
objects for this sprite sheet can be done with the following code. However, before importing this code, be sure to make a global declaration of the texture region—TiledTextureRegion mTiledTextureRegion
—in your test project:
/* Create the texture atlas at the same dimensions as the image (300x50)*/ BuildableBitmapTextureAtlas mBitmapTextureAtlas = new BuildableBitmapTextureAtlas(mEngine.getTextureManager(), 300, 50, TextureOptions.BILINEAR); /* Create the TiledTextureRegion object, passing in the usual parameters, * as well as the number of rows and columns in our sprite sheet for the * final two parameters */ mTiledTextureRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(mBitmapTextureAtlas, this, "gfx/sprite_sheet.png", 12, 1); /* Build and load the mBitmapTextureAtlas object */ try { mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 0, 0)); } catch (TextureAtlasBuilderException e) { e.printStackTrace(); } mBitmapTextureAtlas.load();
Now that we've got the mTiledTextureRegion
sprite sheet to play with in our project, we can create and animate the AnimatedSprite
object. If you are using a sprite sheet with black circles as seen in the previous figure, don't forget to change the color of the Scene
object to a non-black color so we can see the AnimatedSprite
object:
/* Create a new animated sprite in the center of the scene */ AnimatedSprite animatedSprite = new AnimatedSprite(WIDTH * 0.5f, HEIGHT * 0.5f, mTiledTextureRegion, mEngine.getVertexBufferObjectManager()); /* Length to play each frame before moving to the next */ long frameDuration[] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200}; /* We can define the indices of the animation to play between */ int firstTileIndex = 0; int lastTileIndex = mTiledTextureRegion.getTileCount(); /* Allow the animation to continuously loop? */ boolean loopAnimation = true; * Animate the sprite with the data as set defined above */ animatedSprite.animate(frameDuration, firstTileIndex, lastTileIndex, loopAnimation, new IAnimationListener(){ @Override public void onAnimationStarted(AnimatedSprite pAnimatedSprite, int pInitialLoopCount) { /* Fired when the animation first begins to run*/ } @Override public void onAnimationFrameChanged(AnimatedSprite pAnimatedSprite, int pOldFrameIndex, int pNewFrameIndex) { /* Fired every time a new frame is selected to display*/ } @Override public void onAnimationLoopFinished(AnimatedSprite pAnimatedSprite, int pRemainingLoopCount, int pInitialLoopCount) { /* Fired when an animation loop ends (from first to last frame) */ } @Override public void onAnimationFinished(AnimatedSprite pAnimatedSprite) { /* Fired when an animation sequence ends */ } ); mScene.attachChild(animatedSprite);
Creating the AnimatedSprite
object can be done following the steps in this recipe while creating a regular Sprite
object. Once it's created, we are able to set up its animation data, including individual frame duration, first and last tile indices to animate through, and whether or not to loop the animation continuously. Note that the frameDuration
array must be equal to the frame count! Failing to follow this rule will result in an IllegalArgumentException
exception. Once the data has been set up, we can call the animate()
method on the AnimatedSprite
object, supplying all of the data and adding an IAnimationListener
listener if we wish. As the comments in the listener suggest, we gain a large portion of control over the animations with AndEngine's AnimatedSprite
class.
When developing visually appealing games on the mobile platform, it is a likely scenario that we'll want to include some gradients in our images, especially when dealing with 2D graphics. Gradients are great for creating lighting effects, shadows, and many other objects we'd otherwise not be able to apply to a full 2D world. The problem lies in the fact that we're developing for mobile devices, so we unfortunately do not have an unlimited amount of resources at our disposal. Because of this, AndEngine down-samples the surface view color format to RGB_565
by default. Regardless of the texture format we define within our textures, they will always be down-sampled before being displayed on the device. We could alter the color format applied to AndEngine's surface view, but it's likely that the performance-hit will not be worth it when developing larger games with many sprites.
Here, we have two separate screenshots of a simple sprite with a gradient texture; both textures are using the RGBA_8888
texture format and BILINEAR
texture filtering (the highest quality).
The image on the right-hand side is applied to the Scene
object without any modifications, while the image on the left-hand side has OpenGL's dithering capability enabled. The difference between the two otherwise identical images is immediately noticeable. Dithering is a great way for us to combat down-sampling applied by the surface view without us having to rely on maximum color quality formats. In short, by dithering low-levels of randomized noise within our image's colors, it results in a smoother finish which is found in the image to the left.
Enabling dithering is simple to apply to our entities in AndEngine, but as with everything, it's wise to pick and choose which textures apply dithering. The algorithm does add a little bit of extra overhead, where if used too often could result in a larger performance loss than simply reverting our surface view to RGBA_8888
. In the following code, we are enabling dithering in our preDraw()
method and disabling it in our postDraw()
method:
@Override protected void preDraw(GLState pGLState, Camera pCamera) { // Enable dithering pGLState.enableDither(); super.preDraw(pGLState, pCamera); } @Override protected void postDraw(GLState pGLState, Camera pCamera) { // Disable dithering pGLState.disableDither(); super.postDraw(pGLState, pCamera); }
Dithering can be applied to any subtype of AndEngine's Shape
class (Sprites
, Text
, primitives, and so on.).
Note
For more information about OpenGL ES 2.0 and how to work with all of the different functions, visit the link at http://www.khronos.org/opengles/sdk/docs/man/.