Android4 游戏开发入门指南(四)

原文:Beginning Android 4 Games Development

协议:CC BY-NC-SA 4.0

九、发布您的游戏

到目前为止,你可能已经有了一个相当有趣的 2d 游戏。像大多数休闲游戏开发者一样,你可能想与世界其他地方分享你的创作。让你的游戏进入大众手中和设备上的方法是将它发布到 Android Marketplace。本章将概述在 Android Marketplace 上发布游戏的流程。

在您可以发布您的杰作之前,您必须做一些事情来准备您的代码被编译以供发布。这一章将带你完成准备游戏发布的步骤。您必须准备好您的AndroidManifest文件,并签署和调整您的代码。

**注意:**网上有很多资源,包括 Android 开发者论坛,可以获得关于实际上传到市场的指导。这一章不包括上传过程,只包括可能被忽略的准备步骤。

这是您最后一次使用目前为止创建的二维代码的机会。在本书的剩余部分,你将学习创建 3d 游戏的技巧。然而,无论你想发布什么样的游戏或应用,这里概述的步骤都适用。

准备您的货单

准备要发布的代码的第一步是确保您的AndroidManifest文件是有序的。您的AndroidManifest必须具备三条关键信息才能发布。这些关键信息是

  • versionCode
  • versionName
  • android:icon

在 XML 视图中打开您的AndroidManfest文件。您必须在货单中包含的信息以粗体显示如下:

`<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.proandroidgames”
android:versionCode=“1”
android:versionName=“1.0”>

<application android:label=“@string/app_name” android:icon=“@drawable/sficon”>








`

如果您的AndroidManifest文件没有此信息,您必须在继续之前添加它。市场主要使用versionCodeversionName来跟踪你上传的游戏版本。如果你要发布游戏的升级版本,这是很有帮助的。

上述代码中的另一个关键元素是图标的规范。你的游戏必须有一个图标显示在 Android 用户界面上。图标不需要精心制作;它甚至可以是普通的 Android 图标,但你确实需要一个。

然而,这些信息应该已经在您的清单中了,特别是如果您使用 Eclipse 来创建您的项目。下一步是签名、发布编译和调整您的代码。

准备签署、校准和发布

所有发布到 Android Marketplace 的应用都必须经过代码签名。这允许市场识别你,除非你的游戏被签名,否则它不会被接受。如果您没有来自证书颁发机构(CA)的证书,您可以自行签名。Android Marketplace 将接受自签名应用。

签署代码后,您需要对齐它。对齐代码只是确保它设置在 4 位边界。4 位边界最适合在移动设备上下载。

幸运的是,如果您使用 Eclipse 作为您的 Android IDE,一个简单的向导将立刻处理这两项任务。打开你的项目,进入File 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Export,如图图 9–1 所示。这将打开导出向导。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–1。 打开导出向导

在向导打开的情况下,从 Android 目的地选择导出 Android 应用选项,如 Figure 9–2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–2。?? 选择导出安卓应用的目的地

**提示:**或者,您可以通过在 Eclipse 中右键单击项目并选择 Export 来直接进入这一步。

做出选择后,单击“下一步”按钮。Eclipse 现在将测试您的项目的AndroidManifest文件,以确保它满足前面讨论的需求——被签署和发布。在图 9–3 所示的项目检查屏幕中,点击浏览按钮。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–3。 项目检查窗口

现在,您可以让向导检查您的项目清单中的错误,这些错误会阻止对项目进行签名。

检查机器人清单的准备情况

当您单击 Browse 按钮时,将会打开一个较小的窗口,其中列出了所有已加载的项目。从该列表中选择您的项目,如 Figure 9–4 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–4。 选择星际战斗机项目

“导出向导”现在将检查您的代码,以确保它已准备好进行签名。假设您已经满足了要求,包括有一个图标、一个版本代码和一个版本名称,那么您应该会看到消息“没有发现错误”,如 Figure 9–5 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–5。?? 检查成功

检查完成后,单击“下一步”按钮开始签名过程。

创建密钥库

向导的下一个屏幕是密钥库选择,如 Figure 9–6 所示。如果您已经创建了一个现有的证书密钥库(可能是从您之前上传的应用或您购买的证书中创建的),请选择“使用现有的密钥库选项”来导入它。

但是,如果是自签名,则应该选择“创建新的密钥库”选项。选择此选项将引导您完成创建新密钥库的过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–6。??【密钥库选择】窗口

选择密钥库文件的有效位置,并输入密码。

**注意:**您应该为您的密钥库选择一个既安全又有备份的位置。每次更新游戏或应用时,都必须使用相同的密钥库。因此,如果您丢失了密钥库,您将无法再向此游戏上传更新。

点击 Next 按钮进入密钥创建窗口,如 Figure 9–7 所示。在这里,您必须输入在市场上识别您的所有信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–7。??【钥匙创造】窗口

输入密钥库所需的信息后,单击“下一步”按钮。Eclipse 现在将为您生成一个 keystore,它将在流程的下一步中用于对您的应用进行签名。在向导的下一个也是最后一个屏幕上,即“目的地和密钥/证书检查”窗口(参见图 9–8,您将选择您的.apk文件进行签名。

在真正创建之前选择.apk文件可能看起来有点混乱,但还是跟着做吧。点击 Browse 按钮,您应该会看到starfighter.apk

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–8。 向导的最终屏幕

单击“完成”按钮编译并签署您的最终游戏。在此过程中,代码将被调整为 4 位边界,使移动设备更容易下载。

你现在已经准备好将你的作品上传到 Android Marketplace 了——这让你的朋友和同事羡慕不已。如果你的游戏开发爱好更多地延伸到下一代而不是复古,这本书的其余章节正是你所需要的。本书的最后四章将建立在你到目前为止所学的技能之上,并增加你在 3d 游戏环境中使用 OpenGL 的能力。

总结

在这一章中,你学习了如何准备你的代码上传到 Android Marketplace。您还使用 Eclipse Export 向导创建了一个密钥库,并根据市场需求对您的游戏进行了签名和调整。在下一章中,你将开始使用在前八章中学到的相同技能来创建一个 3d 游戏。

十、Blob Hunter:创建 3d 游戏

在这本书的前半部分,你花了很多时间来建立你的 OpenGL ES 技能,创造了星际战士。诚然,星际战士不会让玩家敲你的门来玩它。然而,这个游戏为你做了什么远比这个更重要。你在创建一个 2-D、自上而下的射击游戏时磨练出来的技能可以很容易地转化为创建一些令人惊叹的 3-D 游戏所需的技能。

在本书的剩余部分,您将构建一个 3d 环境,用于创建任意数量的引人入胜的 3d 游戏。让我们从讨论 2d 游戏和 3d 游戏的区别开始。

比较二维和三维游戏

视觉上,我们都可以区分二维游戏和三维游戏。2d 游戏看起来是平面的,很像动画片,而 3d 游戏在动态空间中具有多面物体的外观。2d 游戏无关紧要吗?当然不是。随着令人上瘾的手机游戏的出现,如愤怒的小鸟,以及令人眼花缭乱的其他 iPhone、Android 和脸书游戏,2-D 游戏市场仍然活跃并且相当不错。你可以继续扩展你的 2-D 游戏技能,创造一些令人惊奇的游戏。然而,如果你更喜欢更复杂的 3d 游戏,你需要从学习本书剩余章节中解释的内容开始。

当你创建你的 2d 游戏星际战士时,你创建了平面正方形(用平面三角形)。然后,您将一个精灵映射到该正方形的表面来创建您的角色。然而,拿起一张纸,看看它。即使它是平面的,但它在你手中仍然是立体的。您可以转动、旋转或弯曲它。拿六张纸,创建一个立方体。现在,三维形状更清晰了,但你真正改变的只是平面纸片的数量以及它们的排列方式。

这是一个非常基本的解释,说明你在星际战士中学到的技能和你开始构建一个新的 3d 游戏斑点猎人所需要的技能之间的简单过渡。你看到了吧,在没有意识到的情况下,你一直在做 3d 工作。您忽略了 z 轴的任何值,并告诉 OpenGL 在 2d 中渲染您的场景,从而展平了一切。

就 OpenGL 而言,2d 或 3d 游戏在空间上是一样的。区别在于你如何对待对象,以及你如何告诉 OpenGL 渲染它们。您需要创建更有说服力的复杂多边形,成为您的角色和环境,而不是创建有精灵映射的平面正方形。

在这一章中,你将创建一个新的 Android 项目来保存 Blob Hunter ,它将成为你学习一些重要的 3d 游戏开发技能的沙箱。您还将设置开始三维开发所需的几个文件。

创建您的三维项目

在本节中,您将开始创建将在本书的其余部分中使用的项目。创建 3-D 项目的过程将与您创建星际战士游戏项目的过程相同。

按照您在第二章中使用的相同步骤,创建一个名为blobhunter的新项目。这个项目将包含本书剩余部分的所有例子。你不会创建另一个像星际战士一样完整的项目,你将学习把你在 2d 工作的知识转换成 3d 环境的秘密。

一旦创建了新的blobhunter项目,就用一些启动文件填充它。虽然这个项目不会有星际战士的所有 flash 和菜单,但你仍然有一些启动游戏的基本文件。

在本书的前面,你学习了如何制作菜单和闪屏。事实是,无论游戏是二维还是三维的,用来创建游戏关键部分的过程都是一样的。因此,这里不再赘述。

但是,在接下来的部分中,您将向项目中添加四个基本文件来创建和显示渲染器。这就是你在这里所做的一切。你将不会有任何菜单,或任何优雅的代码杀死程序,就像你在星际战士中做的那样。

BlobhunterActivity.java

您需要在新的blobhunter项目中创建的第一个文件是BlobhunterActivity.java。在星际战斗机项目中,StarfighterActivity.java启动了闪屏,闪屏又启动了主菜单。然而,由于这里没有这些组件,BlobhunterActivity可以简单地启动gameview

**提示:**你将在本章看到的大部分代码对你来说应该非常熟悉。从本质上来说,它都是取自星际战斗机项目。不同的是,它已经被严格地剥离和重新命名。

`package com.proandroidgames;

import android.app.Activity;
import android.os.Bundle;

public class BlobhunterActivity extends Activity {
/** Called when the activity is first created. */

private BHGameView gameView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameView = new BHGameView(this);
setContentView(gameView);
BHEngine.context = this;
}
@Override
protected void onResume() {
super.onResume();
gameView.onResume();
}

@Override
protected void onPause() {
super.onPause();
gameView.onPause();
}
}`

注意,在突出显示的部分,这段代码引用了一个名为BHGameView的类。BHGameView级扩展了GLSurfaceView,与星际战斗机中的SFGameView用途相同。直到在下一节中创建了BHGameView,前面的代码才会编译。

BHGameView

创建BHGameView类的代码非常简单,应该如下所示:

`package com.proandroidgames;

import android.content.Context;
import android.opengl.GLSurfaceView;

public class BHGameView extends GLSurfaceView {
private BHGameRenderer renderer;

public BHGameView(Context context) { super(context);

renderer = new BHGameRenderer();

this.setRenderer(renderer);

}

}`

再次注意,在突出显示的部分中,您引用了另一个类。是这个项目的游戏循环,将持有大部分代码。

游戏渲染器

现在,创建一个名为BHGameRenderer的新文件类。

`package com.proandroidgames;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class BHGameRenderer implements Renderer{

private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;

@Override
public void onDrawFrame(GL10 gl) {
loopStart = System.currentTimeMillis();
try {
if (loopRunTime < BHEngine.GAME_THREAD_FPS_SLEEP){
Thread.sleep(BHEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();

loopEnd = System.currentTimeMillis();
loopRunTime = ((loopEnd - loopStart));

}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();`

`gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glDisable(GL10.GL_DITHER);
}

}`

同样,如果你看一下BHGameRenderer的代码,你会注意到它只是你在星际战斗机中使用的代码的精简版。这将是足够让你真正进入 3d 游戏开发的代码。

BHEngine

建立项目需要创建的最后一个文件是BHEngine。在星际战斗机项目中,你创建了SFEngine文件,保存了游戏的所有全局常量、变量和方法。在 Blob Hunte r 项目中需要创建相同的文件来保存任何游戏引擎相关的代码。

`package com.proandroidgames;
import android.content.Context;
import android.view.Display;

public class BHEngine {
/Constants that will be used in the game/
public static final int GAME_THREAD_DELAY = 4000;
public static final int GAME_THREAD_FPS_SLEEP = (1000/60);
/Game Variables/

public static Context context;
}`

就是这样。现在,您应该有足够的代码来让您的项目脱离代码。然而,代码——以及包含它的项目——并没有真正做任何事情。让我们创建一个小型的三维测试来展示这个新项目可以做什么。

创建三维对象测试

在本节中,您将使用在上一节中设置的 Blob Hunter 项目,并向其中添加一些代码以生成一个 3-D 测试。你将使用星际战士中的一个精灵图像来创建一个围绕玩家旋转的快速图像。

首先拍摄侦察员的图像,如图 10–1 所示,并将其添加到斑点猎人项目的drawable-nodpi文件夹中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10–1。 侦察兵形象

将图像添加到项目中后,在BHEngine类中为它创建一个常量。

注意:创建这个三维测试的步骤对你来说应该是非常熟悉的,并且在前几章中还记忆犹新。因此,对于一些基本的(之前已经介绍过的)技术,就不多解释了。然而,如果有些东西没有意义,试着回到前面的章节。

创建常数

打开BHEngine.java文件,添加以下突出显示的代码行:

`package com.proandroidgames;

import android.content.Context;
import android.view.Display;

public class BHEngine {
/Constants that will be used in the game/
public static final int GAME_THREAD_DELAY = 4000;
public static final int GAME_THREAD_FPS_SLEEP = (1000/60);
public static final int BACKGROUND = R.drawable.scout;
/Game Variables/

public static Context context;
public static Display display;
}`

你现在要创建一个平面正方形,就像你为星际战士所做的一样,然后将这张侦察图像作为纹理映射到它上面。

创建 BHWalls 类

在项目中创建一个名为BHWalls的新类。BHWalls类将在以后的章节中被用来创建墙壁,但是它在这里将作为一种创建平面正方形的方法。所有BHWalls类的代码都来自你为星际战士创建的SFBackground类;什么都没有改变。

`package com.proandroidgames;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;

public class BHWalls {

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 BHWalls() {
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.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(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();
}
}`

现在您已经创建了一个类来构建您的对象,您将在游戏循环中实例化它。

实例化 BHWalls 类

当您创建BHWalls的实例化时,您也将创建两个 floats。这些将被用来在三维空间中移动飞船的图像。

**注意:**需要明确的是,您并没有用这个代码创建一个 3d 的船。你将只拍摄上一个项目中的一幅图像,并在三维空间中旋转它——这在星际战斗机中是不可能做到的。

`package com.proandroidgames;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class BHGameRenderer implements Renderer{
private BHWalls background = new BHWalls();
private float rotateAngle = .25f;
private float rotateIncrement = .25f;

private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;

@Override
public void onDrawFrame(GL10 gl) {
loopStart = System.currentTimeMillis();
try {
if (loopRunTime < BHEngine.GAME_THREAD_FPS_SLEEP){
Thread.sleep(BHEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();

loopEnd = System.currentTimeMillis();
loopRunTime = ((loopEnd - loopStart));`

`}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glDisable(GL10.GL_DITHER);
}

}`

BHWalls类已经被实例化,是时候调用loadTexture()方法了。

映射图像

在本节中,您将使用loadTexture()方法,它是在星际战士游戏中引入的。回想一下,loadTexture()方法会将图像映射到BHWalls的顶点上。

`package com.proandroidgames;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class BHGameRenderer implements Renderer{
private BHWalls background = new BHWalls();
private float rotateAngle = .25f;
private float rotateIncrement = .25f;

private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;

@Override
public void onDrawFrame(GL10 gl) {
loopStart = System.currentTimeMillis();
try {
if (loopRunTime < BHEngine.GAME_THREAD_FPS_SLEEP){ Thread.sleep(BHEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();

loopEnd = System.currentTimeMillis();
loopRunTime = ((loopEnd - loopStart));

}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glDisable(GL10.GL_DITHER);
background.loadTexture(gl,BHEngine.BACKGROUND, BHEngine.context);
}

}`

在这一点上,你可能想知道使用 OpenGL ES 进行 2-D 和 3-D 的最大区别在哪里,因为到目前为止,你使用的所有代码都来自 2-D Star Fighter 项目。

OpenGL 处理二维和三维的主要区别在于你如何告诉系统渲染你的世界。在星际战士游戏中,你告诉 Open GL 使用glOrthof()方法将你的世界渲染成一个扁平的 2d 环境。

glOrthof()方法丢弃了 z 轴值的含义。也就是说,当你使用glOrthof()时,所有的东西都以同样的尺寸呈现,不管它离玩家有多远。

为了在 3d 中渲染你的对象,你将使用gluPerspective(),这将在下面讨论。

使用 gluPerspective()

gluPerspective()方法将考虑对象在 z 轴上与玩家的距离,然后以相对于其位置的正确大小和视角呈现对象。

gluPerspective()方法的参数与glOrthof()略有不同。要调用gluPerspective(),你需要给它传递一个有效的GL10实例,一个视角,一个方向,一个近的和一个远的 z 轴裁剪平面。

gluPerspective(gl10, angle, aspect, nearz, farz)

传递给gluPerspective()的角度指定了您希望 OpenGL 渲染的视角;任何超出该视角的东西都不会被看到。aspect参数是一个宽度/高度的浮点数。最后,近 z 裁剪平面和远 z 裁剪平面告诉 OpenGL 在哪里停止渲染。任何比近 z 平面更近或比远 z 平面更远的物体都将从渲染中被裁剪掉。

BHGameRenderonSurfaceChanged()方法中,您将添加对gluPerspective()的调用。

`package com.proandroidgames;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class BHGameRenderer implements Renderer{
private BHWalls background = new BHWalls();
private float rotateAngle = .25f;
private float rotateIncrement = .25f;

private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;`

`@Override
public void onDrawFrame(GL10 gl) {
loopStart = System.currentTimeMillis();
try {
if (loopRunTime < BHEngine.GAME_THREAD_FPS_SLEEP){
Thread.sleep(BHEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();

loopEnd = System.currentTimeMillis();
loopRunTime = ((loopEnd - loopStart));

}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();

GLU.gluPerspective(gl, 45.0f, (float) width / height, .1f, 100.f);

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glDisable(GL10.GL_DITHER);
background.loadTexture(gl,BHEngine.BACKGROUND, BHEngine.context);
}
}`

在下一节中,您将使用名为drawBackground()的方法绘制背景平面。

创建 drawBackground()方法

你需要一个新的方法来将BHWalls顶点绘制到屏幕上,并移动它们来展示 OpenGL 的 3d 渲染。现在,创建一个drawBackground()方法,它将使用glRotatef()方法在 z 轴上围绕玩家旋转侦察员的图像。

OpenGL 方法glRotatef()有四个参数。第一个指定旋转的角度。第二个、第三个和第四个参数是 x、y 和 z 轴的标志,指示要对哪个轴应用旋转角度。

以下代码显示了上下文中的drawBackground()方法:

`package com.proandroidgames;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class BHGameRenderer implements Renderer{
private BHWalls background = new BHWalls();
private float rotateAngle = .25f;
private float rotateIncrement = .25f;

private long loopStart = 0; private long loopEnd = 0;
private long loopRunTime = 0 ;

@Override
public void onDrawFrame(GL10 gl) {
loopStart = System.currentTimeMillis();
try {
if (loopRunTime < BHEngine.GAME_THREAD_FPS_SLEEP){
Thread.sleep(BHEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();

loopEnd = System.currentTimeMillis();
loopRunTime = ((loopEnd - loopStart));

}

private void drawBackground(GL10 gl){

GLU.gluLookAt(gl, 0f, 0f, 5f, 0f, 0f, 0f, 0f, 1f, 0f);
gl.glRotatef(rotateAngle, 0.0f, 1.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, -3f);

background.draw(gl);
rotateAngle += rotateIncrement;

}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();

GLU.gluPerspective(gl, 45.0f, (float) width / height, .1f, 100.f);

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glDisable(GL10.GL_DITHER);
background.loadTexture(gl,BHEngine.BACKGROUND, BHEngine.context);
}
}`

注意这个例子中有一个新的方法调用。这个gluLookAt()调用告诉“摄像机”在世界的哪个地方寻找。如果您曾经使用过 3-D 渲染软件,如 Maya 或 3-D Studio Max,您可能会熟悉这样一个概念,即在渲染场景时,摄影机充当场景的查看者。OpenGL 并没有真正把相机作为一个独立的对象。然而,gluLookAt()方法是一种指向渲染以查看世界上特定位置的方法。

gluLookAt()方法接受一个有效的GL10对象加上三组三个参数。这三组三个参数是眼睛的 x、y 和 z 值(渲染器正在看的地方);“相机”中心(渲染器位于世界中的位置)的 x、y 和 z 值;以及表示哪个轴向上的 x、y 和 z 位置。如本例所述,您正在告诉“摄像机”查看位于0x0y5z的一个点,将其自身集中在0x0y0z点上,并且向上的方向朝向1y

画龙点睛

现在,调用drawBackground()方法,编译你的游戏。你应该看到一个侦察船的图像在你的前方和后方旋转。

`package com.proandroidgames;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class BHGameRenderer implements Renderer{
private BHWalls background = new BHWalls();
private float rotateAngle = .25f;
private float rotateIncrement = .25f;

private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;

@Override
public void onDrawFrame(GL10 gl) {
loopStart = System.currentTimeMillis();
try {
if (loopRunTime < BHEngine.GAME_THREAD_FPS_SLEEP){
Thread.sleep(BHEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();

drawBackground(gl);

loopEnd = System.currentTimeMillis(); loopRunTime = ((loopEnd - loopStart));

}

private void drawBackground(GL10 gl){

GLU.gluLookAt(gl, 0f, 0f, 5f, 0f, 0f, 0f, 0f, 1f, 0f);
gl.glRotatef(rotateAngle, 0.0f, 1.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, -3f);

background.draw(gl);
rotateAngle += rotateIncrement;

}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();

GLU.gluPerspective(gl, 45.0f, (float) width / height, .1f, 100.f);

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glDisable(GL10.GL_DITHER);
background.loadTexture(gl,BHEngine.BACKGROUND, BHEngine.context);
}
}`

在下一章中,你将为 Blob Hunter 创建一个 3d 环境。这将是一个基于走廊的环境,很像早期的末日雷神之锤 FPS 游戏。

总结

在这一章中,你为 3d 游戏 Blob Hunter 创建了项目。您还了解了 OpenGL ES 渲染二维和三维环境的不同之处。从二维环境创建三维环境的关键在于你告诉 OpenGL 渲染你的对象的方式。OpenGL 通过允许你使用相同的顶点和纹理,并且只改变几行代码,使得从 2d 游戏到 3d 游戏的过程变得非常容易。当你创建一个物体在三维空间旋转的快速演示时,这个过程就清楚了。

十一、创造身临其境的环境

在前一章中,你学习了如何将一些新的 2d 游戏开发技巧应用到 3d 游戏中。您了解了 OpenGL ES 如何以三维方式呈现对象,以及如何移动这些对象以获得三维效果。

在这一章中,你将为你的 3d 游戏建立一个环境。因为这是一个 3d 开发的初级读本,你将学习如何创建所有 FPS 游戏的标准-走廊。您将使用在前面章节中学到的技术来创建一个 L 形走廊供玩家导航。

最后,在本书的最后一章,你将了解到你的玩家如何通过这个走廊,并实施一些碰撞检测,以防止他们穿墙而过。

让我们从你在第十章中创建的BHWalls类开始。

使用 BHWalls 类

在前一章中,您创建了一个小型的三维测试。作为这个测试的一部分,您创建了一个BHWalls类,它创建了一个方形的墙壁形状并对其应用了纹理。这如何应用到你要创建的 3d 游戏中呢?让我们来看看一个 3d 世界来找出答案。在本节中,您将学习如何从上一章的小型BHWalls测试转移到 3d 走廊。

现在看看你的周围;你看到了什么?走到外面;向上看,向下看。

如果你在室内环顾四周,你可能会看到一些墙。你看不到墙外的房间或环境,因为墙的结构挡住了你的视线。你可能知道你在一所房子或一栋大楼里,但是你的眼睛只能看到环境中没有遮挡的部分。

如果你搬到外面,这同样适用;是的,环境大了很多,你能看到的也多了很多,但你看到的依然是有限的空间。总有一些东西会限制你的视野——可能是房子,树木,甚至是你站的地方。

现在将您的注意力转移到计算机环境。直到你放置物体到你的环境中,你的玩家将拥有一个 360 度无阻碍的视野。由您决定将视图限制在您希望他们体验特定区域。在本书前面你创作的 2d 游戏星际战士中,很容易控制玩家的世界观。您创建了场景的单一静态视图。游戏的所有动作都发生在这一个视图中。

在 3d 游戏中,控制玩家看到的东西有点困难,因为玩家可以控制他们对世界的看法。因此,你必须以这样一种方式放置物体和创造环境,即你控制玩家可以看到什么,即使他们将控制如何看到它。

在 3d 第一人称游戏的早期,对游戏环境的控制是通过走廊来实现的。回想一下一些最受欢迎的早期第一人称射击游戏,如末日雷神之锤沃尔芬斯坦城堡。他们都使用房间和走廊来引导你去你需要去的地方,但让你感觉好像你在一个更大的,自由漫游的环境中。

您已经具备了创建有效的三维走廊所需的所有技能。不管任何一条走廊有多长,它都可以由一系列的墙组成。在第十章中,你建了一堵墙,并在玩家周围移动。你可以简单地多建 5、10 或 15 堵这样的墙,把它们放在特定的位置,创造出一条长长的转弯走廊。

从多个 BHWalls 实例创建走廊

让我们来看看BHWalls到底创造了什么。

`…

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

…`

这段代码是BHWalls级的一部分——你应该还记得星际战士中非常重要的一部分。这个数组表示一个可以呈现到屏幕上的正方形。虽然这个正方形理论上可以代表任何东西,但对你来说,它是一面墙。

您可以渲染多面墙,然后使用glTranslatef(),您可以将每面墙移动到位。代码看起来会像这样:

`gl.glTranslatef(0.0f, 0.0f, 0f);
gl.glRotatef( 45.0f, 0.0f,1.0f, 0.0f);
corridor.draw(gl);

gl.glTranslatef(0.0f, 0.0f, 1f);
gl.glRotatef( 45.0f, 0.0f,1.0f, 0.0f);
corridor.draw(gl);

gl.glTranslatef(-1.0f, 0.0f, 0f);
gl.glRotatef( 45.0f, 0.0f,1.0f, 0.0f);
corridor.draw(gl);

gl.glTranslatef(-1.0f, 0.0f, 1f);
gl.glRotatef( 45.0f, 0.0f,1.0f, 0.0f);
corridor.draw(gl);

gl.glTranslatef(0.0f, 0.0f, 0f);
gl.glRotatef( 0.0f, 0.0f,0.0f, 0.0f);
corridor.draw(gl);

…`

虽然这比你可以在游戏中直接使用的任何东西都要更加伪代码,但你可以看到如何通过渲染几面墙并使用 OpenGL 来平移和旋转它们来创建一个走廊;你可以拼凑出一条走廊。

然而,这种方法有其缺点。首先,这很费时间。要建造一个相当大的走廊需要很长时间才能砌好所有的墙。第二,由于要跟踪这么多独立的对象,搞砸一些事情会非常容易。你可能会弄不清哪堵墙通向哪里,把某些东西转错方向。最后,有那么多的对象需要 OpenGL 来创建、移动和渲染,你的游戏将不会尽可能的高效。

有更好的方法来创造游戏环境。您可以用一个对象一次构建整个走廊。

使用 BHCorridor 类

在本节中,您将创建一个新的类,BHCorridorBHCorridor类将负责从多个多边形创建一条走廊。然后,您将能够将该道路视为单个对象。

在下一章,也是最后一章,能够把走廊当作一个单独的物体将会非常方便,你将允许玩家在走廊中导航。这将需要四处移动对象,当您要跟踪的对象较少时,这将容易得多。

让我们构建BHCorridor类。我们将遍历整个类,因为在BHCorridor和你在上一章中使用的BHWalls类之间会有一些不同。

建造 BHCorridorClass

在本节中,您将开始构建BHCorridor类。这个类将用于一次创建一个完整的三维走廊,而不是将许多独立的墙拼凑在一起。首先,在你的 Blob Hunter 项目中创建一个名为BHCorridor的新类。

`package com.proandroidgames;

public class BHCorridor {

}`

接下来,您需要设置您的阵列。

`package com.proandroidgames;

import java.nio.FloatBuffer;

public class BHCorridor {
private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;

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

private float vertices[] = {

};

private float texture[] = {

};

}`

BHWalls类中,甚至在本书前面的SFBackground中,vertices[]数组将保存 12 个值,如下所示:

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

这些值表示正方形角的 x、y 和 z 轴坐标(更准确地说,是构成正方形的两个三角形的角)。

您将通过向数组中一次性输入构建整个走廊所需的所有坐标来构建此逻辑(而不是实例化多个墙对象并将它们粘贴在一起)。你将要建造的走廊将是 L 形的,如图图 11–1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11–1。?? 完工的走廊形状

Figure 11–1 中的图像展示了您将要创建的走廊的形状。这个图像中添加了一个任意的纹理来帮助你看清形状。请注意,走廊呈 L 形,向左弯曲,由四个主要的墙段组成。这些部分被标记为 A、B、C 和 D,我们在建造墙壁时将参考这些字母。

用 vertices[]数组构建多面墙

让我们设置vertices[]数组来创建多面墙。您可以从墙段 a 开始。这是一面平墙,正对着站在走廊尽头的玩家。

private float vertices[] = { -2.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -2.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,

接下来,您将为墙段 b 添加顶点。此段与右侧的 A 相连,并在 z 轴上向玩家延伸。

`private float vertices[] = {
-2.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
-2.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,

1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 5.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 5.0f,`

现在,为墙段 c 添加顶点。该墙段与墙段 B 相对,也向玩家延伸。

`private float vertices[] = {
-2.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
-2.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,

1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 5.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 5.0f,

0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 5.0f,
0.0f, 1.0f, 1.0f,
0.0f, 1.0f, 5.0f,`

最后,添加墙段 d 的顶点。这是与墙段 A 相对的墙段,从墙段 C 的末端向屏幕左侧延伸。

`private float vertices[] = {
-2.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
-2.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,

1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 5.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 5.0f,

0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 5.0f,
0.0f, 1.0f, 1.0f,
0.0f, 1.0f, 5.0f,

-2.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
-2.0f, 1.0f, 1.0f,
0.0f, 1.0f, 1.0f,
};`

就是这样。这些都是建造走廊所需的顶点。

**注意:**因为您使用的是 Java,所以像这样在其他.java文件中存储大型数据数组并非不可能。然后,您可以加载这些文件并从中读取数组。

随着vertices[]数组的完成,您可以创建texture[]数组。像vertices[]阵列一样,texture[]阵列需要一些小的调整,然后才能用于对走廊应用纹理。

创建纹理[]数组

在前面的章节中,您构建了一个类似如下的texture[]数组:

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

texture[]数组包含映射点,它告诉 OpenGL 纹理如何适合顶点。既然您已经创建了一个新的有四组不同顶点的vertices[]数组,那么您还需要一个包含映射点集的texture[]数组:每组顶点一个。

尽量不要纠结于如何在没有 z 轴坐标的情况下将纹理映射到走廊墙壁上。在texture[]数组中的映射点对应于纹理的角落,而不是墙的顶点。因此,因为您将把整个纹理映射到每面墙上,所以四组纹理映射点将是相同的。

`private float texture[] = {
-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

};`

现在,您的BHCorridor类应该是这样的:

`package com.proandroidgames;

import java.nio.FloatBuffer;

public class BHCorridor {
private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;

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

`private float vertices[] = {
s
-2.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
-2.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,

1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 5.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 5.0f,

0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 5.0f,
0.0f, 1.0f, 1.0f,
0.0f, 1.0f, 5.0f,

-2.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
-2.0f, 1.0f, 1.0f,
0.0f, 1.0f, 1.0f,

};

private float texture[] = {

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

};

}`

接下来,添加一个将创建缓冲区的构造函数,就像您对BHWallsSFBackground所做的那样。

`public BHCorridor() {
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);
}`

之后,添加loadTexture()方法。这里也没有什么大的变化,所以不需要进一步的解释(如果你不确定loadTexture()方法是如何工作的,回头查看第四章中的详细解释)。

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

最后,在下一节中,您将创建draw()方法。记住,渲染器调用draw()方法来绘制走廊。在BHCorridor.draw()中有一些变化来解释vertices[]数组中的多组顶点。

创建 draw()方法

您将使用指向墙段顶点的指针的glDrawArrays()方法。看看你的vertices[]阵。该阵列在视觉上被分成四组,每组四个顶点——墙段的每个角一个顶点。你需要告诉 OpenGL 每个墙段的开始和结束位置。因此,您将把每个墙段的起点和顶点数传递到glDrawArrays()中。

墙段 A 的glDrawArrays()调用将如下所示:

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0,4);

这一行告诉 OpenGL 从数组中的位置 0 开始,读取四个顶点。按照这种逻辑,墙段 2 将从数组中的位置 4 开始,延伸到另外四个顶点,依此类推。

由于对glDrawArrays()的调用是draw()方法与BHCorridor的唯一区别,您的方法应该如下所示:

`public void draw(GL10 gl) {

gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glFrontFace(GL10.GL_CCW);

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

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

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12,4);

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

}`

你完成的BHCorridor类显示在清单 11–1 中。

清单 11-1。BHCorridor.java??

`package com.proandroidgames;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;`

`import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;

public class BHCorridor {
private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;

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

private float vertices[] = {

-2.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
-2.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,

1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 5.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 5.0f,

0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 5.0f,
0.0f, 1.0f, 1.0f,
0.0f, 1.0f, 5.0f,

-2.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
-2.0f, 1.0f, 1.0f,
0.0f, 1.0f, 1.0f,

};

private float texture[] = {

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f, 1f, 1.0f,

};

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

public void draw(GL10 gl) {

gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glFrontFace(GL10.GL_CCW);

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

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

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12,4);

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

}

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

}`

添加墙壁纹理

随着BHCorridor类的创建并能够将纹理映射到墙壁上,是时候添加一个漂亮的墙壁纹理来替换图 11–1 中显示的临时纹理了。图 11–2 展示了你将要映射到走廊墙壁上的墙壁纹理。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11–2。 墙壁纹理

首先将这张图片添加到您的drawable.nodpi文件夹中。然后,在BHEngine中添加对它的引用,如下所示:

public static final int BACK_WALL = R.drawable.walltexture256;

当这个新纹理被应用时,你完成的墙将会出现如图 Figure 11–3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11–3。 带纹理的墙壁

在下一节中,你将实例化一个BHCorridor类的副本,并用你刚刚添加到斑点猎人的新纹理将它绘制到屏幕上。

呼叫 BHCorridor

正如本书前面的星际战士项目,所有斑点猎人的渲染都在游戏渲染类中执行。在第十章中,你创建了这个类,BHGameRenderer。在本节中,您将添加一个将在游戏循环中调用的drawCorridor()方法。打开BHGameRenderer.java文件,添加一个BHCorridor的新实例,如下所示:

private BHCorridor corridor = new BHCorridor();

现在,您可以创建一个drawCorridor()方法。该方法将设置gluLookAt()(参见第十章了解如何工作的描述),并在其 x 和 y 轴上旋转走廊,如图图 11–3 所示。

`private void drawCorridor(GL10 gl){

GLU.gluLookAt(gl, 0f, 0f, 5f, 0f, 0f, 0, 0, 1, 0);
gl.glRotatef( 40.0f, 1.0f,0.0f, 0.0f);
gl.glRotatef( 20.0f, 0.0f,1.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, -3f);

corridor.draw(gl);

}`

再次强调,所有这些代码看起来都应该非常熟悉。到这本书的这一点,应该很少有新的代码。你只是把你在创建的 2d 游戏中学到的代码和技能应用到 3d 环境中。

总结

在本章中,您学习了如何在一次调用中创建一个多多边形对象-走廊。然后,你给走廊添加了一个纹理,并用BHGamerRenderer渲染到屏幕上。这是你学习曲线上的一个尖峰,因为它教会你如何管理一些非常复杂的对象。几乎任何你能想到的 3d 环境,从广阔的城市景观到复杂的迷宫,都可以用这种技术建造。

在本书的最后一章,第十二章,你将创建穿越这个 3d 走廊所需的控件,包括碰撞检测,以确保玩家不会穿过你的墙壁。

十二、在三维环境中导航

你已经进入了学习 Android 游戏开发冒险的最后一章。在这本书里,你从零开始,创造了一个二维滚动射击游戏。从创建那个游戏中学到的技能,你能够创建一个 3d 游戏的环境。虽然这本书没有涵盖使用你已经获得的所有技能或一步一步地创建一个完整的 3-D 游戏,你会学到足够的基础知识,希望使用这种逻辑来完成游戏。在本章中,您将了解当您试图创建一个控制系统来导航三维走廊时,等待您的是什么样的不同。

当你为 2d星际战士游戏创建一个控制系统时,动作很简单。玩家只能向左或向右移动。在 Blob Hunter 中,玩家应该有在 z 平面上 360 度移动的自由。让我们来看看这会给你带来什么样的挑战。

在这一章的最后,我提供了一个 3D 项目的关键文件列表。选择这些文件是因为它们的复杂性、更改的数量或者在编译项目时容易引起问题。如果您在本章末尾运行 3D 项目时遇到问题,请对照摘要后列出的文件检查您的文件。

创建控制界面

在本节中,您将创建控制界面,即玩家与游戏交互的方式。

星际战斗机中,控制界面是简单的左右运动。然而,在 3d 游戏中,玩家希望能够向左、向右、向前、向后移动,还可能向上或向下看。尽管需要跟踪更多的控制,你为星际战斗机学习的基本概念仍然适用。

让我们借用一些星际战士的代码,并快速改编它,让玩家在走廊中前进。

目前,您的BlobhunterActivity应该如下所示:

`package com.proandroidgames;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;

public class BlobhunterActivity extends Activity {
private BHGameView gameView;

@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
gameView = new BHGameView(this);
setContentView(gameView);
BHEngine.context = this;
}
@Override
protected void onResume() {
super.onResume();
gameView.onResume();
}

@Override
protected void onPause() {
super.onPause();
gameView.onPause();
}

}`

你将修改你在星际战士中创建的onTouchEvent()方法来处理向前运动。

**注意:**在本章中,您将只添加前进运动控制。但是,您可以轻松地调整该控件来处理向后运动。

在添加您的onTouchEvent()方法之前,您需要向BHEngine添加一些常量。

编辑 BHEngine

这里的目标是帮助你追踪玩家正在试图做什么,以及玩家在环境中的位置。为此,将以下几行添加到您的BHEngine.java文件中:

public static final int PLAYER_FORWARD = 1; public static final int PLAYER_RIGHT = 2; public static final int PLAYER_LEFT = 3;
public static final float PLAYER_ROTATE_SPEED = 1f; public static final float PLAYER_WALK_SPEED = 0.1f; public static int playerMovementAction = 0;

PLAYER_FORWARDPLAYER_RIGHTPLAYER_LEFT常量将用于跟踪玩家触摸了什么控件,指示玩家想要在环境中移动到哪里。PLAYER_ROTATE_SPEEDPLAYER_WALK_SPEED常量分别表示玩家的视角在 y 轴上旋转的速度和玩家在环境中行走的速度。最后,playerMovementAction跟踪哪个动作(PLAYER_FORWARDPLAYER_RIGHTPLAYER_LEFT)是当前动作。

现在您的常量已经就位,您可以在BlobhunterActivity.java中创建控制界面。

编辑博客互动

您需要添加到BlobhunterActivity的第一个代码是对BHEngine.display方法的调用。你需要初始化display变量,这样控制界面就可以调用它来确定玩家触摸了屏幕上的什么地方。

`…

@Override
public void onCreate(Bundle savedInstanceState) {

BHEngine.display = ((WindowManager)
getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

super.onCreate(savedInstanceState);
gameView = new BHGameView(this);
setContentView(gameView);
BHEngine.context = this;
}

…`

初始化display后,向BlobhunterActivity类添加一个onTouchEvent()方法:

`…

@Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}

…`

如果你还有星际战士项目,可以直接从它的控制界面复制粘贴以下代码到 Blob Hunter 的新onTouchEvent()方法中。如果你不再有 Star Fighter 项目的代码,可以从 Apress 网站下载完整的项目。

**注意:**如果你要从星际战士项目中复制粘贴,一定要重命名适当的常量和变量,使之与斑点猎人项目中的相应。

`…

@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();

int height = BHEngine.display.getHeight() / 4;
int playableArea = BHEngine.display.getHeight() - height;

if (y > playableArea){
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(x < BHEngine.display.getWidth() / 2){
BHEngine.playerMovementAction = BHEngine.PLAYER_LEFT;
}else{
BHEngine.playerMovementAction = BHEngine.PLAYER_RIGHT;
}
break;
case MotionEvent.ACTION_UP:
BHEngine.playerMovementAction = 0;
break;
}
}

return false;
}

…`

接下来,让我们添加检测向前运动的控件。

让你的球员向前移动

现在,onTouchEvent()使用y >playableArea条件来检测玩家是否触摸了屏幕的下部。添加一个else语句来检测对屏幕上部的触摸。您将使用这个触摸屏幕的上部来确定用户想要向前移动。

`…

@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();`

`int height = BHEngine.display.getHeight() / 4;
int playableArea = BHEngine.display.getHeight() - height;
if (y > playableArea){
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(x < BHEngine.display.getWidth() / 2){
BHEngine.playerMovementAction = BHEngine.PLAYER_LEFT;
}else{
BHEngine.playerMovementAction = BHEngine.PLAYER_RIGHT;
}
break;
case MotionEvent.ACTION_UP:
BHEngine.playerMovementAction = 0;
break;
}
}else{
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
BHEngine.playerMovementAction = BHEngine.PLAYER_FORWARD;
break;
case MotionEvent.ACTION_UP:
BHEngine.playerMovementAction = 0;
break;
}
}

return false;
}

…`

在这段新代码中,您所做的就是检测玩家是否触摸了屏幕的上部,如果是,您就将playerMovementAction设置为PLAYER_FORWARD

请记住,当你创建一个完整的游戏时,你会想稍微调整一下,也考虑到向后触摸控制,可能还有一些向上或向下平移的控制。在下一节中,您将对BHGameRenderer类中的这些控件做出反应,并相应地在走廊中移动玩家。

穿过走廊

穿过走廊有点棘手,但通过一些练习,你可以创建一个平稳的控制系统。诚然,如果你对 OpenGL 足够熟练,能够创建自己的矩阵并执行自己的矩阵乘法,你将能够优化一个伟大的相机系统。然而,从本书开始,目标就一直是让你使用 OpenGL 的内置工具,作为手动过程的学习曲线的替代品。

打开BHGameRenderer.java,这是你的游戏循环代码存放的地方。你需要做的第一件事是添加几个变量来帮助追踪玩家的位置。

`…

public class BHGameRenderer implements Renderer{
private BHCorridor corridor = new BHCorridor();
private float corridorZPosition = -5f;

private float playerRotate = 0f;

private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;

…`

corridorZPosition变量最初设置为-5。这表示玩家在走廊中的初始位置。值-5 应该将播放器设置在走廊的末端,因为走廊,正如您在BDCorridor类中设置的那样,向 z 轴上的 4 个单位延伸。因此,从-5(或向玩家/屏幕方向 5 个单位)开始播放会给人一种玩家正站在走廊入口处的感觉。

接下来,找到您在上一章中创建的drawCorridor()方法,并删除它的所有内容,除了对走廊的draw()方法的调用,如下所示:

`private void drawCorridor(GL10 gl){

corridor.draw(gl);

}`

使用switch…case语句,类似于星际战士中的语句,你将探测到玩家试图采取的动作。然而,如果向前的动作是玩家想要做的,你该如何应对呢?

星际战士项目中,你只需向左或向右移动玩家。这两种运动都是通过 x 轴上的正值或负值来完成的。然而,在一个三维环境中,在 x 轴上加减将会导致一个侧向或扫射的运动,这不是你在这里要做的。你想让玩家向前移动,让他们把头转向左边或右边。这些动作与你在星球大战中使用的动作完全不同。

要向前移动播放器,您需要向 z 轴添加值。回想一下,您正在沿着 z 轴查看走廊,走廊的 z 轴的 0 值位于远处的墙上。因此,你从-5 开始(见corridorZPosition变量)并移动到 0。

为了模拟转动玩家的头部,你需要沿着 y 轴旋转,而不是平移:你实际上并不想沿着 y 轴或 x 轴移动;而是,就像现实生活中转头一样,想绕轴旋转。

添加一条switch . . . case语句,相应地调整corridorZPositonplayerRotate值。这和星际战斗机用的工艺一样,所以就不详细讨论了。如果它看起来不熟悉,通过第五章中的星际战斗机代码进行检查。

`private void drawCorridor(GL10 gl){

switch(BHEngine.playerMovementAction){
case BHEngine.PLAYER_FORWARD:
corridorZPosition += BHEngine.PLAYER_WALK_SPEED;
break;
case BHEngine.PLAYER_LEFT:
playerRotate -= BHEngine.PLAYER_ROTATE_SPEED;
break;
case BHEngine.PLAYER_RIGHT:
playerRotate += BHEngine.PLAYER_ROTATE_SPEED;
break;
default:
break;
}

corridor.draw(gl);

}`

在下一节中,您将调整玩家在走廊中移动时的位置或视角。

调整玩家的视角

如前所述,OpenGL 不像一些 3d 系统那样有摄像机的概念。更确切地说,可以说,你是在通过欺骗的方式让环境看起来对玩家来说是特定的。

你在 Star Fighter 中用来移动场景中 2-D 模型的相同的平移和旋转也将被用来旋转和平移走廊,以便玩家相信他或她正在穿过它。

drawCorridor()方法添加一个 translate,它将沿着 z 轴移动模型,并添加一个 rotate,它将根据玩家正在看的地方旋转模型。

`private void drawCorridor(GL10 gl){

switch(BHEngine.playerMovementAction){
case BHEngine.PLAYER_FORWARD:
corridorZPosition += BHEngine.PLAYER_WALK_SPEED;
break;
case BHEngine.PLAYER_LEFT:
playerRotate -= BHEngine.PLAYER_ROTATE_SPEED;
break;
case BHEngine.PLAYER_RIGHT:
playerRotate += BHEngine.PLAYER_ROTATE_SPEED;
break;
default: break;
}

GLU.gluLookAt(gl, 0f, 0f, 0.5f, 0f, 0f, 0f, 0f, 1f, 0f);
gl.glTranslatef(-0.5f, -0.5f, corridorZPosition);
gl.glRotatef( playerRotate, 0.0f,1.0f, 0.0f);

corridor.draw(gl);

}`

编译并运行您的代码;你现在应该有一个基本的导航系统向前移动,并向左转和向右转。使用你已经学过的技能做一点工作,你可以很容易地添加一些碰撞检测来防止玩家穿墙。自己尝试这些例子:

  • 添加一个导航控件,允许玩家在走廊中倒车。这里有一个这样做的提示:即使在屏幕上创建一个触摸,当触摸时,将从当前 z 轴位置减去一个给定的整数值。
  • 创建碰撞检测系统,防止玩家穿墙而过。给你一个提示:追踪玩家当前的轴线位置,并对照走廊墙壁的已知位置进行测试。请记住,走廊墙壁不会移动。类似这样的东西可能会对你有所帮助:

if corridorZPosition <= -5f){ corridorZPosition = -5f; } if corridorZPosition >= 0f){ corridorZPosition = 0f; }

  • 创建一个导航系统,让玩家在环境中上下查看。作为对这项任务的一个提示,考虑它听起来比实际困难。只需添加一个触摸事件,该事件将在 x 轴上的新旋转中增加或减少值。这将使玩家的视野向上或向下旋转。

你拥有创建一个全功能 3d 游戏所需的技能,而且令人惊讶的是,这些技能与你创建一个全功能 2d 游戏所用的技能是一样的;你刚刚增加了更多的细节。

总结

我希望你喜欢这本介绍创建一些有趣的休闲游戏所需的基本技能的入门书,并希望你继续练习和扩展这些技能。关于 OpenGL ES 和 Android 冰激凌三明治的内容远不止这本书所涵盖的,但是你现在已经有了一个很好的知识基础,这将帮助你在 Android 游戏开发的世界中规划你的课程。

查看关键的三维代码

下面的清单包含了在 Blob Hunter 无法正常运行时仔细检查您的工作所需的所有代码。我选择了 BHEngine.java、BHCorridor.java 和 BHGameRenderer.java。这些文件要么接触最多的代码——像 BHEngine,包含复杂的概念——像 BHCorridor,要么执行最多的功能——像 BHGameRenderer。

您可以检查的第一个文件是 BHEngine.java,如清单 12–1 所示。BHEngine 是关键设置文件,它包含整个项目中使用的设置。因为这个文件在 Blob Hunter 项目中被广泛使用,所以它最有可能在编译时引起问题。

清单 12–1。BHEngine.java??

`package com.proandroidgames;

import android.content.Context;
import android.view.Display;

public class BHEngine {
/Constants that will be used in the game/
public static final int GAME_THREAD_DELAY = 4000;
public static final int GAME_THREAD_FPS_SLEEP = (1000/60);
public static final int BACK_WALL = R.drawable.walltexture256;
public static final int PLAYER_FORWARD = 1;
public static final int PLAYER_RIGHT = 2;
public static final int PLAYER_LEFT = 3;
public static final float PLAYER_ROTATE_SPEED = 1f;
public static final float PLAYER_WALK_SPEED = 0.1f;
/Game Variables/
public static int playerMovementAction = 0;
public static Context context;
public static Display display;
}`

清单 12–2 显示了 BHCorridor.java 文件。这个文件可能会给您带来问题,因为它包含了一个代码概念,这个概念不仅是抽象的,而且在本书的第一部分中也没有涉及到。[顶点]和纹理的结构?数组是整个项目功能的关键。如果数组设置不正确,项目将无法按预期运行。检查该文件时,请密切注意数组和数组定义。

清单 12–2。BHCorridor.java??

import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer;

`import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;

public class BHCorridor {

private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;

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

private float vertices[] = {
-2.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
-2.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,

1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 5.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 5.0f,

0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 5.0f,
0.0f, 1.0f, 1.0f,
0.0f, 1.0f, 5.0f,

-2.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
-2.0f, 1.0f, 1.0f,
0.0f, 1.0f, 1.0f,
};

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

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,

-1.0f, 0.0f,
1.0f, 0f,
-1f, 1f,
1f, 1.0f,`

`};

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

public void draw(GL10 gl) {

gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glFrontFace(GL10.GL_CCW);

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

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

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8,4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12,4);

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

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

Blob Hunter 中的最后一个密钥文件是 BHGameRenderer.java。这个文件包含了 Blob 猎人游戏的游戏循环。就像 Star Fighter 一样,游戏循环是最有可能出现代码问题的地方,因为它拥有项目中所有文件中最多的代码。清单 12–3 提供了 BHGameRenderer.java 的源代码。

清单 12–3。BHGameRenderer.java??

`package com.proandroidgames;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;

public class BHGameRenderer implements Renderer{
private BHCorridor corridor = new BHCorridor();
private float corridorZPosition = -5f;
private float playerRotate = 0f;

private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;

@Override
public void onDrawFrame(GL10 gl) {
loopStart = System.currentTimeMillis();
// TODO Auto-generated method stub
try {
if (loopRunTime < BHEngine.GAME_THREAD_FPS_SLEEP){
Thread.sleep(BHEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();

drawCorridor(gl);

loopEnd = System.currentTimeMillis();
loopRunTime = ((loopEnd - loopStart));

}

private void drawCorridor(GL10 gl){

if (corridorZPosition <= -5f){
corridorZPosition = -5f;
}
if (corridorZPosition >= 0f){
corridorZPosition = 0f;
}

switch(BHEngine.playerMovementAction){
case BHEngine.PLAYER_FORWARD:
corridorZPosition += BHEngine.PLAYER_WALK_SPEED;
break;
case BHEngine.PLAYER_LEFT:
playerRotate -= BHEngine.PLAYER_ROTATE_SPEED;
break;
case BHEngine.PLAYER_RIGHT:
playerRotate += BHEngine.PLAYER_ROTATE_SPEED;
break;
default:
break;
}

GLU.gluLookAt(gl, 0f, 0f, 0.5f, 0f, 0f, 0f, 0f, 1f, 0f);
gl.glTranslatef(-0.5f, -0.5f, corridorZPosition);
gl.glRotatef( playerRotate, 0.0f,1.0f, 0.0f);

corridor.draw(gl);

}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub

gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();

GLU.gluPerspective(gl, 45.0f, (float) width / height, .1f, 100.f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();`

`}

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

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glDisable(GL10.GL_DITHER);

corridor.loadTexture(gl, BHEngine.BACK_WALL, BHEngine.context);

}

}`

第一部分:2D 游戏的策划与创作

这本书的第一部分,第一章 - 9 ,将带你经历规划和创建一个可玩的 2D 安卓游戏——星际战士的过程。这款游戏的创作将遵循一条独特的逻辑路径。首先,你将计划并撰写游戏背后的故事。接下来,您将为游戏创建背景。然后你将创建可玩和不可玩的角色。最后,您将创建武器系统和碰撞检测。在第九章的中遵循将游戏部署到移动设备所需的步骤之前,在第八章的结尾,我提供了您在第一部分中创建或修改的最重要的 2D 文件的完整代码清单。使用这些清单来比较您的代码,并确保每个游戏都能正常运行。这将为第二部分的 3D 开发阶段做好准备:“创建 3D 游戏”(第十章 - 第十二章)。

第二部分:创建 3D 游戏

在本书的第一部分,你为 Android 平台创建了你的第一个 2D 游戏。在第二部分 ( 第十章–第十二章)中,你将学习如何使用这些相同的技能来创建一个 3D 游戏。虽然你不会在第十章 - 第十二章中创建完整的 3D 游戏,但你将学习如何使用在第一章 - 第九章中学到的技能来创建引人入胜的 3D 游戏。最后,在第十二章的结尾,如同在第八章的结尾一样,您将再次看到本书这一部分的关键文件的源代码。这将帮助你检查你的工作,并加强你作为一个游戏开发者的技能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值