The World Renderer Class

This class should be no surprise. It simply uses the SpriteBatcher we pass to it in the constructor and renders the world accordingly. Listing 9-23 shows the beginning of the code.

Listing 9-23. Excerpt from WorldRenderer.java: Constants, Members, and Constructor package com.badlogic.androidgames.jumper;

import javax.microedition.khronos.opengles.GL10;

import com.badlogic.androidgames.framework.gl.Animation; import com.badlogic.androidgames.framework.gl.Camera2D; import com.badlogic.androidgames.framework.gl.SpriteBatcher; import com.badlogic.androidgames.framework.gl.TextureRegion; import com.badlogic.androidgames.framework.impl.GLGraphics;

public class WorldRenderer {

static final float FRUSTUM_WIDTH = 10; static final float FRUSTUM_HEIGHT = 15; GLGraphics glGraphics; World world; Camera2D cam;

public WorldRenderer(GLGraphics glGraphics, SpriteBatcher batcher, World world) { this.glGraphics = glGraphics; this.world = world;

this.cam = new Camera2D(glGraphics, FRUSTUM_WIDTH, FRUSTUM_HEIGHT); this.batcher = batcher;

SpriteBatcher batcher;

public WorldRenderer(GLGraphics glGraphics, SpriteBatcher batcher, World world) { this.glGraphics = glGraphics; this.world = world;

this.cam = new Camera2D(glGraphics, FRUSTUM_WIDTH, FRUSTUM_HEIGHT); this.batcher = batcher;

As always we start off by defining some constants. In this case it's the view frustum's width and height, which we define as 10 and 15 meters. We also have a couple of members—namely a GLGraphics instance, a camera, and the SpriteBatcher reference we get from the game screen.

The constructor takes a GLGraphics instance, a SpriteBatcher, and the World the WorldRenderer should draw as parameters. We set up all members accordingly. Listing 9-24 shows the actual rendering code.

Listing 9-24. The Rest of WorldRenderer.java: The Actual Rendering Code public void render() {

if(world.bob.position.y > cam.position.y ) cam.position.y = world.bob.position.y; cam.setViewportAndMatrices(); renderBackground(); renderObjects();

The render() method splits up rendering into two batches: one for the background image and another one for all the objects in the world. It also updates the camera position based on Bob's current y-coordinate. If he's above the camera's y-coordinate, the camera position is adjusted accordingly. Note that we use a camera that works in world units here. We only set up the matrices once for both the background and the objects.

public void renderBackground() {

batcher.beginBatch(Assets.background); batcher.drawSprite(cam.position.x, cam.position.y, FRUSTUM_WIDTH, FRUSTUM_HEIGHT, Assets.backgroundRegion);

batcher.endBatch();

The renderBackground() method simply renders the background so that it follows the camera. It does not scroll, but instead is always rendered so that it fills the complete screen. We also don't use any blending for rendering the background so we can squeeze out a little bit more performance.

public void renderObjects() { GL10 gl = glGraphics.getGL(); gl.glEnable(GL10.GL_BLEND);

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

batcher.beginBatch(Assets.items);

renderBob();

renderPlatforms();

renderItems(); renderSquirrels(); renderCastle(); batcher.endBatch(); gl.glDisable(GL10.GL_BLEND);

The renderObjects() method is responsible for rendering the second batch. This time we use blending, as all our objects have transparent background pixels. All the objects are rendered in a single batch. Looking back at the constructor of GameScreen, we see that the SpriteBatcher we use can cope with 1,000 sprites in a single batch — more than enough for our world. For each object type we have a separate rendering method.

private void renderBob() { TextureRegion keyFrame; switch(world.bob.state) { case Bob. BOB_STATE_FALL:

keyFrame = Assets. bobFaII.getKeyFrame(world.bob.stateTime, Animation.ANIMATION_LOOPING); break;

case Bob. BOB_STATE_JUMP:

keyFrame = Assets. bobJump.getKeyFrame(world.bob.stateTime, Animation.ANIMATION_LOOPING); break;

case Bob. BOB_STATE_HIT: default:

keyFrame = Assets.bobHit;

batcher.drawSprite(world.bob.position.x, world.bob.position.y, side * 1, 1, keyFrame);

The method renderBob() is responsible for rendering Bob. Based on Bob's state and state time, we select a keyframe out of the total of five keyframes we have for Bob (see Figure 9-9 earlier in the chapter). Based on Bob's velocity's x-component, we also determine which side Bob is facing. Based on that, we multiply his by with either 1 or -1 to flip the texture region accordingly. Remember, we only have keyframes for a Bob looking to the right. Note also that we don't use BOB_WIDTH or BOB_HEIGHT to specify the size of the rectangle we draw for Bob. Those sizes are the sizes of the bounding shapes, which are not necessarily the sizes of the rectangles we render. Instead we use our 1 x1-meter-to-32x32-pixel mapping. That's something we'll do for all sprite rendering; we'll either use a 1 x1 rectangle (Bob, coins, squirrels, springs), a 2x0.5 rectangle (platforms), or a 2x2 rectangle (castle).

private void renderPlatforms() {

int len = world.platforms.size(); for(int i = 0; i < len; i++) {

Platform platform = world.platforms.get(i); TextureRegion keyFrame = Assets.pIatform; if(platform.state == Platform.PLATFORM_STATE_PULVERIZING) {

keyFrame = Assets.brakingPIatform.getKeyFrame(platform.stateTime, Animation.ANIMATION_NONLOOPING);

batcher.drawSprite(platform.position.x, platform.position.y, 2, 0.5f, keyFrame);

The method renderPlatforms() loops through all the platforms in the world and selects a TextureRegion based on the platform's state. A platform can either be pulverized or not pulverized. In the latter case, we simply use the first keyframe, and in the former case we fetch a keyframe from the pulverization animation based on the platform's state time.

private void renderItems() {

int len = world.springs.size(); for(int i = 0; i < len; i++) {

Spring spring = world.springs.get(i);

batcher.drawSprite(spring.position.x, spring.position.y, 1, 1,

Assets.spring); }

len = world.coins.size(); for(int i = 0; i < len; i++) {

Coin coin = world.coins.get(i);

TextureRegion keyFrame = Assets.coinAnim.getKeyFrame(coin.stateTime, Animation.ANIMATION_LOOPING);

batcher.drawSprite(coin.position.x, coin.position.y, 1, 1, keyFrame);

The method renderItems() renders springs and coins. For springs we just use the one TextureRegion we defined in Assets, and for coins we again select a keyframe from the animation based on a coin's state time.

private void renderSquirrels() {

int len = world.squirrels.size(); for(int i = 0; i < len; i++) {

Squirrel squirrel = world.squirrels.get(i);

TextureRegion keyFrame = Assets.sqwirre!FIy.getKeyFrame(squirrel.stateTime, Animation.ANIMATION_LOOPING);

batcher.drawSprite(squirrel.position.x, squirrel.position.y, side * 1, 1, keyFrame); }

The method renderSquirrels() renders squirrels. We again fetch a keyframe based on the squirrel's state time, figure out which direction it faces, and manipulate the width accordingly when rendering it with the SpriteBatcher. This is necessary since we only have a left-facing version of the squirrel in the texture atlas.

private void renderCastle() { Castle castle = world.castle;

batcher.drawSprite(castle.position.x, castle.position.y, 2, 2, Assets.castle);

The last method is called renderCastle(), and simply draws the castle with the TextureRegion we defined in the Assets class.

That was pretty simple, wasn't it? We only have two batches to render: one for the background and one for the objects. Taking a step back we see that we render a third batch for all the UI elements of the game screen as well. That's three texture changes and three times uploading new vertices to the GPU. We could theoretically merge the UI and object batches, but that would be cumbersome and would introduce some hacks into our code. According to our optimization guidelines from Chapter 6, we should have lightning-fast rendering. Let's see whether that's true.

We are finally done. Our second game, Super Jumper, is now ready to be played.

+1 0

Post a comment