Chapter     8

Scrolling Multiple Backgrounds

In Chapter 7, solutions were presented for creating a background that could scroll. While that solution will help you create a compelling looking game, it could have more depth.

In this chapter, you will be presented with solutions for loading and using two images in your game’s background. Not only will this make your game more dynamic, it will allow you to scroll the two images at different speeds.

At the end of this chapter, a solution is presented for scrolling two different background images at different speeds. This gives a game a more realistic look and it adds depth to an otherwise flat environment.

8.1 Load Two Background Images

Problem

The background of the game needs to contain two images.

Solution

Use OpenGL to load two images to create a layered background that can be scrolled independently for a more dynamic look.

How It Works

As discussed in Chapter 7, the easiest way to load an image for use by OpenGL ES, is to create a custom class that loads all of the vertices required and maps the image as a texture to those vertices.

In this solution, you will copy two images into the res folder of your project. Then, you will instantiate two copies of the class created for the solutions in Chapter 7. Using these two separate instantiations, you will then load up and draw two different images in the background. Figures 8-1 and 8-2 illustrate the star field image and the debris field image that will be used in this solution.

9781430257646_Fig08-01.jpg

Figure 8-1. Thestar field image

9781430257646_Fig08-02.jpg

Figure 8-2. The debris field image

The first step is to add the images to the correct res/drawable folder of your project. We’ve previously discussed adding images to a project, and the various folders available for this purpose. After the image files have been added to the project, you can instantiate two copies of the class that was created in Chapter 7.

The classes need to be instantiated in the class containing the game loop. The class containing the game loop is an implementation of an OpenGL ES Renderer. The background classes should be instantiated in a location that all of the methods of the Renderer have access to.

For reference, Listings 8-1 and 8-2 show the completed code of the SBGBackground() class from Chapter 7.

Listing 8-1.  SBGBackground (OpenGL ES 1)

public class SBGBackground {

private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;
private ByteBuffer indexBuffer;

private int[] textures = new int[1];

private float vertices[] = {
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
};

private float texture[] = {
0.0f, 0.0f,
1.0f, 0f,
1f, 1.0f,
0f, 1f,
};

private byte indices[] = {
0,1,2,
0,2,3,
};

Public SBGBackground() {
ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuf.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);

indexBuffer = ByteBuffer.allocateDirect(indices.length);
indexBuffer.put(indices);
indexBuffer.position(0);
   }

public void draw(GL10gl) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);

gl.glFrontFace(GL10.GL_CCW);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);

gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, indexBuffer);

gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisable(GL10.GL_CULL_FACE);
   }

public void loadTexture(GL10gl,int texture, Context context) {
InputStream imagestream = context.getResources().openRawResource(texture);
Bitmap bitmap = null;
try {

bitmap = BitmapFactory.decodeStream(imagestream);

}catch(Exception e){

}finally {
try {
imagestream.close();
imagestream = null;
} catch (IOException e) {
}
}

gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);

GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();
}

}

Listing 8-2.  SBGBackground (OpenGL ES 2/3)

class SBGBackground{
private final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"attribute vec2 TexCoordIn;" +
"varying vec2 TexCoordOut;" +
"void main() {" +
"  gl_Position = uMVPMatrix * vPosition;" +
"  TexCoordOut = TexCoordIn;" +
"}";

private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"uniform sampler2D TexCoordIn;" +
"uniform float scroll;" +
"varying vec2 TexCoordOut;" +
"void main() {" +
" gl_FragColor = texture2D(TexCoordIn, vec2(TexCoordOut.x + scroll,TexCoordOut.y));"+
"}";
private float texture[] = {
0f, 0f,
.25f, 0f,
.25f, .25f,
0f, .25f,
};

Private int[] textures = new int[1];
private final FloatBuffer vertexBuffer;
private final ShortBuffer drawListBuffer;
private final FloatBuffer textureBuffer;
private final int mProgram;
private int mPositionHandle;
private int mMVPMatrixHandle;

static final int COORDS_PER_VERTEX = 3;
static final int COORDS_PER_TEXTURE = 2;
static float squareCoords[] = { -1f,  1f, 0.0f,
 -1f, -1f, 0.0f,
1f, -1f, 0.0f,
1f,  1f, 0.0f };

private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 };

private final int vertexStride = COORDS_PER_VERTEX * 4;
public static int textureStride = COORDS_PER_TEXTURE * 4;

public void loadTexture(int texture, Context context) {
InputStreami magestream = context.getResources().openRawResource(texture);
      Bitmap bitmap = null;

android.graphics.Matrix flip = new android.graphics.Matrix();
flip.postScale(-1f, -1f);
try {

bitmap = BitmapFactory.decodeStream(imagestream);

}catch(Exception e){

}finally {
try {
imagestream.close();
imagestream = null;
} catch (IOException e) {
}
      }

GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();
   }
public SBGBackground() {
ByteBuffer bb = ByteBuffer.allocateDirect(
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);

bb = ByteBuffer.allocateDirect(texture.length * 4);
bb.order(ByteOrder.nativeOrder());
textureBuffer = bb.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);

ByteBuffer dlb = ByteBuffer.allocateDirect(
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);

int vertexShader = SBGGameRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = SBGGameRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);

mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
}

public void draw(float[] mvpMatrix, float scroll) {
GLES20.glUseProgram(mProgram);

mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

GLES20.glEnableVertexAttribArray(mPositionHandle);

int vsTextureCoord = GLES20.glGetAttribLocation(mProgram, "TexCoordIn");
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
GLES20.glVertexAttribPointer(vsTextureCoord, COORDS_PER_TEXTURE,
GLES20.GL_FLOAT, false,
textureStride, textureBuffer);
GLES20.glEnableVertexAttribArray(vsTextureCoord);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
int fsTexture = GLES20.glGetUniformLocation(mProgram, "TexCoordOut");
int fsScroll = GLES20.glGetUniformLocation(mProgram, "scroll");
GLES20.glUniform1i(fsTexture, 0);
GLES20.glUniform1f(fsScroll, scroll);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}

If you are comparing this code to that in Chapter 7, you should notice one small change to the OpenGL ES 2/3 version. The scroll variable has been moved to the constructor. This allows you to pass in the amount of scroll so that you can scroll multiple instantiations of the background at different rates.

Instantiate the two new SBGBackground() in your game, as follows:

private SBGBackground background1 = new SBGBackground();
private SBGBackground background2 = new SBGBackground();

Now you need to load the images and map them as textures using the loadTexture() method of the SBGBackground(). The code for loading the textures should be called in the onSurfaceCreated() method of the Renderer.

public void onSurfaceCreated(GL10gl, EGLConfigconfig) {
//TODO Auto-generated method stub

...

background1.loadTexture(gl, R.drawable.starfield, context);
background1.loadTexture(gl, R.drawable.debrisfield, context);
}

The next two solutions will cover scrolling the background textures, now that they have been loaded.

8.2 Scroll Two Background Images

Problem

Only one of the background images scrolls.

Solution

Modify the game loop so both images scroll, by modifying the scroll variable for each image.

How It Works

The first step in the solution is to create four variables that will be used to track the current location of the background texture and the value by which to translate the texture, respectively.

int bgScroll1 = 0;
int bgScroll2 = 0;

float SCROLL_BACKGROUND_1 = .002f;
float SCROLL_BACKGROUND_2 = .002f;

These variables can be local to your Renderer class, or you can store them in a separate class. This solution takes a lot from a solution in Chapter 7. However, keeping track of multiple moving elements in your game can be tricky if you do not start off slow. Try to avoid skipping through this, as it can be easy to miss an important detail.

The onDrawFrame() method, within an implementation of an OpenGL ES Renderer, is called on every iteration of the game loop. Create a new method, called scrollBackgrounds(), that will be called from the onDrawFrame() method. See Listings 8-3 and 8-4.

Listing 8-3.  scrollBackgrounds() (OpenGL ES 1)

private void scrollBackgrounds(GL10gl){
if (bgScroll1 == Float.MAX_VALUE){
bgScroll1 = 0f;
}

if (bgScroll2 == Float.MAX_VALUE){
bgScroll2 = 0f;
}

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(1f, 1f, 1f);
gl.glTranslatef(0f, 0f, 0f);

gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, bgScroll1, 0.0f);

background1.draw(gl);
gl.glPopMatrix();
bgScroll1 +=  SCROLL_BACKGROUND_1;
gl.glLoadIdentity();

gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, bgScroll2, 0.0f);

background2.draw(gl);
gl.glPopMatrix();
bgScroll2 +=  SCROLL_BACKGROUND_2;
gl.glLoadIdentity();

}

Listing 8-4.  scrollBackgrounds() (OpenGL ES 2/3)

private void scrollBackgrounds(GL10gl){
if (bgScroll1 == Float.MAX_VALUE){
bgScroll1 = 0f;
}

if (bgScroll2 == Float.MAX_VALUE){
bgScroll2 = 0f;
}

background1.draw(mMVPMatrix, bgScroll1);
background2.draw(mMVPMatrix, bgScroll2);
bgScroll1 +=  SCROLL_BACKGROUND_1;
bgScroll2 +=  SCROLL_BACKGROUND_2;
}

The first part of this method tests the current value of the bgScroll1 and bgScroll2 variables. Just as in Chapter 7, the if statements are necessary to insure you do not overload your floats.

The view matrix and texture matrix models are scaled and translated to provide the needed “movement” of the background images.

Finally, call the new scrollBackgrounds() method from the onDrawFrame() method and both background images should scroll across the screen together. The background should appear as in Figure 8-3.

9781430257646_Fig08-03.jpg

Figure 8-3. Both background images together

8.3 Scroll Two Background Images at Different Speeds

Problem

The background images don’t scroll at different speeds.

Solution

Add a sense of depth by modifying the game loop to scroll multiple background images at different speeds.

How It Works

Building on the previous solution, only one change needs to be made to scroll the background images at different speeds.

Ideally, to create an artificial sense of depth, you would want the foreground image (of the two images) to scroll at a faster speed than the image that is furthest in the background.

To accomplish this effect, change the value of SCROLL_BACKGROUND_2 from the previous solution to a higher number. The higher the number that you set it to, the faster the image will scroll.

int bgScroll1 = 0;
int bgScroll2 = 0;

float SCROLL_BACKGROUND_1 = .002f;
float SCROLL_BACKGROUND_2 = .005f;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.149.94