Chapter     7

Scrolling a Background

The solutions in this chapter will help you create a scrolling background for a game. Many game types have background images that scroll as the player is playing. Chances are you have some questions about just how to make the background image of your game appear to move.

In some cases, the images will scroll automatically. For example, scrolling shooters and other “rail”-style games will have backgrounds that scroll automatically. This is in contrast to other game types, such as side-scrolling platform games, where the background image will scroll in coordination with the movements of the player (this is covered in Chapter 8,“Scrolling Multiple Backgrounds”).

This chapter will present three solutions for loading the background image, scrolling that image vertically, and scrolling that image horizontally.

7.1 Load the Background Image

Problem

Your game cannot load a background image using OpenGL ES.

Solution

Create a class that can load the image as a texture and map it to a set of vertices.

How It Works

The easiest way to load an image for use by OpenGL ES, is to create a custom class that creates all of the vertices required and maps the image as a texture to those vertices. Because this background is going to scroll, the class also needs to map the texture in a way that it will be able to repeat itself. The background image will appear as though it goes on infinitely, if OpenGL ES can repeat the texture as it scrolls.

One of the most common types of backgrounds used to scroll infinitely, and one of the easiest to work with, is a star field. Star fields are random patterns of dots that are easy to repeat seamlessly. Games such as side-scrolling shooters often use star fields as an infinitely scrolling background.

Figure 7-1 illustrates the star field image that will be used in this solution.

9781430257646_Fig07-01.jpg

Figure 7-1. A star field image

The first step is to add the image to the correct res/drawable folder of your project. We’ve already discussed adding images to a project, and the various folders available for this purpose (see Chapter 2, “Loading and Image,” Chapter 3, “The Splash Screen,” or Chapter 6, “Loading a Sprite Sheet,” for more specific information). After the image file has been added to the project, create a new class. For this solution, the name of the new class will be SBGBackground().

public class SBGBackground {

}

A similar class was created in Chapter 6 to load the image and vertices for a spritesheet character. Much of the code for Listings 7-1 (for OpenGL ES 1) and 7-2 (for OpenGL ES 2/3) come directly from the solution in Chapter 6.

Listing 7-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(GL10 gl) {
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);
   }
}

Listing 7-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() {" +
"}";
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 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) {
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");
GLES20.glUniform1i(fsTexture, 0);
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);
}
}

This class, in its current form, creates vertex, index, and texture arrays. It also contains a constructor that initializes the buffers and a draw() method that is called when the background image needs to be drawn. This class should look substantially familiar, based on other image classes you’ve seen from previous solutions in this book.

Take special note of the bolded line of code in Listing 7-1. This line creates an int array named textures, but only instantiates it to one element. The reason for this is that an existing OpenGL ES method used to generate texture names (glGenTextures) only accepts an array of textures, as it was built to work on multiple textures.

Now we’ll create a new method named loadTexture() using both OpenGL ES 1 and OpenGL ES 2/3, which is needed to load the image file and map it as a texture to the vertices. For OpenGL ES 1, use the following:

public void loadTexture(GL10 gl,int texture, Context context) {

}

For OpenGL ES 2/3, use the following:

public void loadTexture(int texture, Context context) {

}

Notice that the OpenGL ES 1 version of the method accepts an OpenGL ES object, the ID of the image to load, and the current Android context. Within this method, you need to create a bitmap from the image (using the ID that is passed in) and then set some texture parameters that will dictate how OpenGL ES treats the texture (see Listings 7-3 and 7-4).

Listing 7-3.  loadTexture()(OpenGL ES 1)

public void loadTexture(GL10 gl,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 7-4.  loadTexture()(OpenGL ES 2/3)

public void loadTexture(int texture, Context context) {
InputStream imagestream = 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){
//Handle your exceptions here
}finally {
try {
imagestream.close();
imagestream = null;
} catch (IOException e) {
 //Handle your exceptions here
}
}

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();
}

Pay particular attention to the bolded code in this method. This code explicitly sets the texture to repeat along the x and y axes. In OpenGL ES, the S texture coordinate axis refers to the x Cartesian axis; T refers to the y axis. Repeating the texture is critical in this example because we are using one star field image that will be repeated infinitely.

Now that the SBGBackground() class is complete, there is code that needs to be added to the game loop that utilizes the new class. There are two more steps to completing this solution. The first is to instantiate a new SBGBackground. Then the image ID must be passed to the loadTexture() method.

In your game loop, instantiate a new SBGBackground, as follows:

private SBGBackground background1 = new SBGBackground();

The game loop is contained in an implementation of an OpenGL ES Renderer. As such, it has some required methods that, again, were covered heavily in previous chapters. One of these methods is onSurfaceCreated(), and this is where the code for loading the texture should be called.

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//TODO Auto-generated method stub

...

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

The next two solutions will cover scrolling the background texture now that it has been loaded.

7.2 Scroll the Background Horizontally

Problem

The background is currently static, and it should scroll horizontally.

Solution

Create a new class in the game loop that translates the background texture a set amount on the y axis.

How It Works

The first step in the OpenGL ES 1 version of this solution is to create two 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;
float SCROLL_BACKGROUND_1 = .002f;

These variables can be local to your Renderer class, or you can store them in a separate class.

The onDrawFrame() method, within an implementation of an OpenGL ES Renderer, is called on every iteration of the game loop. You need to create a new method, called scrollBackground(), that is in turn called from the onDrawFrame() method (see Listing 7-5).

Listing 7-5.  scrollBackground() (OpenGL ES 1)

private void scrollBackground1(GL10 gl){
if (bgScroll1 == Float.MAX_VALUE){
bgScroll1 = 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();

}

The first part of this method tests the current value of the bgScroll1 variable. Given that floats have an upper limit, this if statement is necessary to insure you do not overload your float.

Next, the model matrix view is scaled and translated before you begin to work with the texture matrix. Notice that the y coordinate of the texture model is translated by the value in bgScroll1. This is what moves your background across the screen.

Finally, the draw() method of the SBGBackground() class is called, and the bgScroll1 variable is incremented by the value in the SCROLL_BACKGROUND_1 variable to prepare for the next iteration of the loop.

Call the new scrollBackground() method from the onDrawFrame() method and the background star field will move smoothly across the screen horizontally.

Accomplishing this same process in OpenGL ES 2/3 is slightly different (see Listing 7-6). The variable for controlling the scroll is setup in the draw() method of the object class. This variable can also be passed into the draw() method, like that used for the spritesheet solution in Chapter 6. However, since this background is scrolling automatically, and infinitely, it makes more sense to handle everything in the method.

Listing 7-6.  scrollBackground() (OpenGL ES 2/3)

class SBGBackground{
public float scroll = 0;
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) {
      ...
   }
public SBGBackground() {
...
}

public void draw(float[] mvpMatrix) {
scroll += .01f;
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);
}
}

7.3 Scroll the Background Vertically

Problem

The background is currently static, and it should scroll horizontally.

Solution

Create a new class in the game loop that translates the background texture a set amount on the x axis.

How It Works

Building on the previous solution, only one change needs to be made to scroll the background vertically rather than horizontally, as shown in Listings 7-7 and 7-8.

Listing 7-7.  Vertical Scroll (OpenGL ES 1)

private void scrollBackground1(GL10 gl){
if (bgScroll1 == Float.MAX_VALUE){
bgScroll1 = 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(bgScroll1, 0.0f, 0.0f);

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

}

Listing 7-8.  Vertical Scroll (OpenGL ES 2/3)

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,TexCoordOut.y+ scroll));"+
"}";

Notice the bolded code in the scrollBackground() for OpenGL ES 1 method. The bgScroll1 value has been moved from the y axis position to the x axis position in the glTranslatef() method call. This is all that is needed to cause the background to scroll vertically rather than horizontally.

The only code that needs to be changed for OpenGL ES 2/3 is the fragment shader. The scroll float is now added to the y coordinate of the texture rather than the x.

..................Content has been hidden....................

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