十三、在有障碍物情况下移动角色

如果游戏,尤其是平台游戏,以人物从左到右无阻碍地奔跑为特色,它们不会长久地吸引玩家的兴趣。

平台游戏,如超级马里奥兄弟,小行星 2,和无数其他包含障碍,玩家必须导航。在你的游戏中使用障碍物是增加刺激和阻止动作的好方法。然而,在编写游戏代码时,障碍也会增加复杂性。

这一章将会介绍一些你在游戏中遇到障碍时可能会遇到的情况。第一个场景包括让一个角色在平台之间跳跃。

13.1 平台之间的跳跃

问题

该游戏不允许玩家在游戏关卡的平台之间跳跃。

解决办法

使用预定的距离,以及数学公式来调整跳跃动画。

它是如何工作的

每个人都知道一个人跳起来是什么样子。跳跃有一种特殊的运动和流畅,这在游戏中很难复制。在这个解决方案中,我们将修改早期解决方案中的一些代码,为可玩角色创建一个跳跃动作。

第一步是创建一个供用户“跳转”的控件在解决方案 5.4 中,我向你展示了如何使用SimpleOnGestureListeneronFling()方法创建一个手势。修改代码以设置一个公共变量来指示玩家想要跳转。

GestureDetector.SimpleOnGestureListener gestureListener = new
GestureDetector.SimpleOnGestureListener(){

@Override
publicbooleanonFling(MotionEvente1, MotionEvente2, float velocityX,
floatvelocityY) {

playeraction = PLAYER_JUMPING;
}
};

playeractionPLAYER_JUMPING变量是存储在项目可访问的类中的整数。

我们要跳跃的角色是SuperBanditGuy。在本书的前面,你创建了一个SuperBanditGuy类,它创建了游戏的主角并在屏幕上移动他。修改SuperBanditGuy类,添加两个浮点数(xy),用于跟踪角色在跳跃过程中的 x 和 y 坐标。

public class SuperBanditGuy {

public float x = .75f;
public float y = .75f;
//I like to start characters a little higher than the bottom of the screen so
//that the player can see some ground under them
...

}

现在,在游戏循环中添加以下浮动,Renderer

private float previousJumpPos = 0;
private float posJump = 0;

同样,在之前的解决方案中,我们在游戏循环中创建了一个case语句,允许您测试playeraction并相应地移动角色。现在让我们修改case语句来测试PLAYER_JUMPING ,然后开始计算跳跃。清单 13-113-2 (分别是 OpenGL ES 1 和 OpenGL ES 2/3)会让你的角色进行一次基本的跳跃。

清单 13-1PLAYER_JUMPING (OpenGL 是 1)

switch(playeraction){
case PLAYER_MOVE_RIGHT:

...

break;
case PLAYER_JUMPING:
previousJumpPos = posJump;

posJump += (float)(((Math.PI / 2) / .5) * PLAYER_RUN_SPEED);
if (posJump<= Math.PI)
{
goodguy.y += 1.5 / .5 * .15 * PLAYER_RUN_SPEED;

}else{
goodguy.y -=(Math.sin((double)posJump) - Math.sin((double)previousJumpPos))* 1.5;
if (goodguy.y<= .75f){
playeraction = PLAYER_STAND;
goodguy.y = .75f;
}
}
goodguy.x += PLAYER_RUN_SPEED;

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.15f, .15f, 1f);
gl.glTranslatef(goodguy.x, goodguy.y, 0f);
gl.glPopMatrix();
gl.glLoadIdentity();

break;
...
}

清单 13-2PLAYER_JUMPING (OpenGL 是 2/3)

switch(playeraction){
case PLAYER_MOVE_RIGHT:

...

break;
case PLAYER_JUMPING:
previousJumpPos = posJump;

posJump += (float)(((Math.PI / 2) / .5) * PLAYER_RUN_SPEED);
if (posJump<= Math.PI)
{
goodguy.y += 1.5 / .5 * .15 * PLAYER_RUN_SPEED;

}else{
goodguy.y -=(Math.sin((double)posJump) - Math.sin((double)previousJumpPos))* 1.5;
if (goodguy.y<= .75f){
playeraction = PLAYER_STAND;
goodguy.y = .75f;
}
}
goodguy.x += PLAYER_RUN_SPEED;

Matrix.translateM(RotationMatrix, 0, goodguy.x, goodguy.y, 0);

break;
...
}

无论 OpenGL ES 版本如何,以令人信服的跳跃动作移动角色的关键是以下公式:

posJump += (float)(((Math.PI / 2) / .5) * PLAYER_RUN_SPEED);
if (posJump<= Math.PI)
{
goodguy.y += 1.5 / .5 * .15 * PLAYER_RUN_SPEED;
}else{
goodguy.y -=(Math.sin((double)posJump) - Math.sin((double)previousJumpPos))* 1.5;
if (goodguy.y<= .75f){
playeraction = PLAYER_STAND;
goodguy.y = .75f;
}
}

注意根据你的具体游戏,这个公式中有许多值你需要调整。这些值包括跳跃的高度和时间长度。

注意这个公式只作用于角色位置的 y 轴。x 轴位置将以角色确定的运行速度继续向左或向右移动。让我们检查这个公式的每一行。

previousJumpPos = posJump;

第一行设置了previousJumpPos ,供稍后在公式中使用。

posJump += (float)(((Math.PI / 2) / .5) * PLAYER_RUN_SPEED);

这条线描绘了正弦波上跳跃的位置。这并不用于直接确定角色在屏幕上的跳转位置。相反,它用于在内存中确定角色何时到达跳跃的顶点。

.5的值是跳跃的时间长度。虽然它不代表特定的时间单位,但它可以增加或减少,以创建更长或更短的持续跳跃。这条线的Math.PI/2或半圆周率部分简单地表示了这样一个事实,即我们开始的平面已经是正弦波的一半。

当你处理正弦波时,圆周率是周期波所需要的时间。所以我们这波的时机是半π对π。当正弦波上的当前位置小于π时,我们知道角色正处于上升到跳跃顶点的过程中。一旦角色的位置大于圆周率,我们就可以开始让它回到地面。下一行的目的是测试这种情况。

if (posJump<= Math.PI)
{
goodguy.y += 1.5 / .5 * .15 * PLAYER_RUN_SPEED;
}else{
goodguy.y -=(Math.sin((double)posJump) - Math.sin((double)previousJumpPos))* 1.5;
...
}

最后,在if语句中,第一个条件在 y 轴上向上移动字符,第二个条件向下移动字符。

goodguy.y += 1.5 / .5 * .15 * PLAYER_RUN_SPEED;

请注意,该角色的 y 轴位置会因公式而增加。这将在 y 轴上向上移动角色。与此同时,角色的 x 轴位置正在增加,就好像角色在 x 轴上朝着跳跃的方向正常移动一样。

下面一行将字符下移。

goodguy.y -=(Math.sin((double)posJump) - Math.sin((double)previousJumpPos))* 1.5;

此公式减少 y 轴位置的值,以将角色向下移动到地平面。

注意两个移动语句中的值 1.5 代表跳跃的高度。同样,您可以根据需要调整该值,以获得更高或更短的跳跃。

当跳转结束时,只需测试角色的位置是否再次位于 y 轴上的 0.75 处(添加到渲染器的 float 中定义的起始位置),然后退出跳转,如下所示。

if (goodguy.y<= .75f){
playeraction = PLAYER_STAND;
goodguy.y = .75f;
}

此代码的最佳位置是在正在减少 y 轴位置的if语句中。如果你把这段代码放在if语句之外,你就冒着让角色在一瞬间掉到地平面以下的风险。

最后,抽出角色,如列表 13-313-4 所示。

清单 13-3 。绘制角色 (OpenGL ES 1)

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.15f, .15f, 1f);
gl.glTranslatef(goodguy.x, goodguy.y, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();

清单 13-4 。绘制角色(OpenGL ES 2/3)

Matrix.translateM(RotationMatrix, 0, goodguy.x, goodguy.y, 0);

这段代码可以很容易地添加到您的渲染器中,以产生跳跃运动。

13.2 向上移动台阶

问题

角色需要跳上或跳下台阶,或者不平坦平面上的其他物体。

解决办法

使用跳转解决方案的修改版本来导航步骤。

它是如何工作的

在第 15 章中,我将介绍碰撞检测的解决方案。虽然本章中的解决方案确实提出了冲突检测的主题,但它没有后面将要介绍的解决方案那么深入。相反,这个解决方案将专门处理导航步骤所需的来自上一个解决方案的跳转代码的修改。

如果您正在向上跳,您将从来自清单 13-1 和 13-2 的相同代码开始。

需要做的修改是在if语句中,该语句测试角色是否通过跳跃下降足够远以再次到达地面。将测试值替换为台阶的高度。

if (goodguy.y<=<height of step>){
playeraction = PLAYER_STAND;
goodguy.y =<height of step>;
}

通过测试角色是否下降到台阶的高度,可以停止角色在台阶上的移动。这种解决方案要求您知道或测试台阶的高度。

这种解决方案适用于您可以在代码中预测级别布局的级别。例如,如果你使用瓷砖来建造你的关卡,你可以测试瓷砖地图来知道台阶在哪里,从而知道停止下降的高度。如果关卡的布局可以动态改变,这种解决方案就不那么好了。

如果你的关卡中有碎片或者可能有移动的平台,而你不能使用这种方法,请参考第 15 章,其中有更多关于碰撞检测的内容。