An Example

Let's add this texture atlas to our previous example to make it look more beautiful. Bob will be out target.

We just copy the Camera2DTest and modify it a little. I placed the copy in a file called TextureAtlasTest.java and renamed the two classes contained in it accordingly (TextureAtlasTest and TextureAtlasScreen).

The first thing we do is add a new member to the TextureAtlasScreen: Texture texture;

Instead of creating a Texture in the constructor, we create it in the resume() method. Remember that textures will get lost when our application comes back from a paused state, so we have to re-create them in the resume() method:

^Override public void resume() {

texture = new Texture(((GLGame)game), "atlas.png");

I just put the image in Figure 8-23 in the assets/ folder of our project and named it atlas.png. (It of course doesn't contain the gridlines shown in the figure.)

Next we need to change the definitions of the vertices. We have one Vertices instance for each entity type (cannon, cannonball, and Bob) holding a single rectangle of four vertices and six indices, making up three triangles. All we need to do is add texture coordinates to each of the vertices in accordance with the texture atlas. We also change the cannon from being represented as a triangle to being represented by a rectangle of size 1 x0.5 meters. Here's what we replace the old vertex creation code in the constructor with:

cannonVertices = new Vertices(glGraphics, 4, 6, false, true); cannonVertices.setVertices(new float[] { -0.5f, -0.25f, 0.0f, 0.5f,

0.5f, -0.25f, 1.0f, 0.5f, 0.5f, 0.25f, 1.0f, 0.0f, -0.5f, 0.25f, 0.0f, 0.0f }, 0, 16);

cannonVertices.setIndices(new short[] {0, 1, 2, 2, 3, 0}, 0, 6);

ballVertices = new Vertices(glGraphics, 4, 6, false, true);

ballVertices.setVertices(new float[] { -0.1f, -0.1f, 0.0f, 0.75f,

ballVertices.setIndices(new short[] {0, 1, 2, 2, 3, 0}, 0, 6);

targetVertices = new Vertices(glGraphics, 4, 6, false, true);

targetVertices.setVertices(new float[] { -0.25f, -0.25f, 0.5f, 1.0f,

targetVertices.setIndices(new short[] {0, 1, 2, 2, 3, 0}, 0, 6);

Each of our meshes is now composed of four vertices, each having a 2D position and texture coordinates. We added six indices to the mesh, specifying the two triangles we want to render. We also made the cannon a little smaller on the y-axis. It now has size of 1 x0.5 meters instead of 1 x1 meters. This is also reflected in the construction of the Cannon object earlier in the constructor:

Since we don't do any collision detection with the cannon itself, it doesn't really matter what size we set in that constructor, though. We just do it for consistency.

The last thing we need to change is our render method. Here it is in its full glory: ^Override public void present(float deltaTime) { GL10 gl = glGraphics.getGL(); gl.glClear(GL10.GL_C0L0R_BUFFER_BIT); camera.setViewportAndMatrices();

gl.glEnable(GL10.GL_BLEND);

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_0NE_MINUS_SRC_ALPHA);

gl.glEnable(GL10.GL_TEXTURE_2D);

texture.bind();

targetVertices.bind(); int len = targets.size(); for(int i = 0; i < len; i++) {

GameObject target = targets.get(i); gl.glLoadIdentity();

gl.glTranslatef(target.position.x, target.position.y, 0); targetVertices.draw(GL10.GL_TRIANGLES, 0, 6);

targetVertices.unbind(); gl.glLoadIdentity();

gl.glTranslatef(ball.position.x, ball.position.y, 0); ballVertices.bind();

ballVertices.draw(GL10.GL_TRIANGLES, 0, 6); ballVertices.unbind();

gl.glLoadIdentity();

gl.glTranslatef(cannon.position.x, cannon.position.y, 0); gl.glRotatef(cannon.angle, 0, 0, 1); cannonVertices.bind();

cannonVertices.draw(GL10.GL_TRIANGLES, 0, 6); cannonVertices.unbind();

Here, we enable blending and set a proper blending function, and enable texturing and bind our atlas texture. We also slightly adapt the cannonVertices.draw() call, which now renders two triangles instead of one. That's all there is to it. Figure 8-24 of our facelifting operation.

e a

1?

t?

1?

# W

Figure 8-24. Beautifying the cannon example with a texture atlas

Figure 8-24. Beautifying the cannon example with a texture atlas

There are a few more things we need to know about texture atlases:

■ When we use GL_LINEAR as the minification and/or magnification filter, there might be artifacts when two images within the atlas are touching each other. This is due to the texture mapper actually fetching the four nearest texels from a texture for a pixel on the screen. When it does that for the border of an image, it will also fetch texels from the neighboring image in the atlas. We can eliminate this problem by introducing an empty border of 2 pixels between our images. Even better, we can duplicate the border pixel of each image. The first solution is of course easier—just make sure your texture stays a power of two.

■ There's no need to lay out all the images in the atlas in a fixed grid. We could put arbitrarily sized images in the atlas as tightly as possible. All we need to know is where one image starts and ends in the atlas so we can calculate proper texture coordinates for it. Packing arbitrarily sized images is a nontrivial problem, however. There are a couple of tools on the Web that can help you with creating a texture atlas; just do a search and you'll be hit by a plethora of options.

■ Often we cannot group all images of our game into a single texture. Remember that there's a maximum texture size that varies from device to device. We can safely assume that all devices support a texture size of 512x512 pixels (or even 1024x1024). So, we just have multiple texture atlases. You should try to group objects that will be seen on the screen together in one atlas, though—say, all the objects of level 1 in one atlas, all the objects of level 2 in another, all the UI elements in another, and so on. Think about the logical grouping before finalizing your art assets.

■ Remember how we drew numbers dynamically in Mr. Nom? We used a texture atlas for that. In fact, we can perform all dynamic text rendering via a texture atlas. Just put all the characters you need for your game into an atlas and render them on demand via multiple rectangles mapping to the appropriate characters in the atlas. There are tools you can find on the Web that will generate such a so-called bitmap font for you. For our purposes in the coming chapters, we will stick to the approach we used in Mr. Nom, though: static text gets prerendered as a whole, and only dynamic text (e.g., numbers in high scores) will get rendered via an atlas.

You might have noticed that Bobs disappear a little before they are actually hit by the cannonball graphically. That's because our bounding shapes are a little too big. We have some whitespace around Bob and the cannonball in the border. What's the solution? We just make the bounding shapes a little smaller. I want you to get a feel for this, so manipulate the source until the collision feels right. You will often find such fine-tuning "opportunities" while developing a game. Fine tuning is probably one of the most crucial parts apart from good level design. Getting things to feel right can be hard, but is highly satisfactory once you achieved the level of perfection of Super Mario Brothers. Sadly, this is nothing I can teach you, as it is dependent on the look and feel of your game. Consider it the magic sauce that sets good and bad games apart.

NOTE: To handle the disappearance issue just mentioned, make the bounding rectangles a little smaller than their graphical representations to allow for some overlap before a collision is triggered.

0 0

Post a comment