十一、移动角色

在屏幕上移动角色——无论是人、动物、机器人还是车辆——是引人注目的游戏中最重要的部分之一。如果你试图创造一个在游戏中自由移动的角色,你可能会遇到一些问题。

本章将介绍帮助你移动角色的解决方案。本章中的解决方案包括让角色奔跑,以及在角色移动时改变角色动画。

第一种方法帮助你在屏幕上向四个方向移动你的角色。其余的解决方案帮助您以不同的速度移动角色,并在角色移动时为其设置动画。

11.1 向四个方向移动角色

问题

屏幕上的角色不会移动。

解决办法

使用游戏循环来控制角色的移动。

它是如何工作的

这个解决方案要求你追踪玩家想要角色移动到哪里,然后将这个意图转换到模型矩阵的 x 或 y 轴。换句话说,一旦你捕捉到玩家想要移动的位置,你就可以使用一个switch...case语句来确定在模型矩阵中平移哪个轴,从而相应地移动屏幕上的角色。

您分三步完成此解决方案。您需要确定玩家想要移动的方向,然后创建一个保存该值的标志,最后使用该值来移动屏幕上的角色。第一步是捕捉玩家想要移动的方向。我们将通过使用SimpleOnGestureListener() 来实现这一点。

玩家将向左、向右、向上或向下滑动来指示角色应该朝哪个方向跑(想想类似寺庙跑步风格的输入系统)。在游戏的主意图中,实例化一个新的SimpleOnGestureListener(),如清单 11-1 所示。

清单 11-1SimpleOnGestureListener()

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

};

注意这个实例化中的四个if语句。它们代表向左、向右、向上和向下的动作。现在创建一个可以从主意图和游戏循环中访问的int。根据SimpleOnGestureListener()检测到的方向设置int(参见清单 11-2 )。

清单 11-2SimpleOnGestureListener()

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

...

最后,在Renderer中,创建一个方法来读取你刚刚设置的int的值,并相应地转换角色的模型矩阵,如清单 11-3清单 11-4 所示。

清单 11-3movePlayer() (OpenGL 是 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;

}
}

清单 11-4movePlayer() (OpenGL 是 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;

}
}

glTranslatef()的调用在清单 11-3 (对于 OpenGL ES 代码)中用粗体突出显示,因为你应该用在你的特定游戏中效果最好的值来转换你的模型矩阵。

11.2 以不同的速度移动角色

问题

游戏角色需要以不同的速度行走和奔跑。

解决办法

使用游戏循环数来决定角色何时应该改变速度。

它是如何工作的

在这个解决方案中,您计算已经执行的游戏循环次数,并使用这个计数来确定角色的速度何时应该改变。例如,您的游戏是以这样的方式构建的,当玩家触摸屏幕的右侧时,角色将向右移动,当玩家触摸屏幕的左侧时,角色将向左移动,当玩家不触摸屏幕时,角色静止不动。如果玩家只是短时间触摸屏幕,你可以使用这种架构让角色行走,如果玩家触摸屏幕的时间更长,就让角色奔跑。

提示 第 5 章概述了设置基于触摸控制的游戏的解决方案。

第一步是创建两个变量,这两个变量可以从游戏中的任何类中读取。第一个变量跟踪已经执行的游戏循环次数,第二个变量跟踪角色的当前速度。

public static final float PLAYER_RUN_SPEED = .15f;
public static int totalGameLoops = 0;

接下来,在Renderer类中创建一个movePlayer()方法。到目前为止,这种方法已经在本书的多个解决方案中使用。如果你需要这个方法如何工作的基本解释,请参见第 6 章

movePlayer()方法包含一个switch...case语句,该语句读取玩家的动作并相应地移动角色。修改这个方法来测试执行循环的次数,并在此基础上改变角色的速度(参见清单 11-5清单 11-6 )。

清单 11-5 。改变移动速度(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;
}
}

清单 11-6 。改变移动速度(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;
}

}

最后,在RendereronDrawFrame()方法中,每次执行增加totalGameLoops int(参见清单 11-7 )。

清单 11-7totalGameLoops

public void onDrawFrame(GL10gl) {

...

totalGameLoops +=1;
movePlayer(gl);

...
}

11.3 当角色移动时制作动画

问题

当游戏角色移动时,它看起来不像是在行走。

解决办法

使用 spritesheet 动画使角色在移动时看起来像在行走。

它是如何工作的

这个解决方案将涉及到对您已经广泛使用的movePlayer()方法进行修改。在模型矩阵被转换后,转换纹理矩阵以显示 spritesheet 中的下一帧。

注意关于使用斜板的解决方案,参见第 6 章

首先创建一个在所有类中都可见的作用域变量,该变量将用于跟踪 spritesheet 动画的当前帧。

public static float currentrunaniframe = 0f;

接下来,对movePlayer()方法进行加粗的修改(参见清单 11-811-9 )。

清单 11-8 。动画角色(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;
}
}

清单 11-9 。制作角色动画(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;
}

}

清单 11-8 中加粗的if语句基于四帧动画工作。如果 sprite 工作表中有四帧角色动画,则在四次循环后,动画需要重置为第一帧。if语句测试当前帧,并在到达第四帧时重置动画。

制作角色动画的关键(如果你在 OpenGL ES 2/3 中工作)是修改你的角色类的draw()方法,以传入你想要显示的 sprite sheet 图像的 x 和 y 位置(见第 6 章以获得完成这个的详细解决方案)。

最后,修改PLAYER_STAND案例,将动画从变为静态的“站立”图像。请记住,根据 spritesheet 的设置,该解决方案中显示的坐标可能需要更改(参见清单 11-1011-11 )。

清单 11-10PLAYER_STAND (OpenGL 是 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;

清单 11-11PLAYER_STAND (OpenGL 是 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;