Moving a character around the screen—whether it’s a person, animal, robot, or vehicle—is one of the more crucial parts of a compelling game. Chances are, if you have tried to create a character that moves freely in a game, you have run into some problems.
This chapter will present solutions to help you move your character around. Solutions in this chapter include making a character run, and changing the character animation when the character is moving.
The first solution helps you move your character in four directions on the screen. The remaining solutions help you move your character at different speeds and animate your character as it moves.
11.1 Move a Character in Four Directions
Problem
The character on the screen will not move.
How It Works
This solution requires you to track where the player wants the character to move, then translate that intent to the x or y axis of the model matrix. In other words, once you have captured where the player wants to move, you can use a switch...case statement to determine which axis to translate in the model matrix, thus moving the character on the screen accordingly.
You complete this solution in three steps. You need to determine which direction the player wants to move in, then create a flag that holds this value, and finally use that value to move the character on the screen. The first step is to capture which direction the player wants to move. We will accomplish this by using the SimpleOnGestureListener().
The player will swipe left, right, up, or down to indicate which direction the character should run (think something similar to a Temple Run–style input system).In the game's main intent, instantiate a new SimpleOnGestureListener(), as shown in Listing 11-1.
Listing 11-1. SimpleOnGestureListener()
GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEventarg0) {
//TODO Auto-generated method stub
return false;
}
@Override
public boolean onFling(MotionEvente1, MotionEvente2, float velocityX,
float velocityY) {
float leftMotion = e1.getX() - e2.getX();
float upMotion = e1.getY() - e2.getY();
float rightMotion = e2.getX() - e1.getX();
float downMotion = e2.getY() - e1.getY();
if((leftMotion == Math.max(leftMotion, rightMotion))&&
(leftMotion>Math.max(downMotion, upMotion)) )
{
}
if((rightMotion == Math.max(leftMotion, rightMotion))&&
(rightMotion>Math.max(downMotion, upMotion) )
{
}
if((upMotion == Math.max(upMotion, downMotion))&&
(upMotion>Math.max(leftMotion, rightMotion)) )
{
}
if((downMotion == Math.max(upMotion, downMotion))&&
(downMotion>Math.max(leftMotion, rightMotion)) )
{
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
//TODO Auto-generated method stub
}
@Override
public boolean onScroll(MotionEvente1, MotionEvente2, float distanceX,
float distanceY) {
//TODO Auto-generated method stub
return false;
}
@Override
public void onShowPress(MotionEvent e) {
//TODO Auto-generated method stub
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
//TODO Auto-generated method stub
return false;
}
};
Notice the four if statements within this instantiation. They represent the left, right, up, and down actions. Now create an int that can be accessed from both the main intent and the game loop. Set the int according to which direction the SimpleOnGestureListener() has detected (see Listing 11-2).
Listing 11-2. SimpleOnGestureListener()
public static int playeraction = 0;
public static final int PLAYER_MOVE_LEFT = 1;
public static final int PLAYER_MOVE_RIGHT = 2;
public static final int PLAYER_MOVE_UP = 3;
public static final int PLAYER_MOVE_DOWN = 4;
...
if((leftMotion == Math.max(leftMotion, rightMotion)) &&
(leftMotion>Math.max(downMotion, upMotion)) )
{
playeraction = PLAYER_MOVE_LEFT;
}
if((rightMotion == Math.max(leftMotion, rightMotion)) &&
(rightMotion>Math.max(downMotion, upMotion) )
{
playeraction = PLAYER_MOVE_RIGHT;
}
if((upMotion == Math.max(upMotion, downMotion)) &&
(upMotion>Math.max(leftMotion, rightMotion)) )
{
playeraction = PLAYER_MOVE_UP;
}
if((downMotion == Math.max(upMotion, downMotion)) &&
(downMotion>Math.max(leftMotion, rightMotion)) )
{
playeraction = PLAYER_MOVE_DOWN;
}
...
Finally, in the Renderer, create a method that reads the value of the int you just set and translates the model matrix of the character accordingly, as shown in Listings 11-3 and 11-4.
Listing 11-3. movePlayer()(OpenGL ES 1)
private void movePlayer(GL10gl){
switch(playeraction){
case PLAYER_MOVE_RIGHT:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(0f, .75f, 0f);
character.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PLAYER_MOVE_LEFT:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(0f, -.75f, 0f);
character.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PLAYER_MOVE_UP:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(.75f, 0f, 0f);
character.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PLAYER_MOVE_DOWN:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(-.75f, 0f, 0f);
character.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
}
}
Listing 11-4. movePlayer()(OpenGL ES 2/3)
private void movePlayer(GL10gl){
switch(playeraction){
case PLAYER_MOVE_RIGHT:
Matrix.translateM(mTMatrix, 0, 0, .75f, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix);
break;
case PLAYER_MOVE_LEFT:
Matrix.translateM(mTMatrix, 0, 0, -.75f, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix);
break;
case PLAYER_MOVE_UP:
Matrix.translateM(mTMatrix, 0, .75f, 0, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix);
break;
case PLAYER_MOVE_DOWN:
Matrix.translateM(mTMatrix, 0, -.75f, 0, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix);
break;
}
}
The calls to glTranslatef() have been highlighted in bold in Listing 11-3 (for the OpenGL ES 1code) because you should translate your model matrix by whatever values work best in your specific game.
11.2 Move a Character at Different Speeds
Solution
Use a count of game loops to determine when the character should change speeds.
How It Works
In this solution, you count the number of game loops that have been executed and use this count to determine when the character's speed should change. For example, your game is built in such a way that the character will move right when the player touches the right side of the screen, the character will move left when the player touches the left side of the screen, and the character stands still when the player is not touching the screen. You can use this architecture to let the character walk if the player just touches the screen for a short amount of time, and then run if the player touches the screen longer.
Tip Chapter 5 outlines solutions for setting up a game with touch-based controls.
The first step is to create two variables that are scoped to be read from any class in your game. The first variable is to track the number of game loops that have been executed, and the second variable tracks the current speed of the character.
public static final float PLAYER_RUN_SPEED = .15f;
public static int totalGameLoops = 0;
Next, create a movePlayer() method in the Renderer class. This method has been used in multiple solutions thus far in this book. If you need a base explanation of how this method works, please see Chapter 6.
The movePlayer() method contains a switch...case statement that reads the actions of the player and moves the character accordingly. Modify this method to test for the number of executed loops and change the speed of the character based on this (see Listings 11-5 and 11-6).
Listing 11-5. Varying the Speed of Movement (OpenGL ES 1)
private void movePlayer(GL10gl){
if (totalGameLoops> 15)
{
PLAYER_RUN_SPEED += .5f;
}
switch(playeraction){
case PLAYER_MOVE_RIGHT:
playercurrentlocation += PLAYER_RUN_SPEED;
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(playercurrentlocation, 0f, 0f);
goodguy.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PLAYER_MOVE_LEFT:
playercurrentlocation -= PLAYER_RUN_SPEED;
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(playercurrentlocation, 0f, 0f);
goodguy.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PLAYER_STAND:
PLAYER_RUN_SPEED = .15f;
totalGameLoops = 0;
break;
}
}
Listing 11-6. Varying the Speed of Movement (OpenGL ES 2/3)
private void movePlayer(GL10gl){
if (totalGameLoops> 15)
{
PLAYER_RUN_SPEED += .5f;
}
switch(playeraction){
case PLAYER_MOVE_RIGHT:
playercurrentlocation += PLAYER_RUN_SPEED;
Matrix.translateM(mTMatrix, 0, 0,playercurrentlocation, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix);
break;
case PLAYER_MOVE_LEFT:
playercurrentlocation -= PLAYER_RUN_SPEED;
Matrix.translateM(mTMatrix, 0, 0,playercurrentlocation, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix);
break;
case PLAYER_STAND:
PLAYER_RUN_SPEED = .15f;
totalGameLoops = 0;
break;
}
}
Finally, in the onDrawFrame() method of the Renderer, increment the totalGameLoops int with each execution (see Listing 11-7).
Listing 11-7. totalGameLoops
public void onDrawFrame(GL10gl) {
...
totalGameLoops +=1;
movePlayer(gl);
...
}
11.3 Animate a Character When It Moves
Problem
The game character does not appear to be walking when it moves.
How It Works
This solution will involve making a modification to the movePlayer() method that you have been working with quite extensively. After the model matrix is translated, translate the texture matrix to present the next frame in the spritesheet.
Note For solutions on working with spritesheets, see Chapter 6.
First create a scoped variable, visible from all classes, that will be used to track the current frame of spritesheet animation.
public static float currentrunaniframe = 0f;
Next, make the bolded changes to the movePlayer() method (see Listings 11-8 and 11-9).
Listing 11-8. Animating the Character (OpenGL ES 1)
private void movePlayer(GL10gl){
if (totalGameLoops> 15)
{
PLAYER_RUN_SPEED += .5f;
}
currentrunaniframe += .25f;
if (currentrunaniframe> .75f)
{
currentrunaniframe = .0f;
}
switch(playeraction){
case PLAYER_MOVE_RIGHT:
playercurrentlocation += PLAYER_RUN_SPEED;
scrollBackground1(gl, playeraction);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(playercurrentlocation, 0f, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(currentrunaniframe,.50f, 0.0f);
goodguy.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PLAYER_MOVE_LEFT:
playercurrentlocation -= PLAYER_RUN_SPEED;
scrollBackground1(gl, playeraction);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(playercurrentlocation, 0f, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(currentrunaniframe,.75f, 0.0f);
goodguy.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PLAYER_STAND:
PLAYER_RUN_SPEED = .15f;
totalGameLoops = 0;
break;
}
}
Listing 11-9. Animating the Character (OpenGL ES 2/3)
private void movePlayer(GL10gl){
if (totalGameLoops> 15)
{
PLAYER_RUN_SPEED += .5f;
}
currentrunaniframe += .25f;
if (currentrunaniframe> .75f)
{
currentrunaniframe = .0f;
}
switch(playeraction){
case PLAYER_MOVE_RIGHT:
playercurrentlocation += PLAYER_RUN_SPEED;
Matrix.translateM(mTMatrix, 0, 0, playercurrentlocation, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix,currentrunaniframe, .50f );
break;
case PLAYER_MOVE_LEFT:
playercurrentlocation -= PLAYER_RUN_SPEED;
Matrix.translateM(mTMatrix, 0, 0, playercurrentlocation, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix,currentrunaniframe, .75f );
break;
case PLAYER_STAND:
PLAYER_RUN_SPEED = .15f;
totalGameLoops = 0;
break;
}
}
The bolded if statement in Listing 11-8 works based on four frames of animation. If there are four frames of character animation in the sprite sheet, after four loops the animation needs to be reset to the first frame. The if statement tests the current frame, and resets the animation if it has reached the fourth frame.
The key to animating the character (if you are working in OpenGL ES 2/3) is to modify the draw() method of your character class to pass in the x and y locations of the sprite sheet image that you want to display (see Chapter 6 for a detailed solution to accomplish this).
Finally, modify the PLAYER_STAND case to change the animation from to a static “standing” image. Keep in mind that depending on the setup of your spritesheet, the coordinates presented in this solution might need to be altered (see Listings 11-10 and 11-11).
Listing 11-10. PLAYER_STAND (OpenGL ES 1)
case PLAYER_STAND:
PLAYER_RUN_SPEED = .15f;
totalGameLoops = 0;
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(playercurrentlocation, 0f, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(.25f,.25f, 0.0f);
goodguy.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
Listing 11-11. PLAYER_STAND (OpenGL ES 2/3)
case PLAYER_STAND:
PLAYER_RUN_SPEED = .15f;
totalGameLoops = 0;
playercurrentlocation -= PLAYER_RUN_SPEED;
Matrix.translateM(mTMatrix, 0, 0, playercurrentlocation, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0)
character.draw(mMVPMatrix,.25f, .25f );
break;
18.117.91.2