An Example

Let's create an example called AnimationTest with a corresponding screen called AnimationScreen. As always we'll only discuss the screen itself.

We want to render a number of cavemen, all walking to the left. Our world will be the same size as our view frustum, which has the size 4.8x3.2 meters (this is really arbitrary; we could use any size). A caveman is a DynamicGameObject with a size of 1 x1 meters. We will derive from DynamicGameObject and create a new class called Caveman, which will store an additional member that keeps track of how long the caveman has been walking already. Each caveman will move 0.5 m/s either to the left or to the right. We'll also add an update() method to the Caveman class to update the caveman's position based on the delta time and his velocity. If a caveman reaches the left or right edge of our world, we set him to the other side of the world. We'll use the image in Figure 8-25 and create TextureRegions and an Animation instance accordingly. For rendering we'll use a Camera2D instance and a SpriteBatcher because they are fancy. Listing 8-20 shows the code of the Caveman class.

Listing 8-20. Excerpt from AnimationTest, Showing the Inner Caveman Class.

static final float WORLD_WIDTH = 4.8f; static final float WORLD_HEIGHT = 3.2f;

static class Caveman extends DynamicGameObject { public float walkingTime = 0;

public Caveman(float x, float y, float width, float height) { super(x, y, width, height);

this.position.set((float)Math.random() * WORLD_WIDTH,

(float)Math.random() * WORLD_HEIGHT); this.velocity.set(Math.random() > 0.5f?-0.5f:0.5f, 0); this.walkingTime = (float)Math.random() * l0;

public void update(float deltaTime) {

position.add(velocity.x * deltaTime, velocity.y * deltaTime); if(position.x < 0) position.x = WORLD_WIDTH; if(position.x > WORLD_WIDTH) position.x = 0; walkingTime += deltaTime;

The two constants WORLD_WIDTH and WORLD_HEIGHT are part of the enclosing AnimationTest class and are used by the inner classes. Our world is 4.8x3.2 meters in size.

Next up is the inner Caveman class, which extends DynamicGameObject, since we will move cavemen based on velocity. We define an additional member that keeps track of how long the caveman is walking already. In the constructor we place the caveman at a random position and let him either walk left or right. We also initialize the walkingTime member to a number between 0 and 10; this way our cavemen won't walk in sync.

The update() method advances the caveman based on his velocity and the delta time. In case he leaves the world, we reset him to either the left or right edge. We also add the delta time to the walkingTime to keep track of how long he's been walking.

Listing 8-21 shows the AnimationScreen class.

Listing 8-21. Excerpt from AnimationTest.java: The AnimationScreen Class class AnimationScreen extends Screen { static final int NUM_CAVEMEN = l0; GLGraphics glGraphics; Caveman[] cavemen; SpriteBatcher batcher; Camera2D camera; Texture texture; Animation walkAnim;

Our screen class has the usual suspects as members. We have a GLGraphics instance, a Caveman array, a SpriteBatcher, a Camera2D, the Texture containing the walking keyframes, and an Animation instance.

public AnimationScreen(Game game) { super(game);

glGraphics = ((GLGame)game).getGLGraphics(); cavemen = new Caveman[NUM_CAVEMEN]; for(int i = 0; i < NUM_CAVEMEN; i++) {

cavemen[i] = new Caveman((float)Math.random(), (float)Math.random(), l, l);

batcher = new SpriteBatcher(glGraphics, NUM_CAVEMEN); camera = new Camera2D(glGraphics, WORLD_WIDTH, WORLD_HEIGHT);

In the constructor we create the Caveman instances, as well as the SpriteBatcher and Camera2D.

^Override public void resume() {

texture = new Texture(((GLGame)game), "walkanim.png"); walkAnim = new Animation( 0.2f, new TextureRegion(texture, 0, 0, 64, 64), new TextureRegion(texture, 64, 0, 64, 64), new TextureRegion(texture, 128, 0, 64, 64), new TextureRegion(texture, 192, 0, 64, 64));

In the resume() method we load the texture atlas containing the animation keyframes from the asset file walkanim.png, which is the same as in Figure 8-25. Afterward, we create the Animation instance, setting the frame duration to 0.2 seconds and passing in a TextureRegion for each of the keyframes in the texture atlas.

^Override public void update(float deltaTime) { int len = cavemen.length; for(int i = 0; i < len; i++) { cavemen[i].update(deltaTime);

The update() method just loops over all Caveman instances and calls their Caveman.update() method with the current delta time. This will make the cavemen move and update their walking times.

^Override public void present(float deltaTime) { GL10 gl = glGraphics.getGL(); gl.glClear(GL10. GL_COLOR_BUFFER_BIT); camera.setViewportAndMatrices();

gl.glEnable(GL10.GL_BLEND);

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); gl.glEnable(GL10. GL_TEXTURE_2D);

batcher.beginBatch(texture); int len = cavemen.length; for(int i = 0; i < len; i++) { Caveman caveman = cavemen[i];

TextureRegion keyFrame = walkAnim.getKeyFrame(caveman.walkingTime, Animation.ANIMATION_LOOPING);

batcher.drawSprite(caveman.position.x, caveman.position.y, caveman.velocity.x < 0?1:-1, 1, keyFrame); }

batcher.endBatch();

^Override public void dispose() { }

Finally we have the present() method. We start off by clearing the screen and setting the viewport and projection matrix via our camera. Next we enable blending and texture mapping, and set the blend function. We start rendering by telling the sprite batcher that we want to start a new batch using the animation texture atlas. Next we loop through all the cavemen and render them. For each caveman we first fetch the correct keyframe from the Animation instance based on the caveman's walking time. We specify that the animation should be looping. Then we draw the caveman with the correct texture region at his position.

But what do we do with the width parameter here? Remember that our animation texture only contains keyframes for the "walk left" animation. We want to flip the texture horizontally in case the caveman is walking to the right, which we can do by simply specifying a negative width. If you don't trust me, go back to the SpriteBatcher code and check whether this works. We essentially flip the rectangle of the sprite by specifying a negative width. We could do the same vertically as well by specifying a negative height.

Figure 8-26 shows our walking cavemen.

Figure 8-26 shows our walking cavemen.

Figure 8-26. Cavemen walking

And that is all there is to know to produce a nice 2D game with OpenGL ES. Note how we still separate the game logic and the presentation from each other. A caveman does not need to know that he is actually being rendered. He therefore doesn't keep any rendering-related members, such as an Animation instance or a Texture. All we need to do is keep track of the state of the caveman and how long he's been in that state. Together with his position and size, we can then render him easily by using our little helper classes.

0 0

Post a comment