一、入门指南

欢迎来到安卓游戏食谱。这本书很像一本烹饪书。它旨在解决在你为 Android 平台开发游戏时可能出现的特定的、常见的问题。解决方案是以一种经过充分测试、深思熟虑的方法提供的,这种方法易于遵循并且易于适应多种情况。

假设你知道鸡汤的原理,但是你不确定如何把一些鸡肉和蔬菜做成汤。查阅一本标准的厨房食谱会给你一步一步的制作这道汤的食谱。同样的,你将能够使用 Android 游戏配方来找出如何在游戏中编写特定场景的代码——从创建闪屏到在消灭敌人时使用碰撞检测。

在你进入食谱之前,重要的是建立适当的框架来充分利用它们。在这一章中,我们将讨论你需要什么样的技巧和工具来从这本书中获得最大的收益。

你将需要什么

游戏编程作为一门学科,很复杂,可能需要数年才能掌握。但是游戏编程的基本概念其实学起来比较简单,在很多情况下都是可以重用的。你在游戏和代码上投入的时间将最终决定你和你的游戏有多成功。每个人在编写代码时都会遇到这样一个问题,不管你绞尽脑汁想了多久,或者在谷歌上搜索了多少次,你都无法得到一个精确的解决方案。这本书旨在为你解决这些问题。

技能和经验

这本书不是针对新手或者没有游戏开发经验的人。通过阅读这本书,你不会学到如何从头开始开发一个完整的游戏。这并不是说你需要成为一个专业的游戏开发者才能使用这本书。相反,通过阅读这本书,你很可能是一个休闲游戏开发者;你可能尝试过开发一两个游戏(甚至可能是为 Android 开发的),但在将你的一些开发知识转换到 Android 平台时遇到了问题。

这本书致力于帮助你解决特定的问题或场景。所以你至少要有游戏开发的工作知识,至少要有 Android 专用开发的基础知识。从“从头开始”初级读本的角度来看,这两个主题都不会涉及。

由于 Android 是用 Java 开发的,所以你也应该具备良好的 Java 开发知识。不会有关于 Java 如何工作的教程,在某些场景中可能暗示你知道 Java 结构背后的含义。

然而,你可能在另一个平台上有一些游戏开发经验——比如 Windows——甚至可能有一些商业级别的 Java 经验,但从未使用过 OpenGL ES。大多数时候,为 Android 开发游戏需要使用 OpenGL ES。出于这个原因,本章的第二部分致力于向您介绍 OpenGL ES,并解释为什么它对 Android 很重要。如果你已经有了 OpenGL ES 的经验,请随意跳过这一章的“OpenGL ES 概览”

简而言之,如果你对游戏开发和 Android 有热情,但在开发中遇到了一些问题,这本书就是为你准备的。无论你已经开始开发一款游戏并遇到了问题,还是你正处于开发的初级阶段,不知道下一步该做什么, Android 游戏开发食谱将指导你克服最常见的障碍和问题。

软件版本

此时,您可能已经准备好为您的 Android 游戏场景寻找解决方案了。那么你需要什么工具来开始你的旅程呢?

这本书面向 Android 4.1 和 4.2 果冻豆。如果你不是在果冻豆工作,建议你在http://developer.android.com/sdk/升级你的 SDK。然而,这些例子应该也适用于 Android 4.0 冰淇淋三明治。如果您需要帮助,有许多资源可以帮助您下载和安装 SDK(以及您可能需要的相应 Java 组件);然而,这本书不会涵盖安装 SDK。

您还将使用开普勒版本的 Eclipse。Eclipse 的一大特点是它将支持多个版本的 Android SDKs。因此,如果需要的话,您可以在软糖豆、冰淇淋三明治甚至姜饼中快速测试您的代码。虽然您几乎可以使用任何 Java IDE 或文本编辑器来编写 Android 代码,但我更喜欢 Eclipse,因为它具有这样的特性,以及与编译和调试 Android 代码的许多更繁琐的手动操作紧密集成的精心制作的插件。毕竟 Eclipse 是 Android 的创造者 Google 推荐的 Android 官方开发 IDE。

如果你还没有 Eclipse Kepler ,并且想尝试一下,它可以从http://eclipse.org免费下载。

这本书不会深入 Eclipse 的下载或安装。有许多资源,包括 Eclipse 自己的站点和 Android 开发人员论坛上的资源,可以在您需要帮助时帮助您设置环境。

提示如果您从未安装过 Eclipse 或类似的 IDE,请仔细遵循安装说明。你最不希望的就是一个错误安装的 IDE 阻碍了你编写优秀游戏的能力。

在下一节中,我们将探索在 Android 平台上创建游戏最常用的工具之一,OpenGL ES。

OpenGL ES 一览

OpenGL ES,或称为 OpenGL for Embedded Systems,是一个开源图形 API,与 Android SDK 打包在一起。虽然对使用核心 Android 调用处理图形的支持有限,但如果不使用 OpenGL ES 来创建一个完整的游戏将是极其困难的——如果不是不可能的话。核心的 Android 图形调用缓慢而笨拙,除了少数例外,不应该用于游戏。这就是 OpenGL ES 的用武之地。

自平台诞生之初,OpenGL ES 就以这样或那样的形式包含在 Android 中。在 Android 的早期版本中,OpenGL ES 的实现是 OpenGL ES 1 的受限版本。随着 Android 的发展,Android 版本的成熟,更多功能丰富的 OpenGL ES 实现被添加进来。有了 Android 版 Jelly Bean,开发者就可以接入 OpenGL ES 2 进行游戏开发。

那么 OpenGL ES 到底为你做了什么,又是怎么做到的呢?我们来看看。

OpenGL ES 如何与 Android 协同工作

Open GL ES 与图形硬件的通信方式比核心 Android 调用要直接得多。这意味着您将数据直接发送到负责处理数据的硬件。核心 Android 调用在到达图形硬件之前必须通过核心 Android 进程、线程和解释器。为 Android 平台编写的游戏只能通过直接与 GPU(图形处理单元)通信来实现可接受的速度和可玩性。

当前版本的 Android 能够使用 OpenGL ES 1 或 OpenGL ES 2 / 3 调用。这两个版本有很大的区别,你用哪个版本将决定谁能运行你的游戏,谁不能。

注意本书中包含 OpenGL ES 代码的所有示例都在 OpenGL ES 版本 1 和 OpenGL ES 版本 2 / 3 中给出。

OpenGL ES 以两种不同的方式促进了游戏和图形硬件之间的交互。运行你的游戏的 Android 设备中使用的 GPU 类型将决定你使用哪个版本的 OpenGL ES,因此 OpenGL 将如何与硬件交互。市场上有两种主要的图形硬件,因为它们非常不同,所以需要两个不同版本的 OpenGL ES 来与之交互。

两种不同类型的硬件是具有固定功能管道的硬件和具有着色器的硬件。接下来的几节快速回顾 OpenGL ES 和固定功能管道,以及 OpenGL ES 和着色器。请记住,OpenGL ES 版本 1 运行在固定函数管道上,而 OpenGL ES 2 / 3 运行在着色器上。

固定功能流水线

较老的设备将具有采用固定功能流水线的硬件。在这些较旧的 GPU 中,有特定的专用硬件来执行功能。诸如转换之类的功能是由 GPU 的专用部分来执行的,而作为开发人员,您对此几乎无法控制。这意味着你只需将你的顶点交给 GPU,告诉它转换顶点,就这样。

例如,当您有一组表示立方体的顶点时,您想要将该立方体从一个位置移动到另一个位置。这可以通过将顶点放入固定功能流水线,然后告诉硬件对这些顶点执行变换来实现。然后,硬件会为您进行矩阵运算,并确定最终立方体的位置。

在下面的代码中,您将看到在固定函数管道中所做工作的一个非常简化的版本。顶点myVertices被发送到流水线中。然后使用glTranslatef()将顶点转换到新的位置。接下来的矩阵数学会在 GPU 中为你完成。

private float myVertices[] = {
0.0f, 0.0f, 0.0f,
   1.0f, 0.0f, 0.0f,
   1.0f, 1.0f, 0.0f,
   0.0f, 1.0f, 0.0f,
};

//Other OpenGL and game stuff//

gl.glMatrixMode(GL10.GL_MODELVIEW)
gl.glLoadIdentity();
gl.glTranslatef(0f, 1f, 0f);

这样做的好处是,在使用专用硬件的情况下,可以非常快速地执行该功能。硬件可以以非常快的速度执行功能,而专用硬件(或功能集非常有限的硬件)可以更快地执行功能。

这种固定功能流水线方法的缺点是硬件不能像软件那样改变或重新配置。这限制了硬件向前发展的有用性。此外,专用硬件一次只能对一个队列项执行功能。这意味着,如果队列中有大量项目等待处理,管道通常会变慢。

另一方面,较新的设备具有使用着色器的 GPU。着色器仍然是一种专门的硬件,但它比其固定功能的前身灵活得多。OpenGL ES 通过使用一种称为 GLSL 或 OpenGL 着色语言的编程语言来执行任何数量的可编程任务,从而与着色器一起工作。

着色器

着色器是一种用着色器语言编写的软件程序,它执行过去由固定功能硬件处理的所有功能。OpenGL ES 2 / 3 使用两种不同类型的着色器:顶点着色器和片段着色器。

顶点着色器

顶点着色器对顶点执行功能,例如变换顶点的颜色、位置和纹理。着色器将在传递给它的每个顶点上运行。这意味着,如果你有一个由 256 个顶点组成的形状,顶点着色器将在每个顶点上运行。

顶点可大可小。然而,在所有情况下,顶点将由许多像素组成。顶点着色器将以相同的方式处理单个顶点中的所有像素。单个顶点内的所有像素被视为单个实体。当顶点着色器完成时,它将顶点向下游传递到光栅化器,然后传递到片段着色器。

下面是一个基本的顶点着色器:

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;" +
        "varying vec2 TexCoordOut;" +
        "void main() {" +
        "  gl_FragColor = texture2D(TexCoordIn, TexCoordOut);" +
        "}";

注意还有其他类型的着色器,包括镶嵌着色器和几何着色器。这些可以是可选的,并在硬件中处理。你对他们的运作几乎一无所知。

大多数 Android 设备现在可以处理 OpenGL ES 1 和 OpenGL ES 2 调用的组合。一些开发人员,如果他们对编程着色器感到不舒服,将继续为视口和其他动态使用固定功能管道调用。要知道,随着 OpenGL 的发展,与 OpenGL ES 的固定函数管道调用的兼容性正在被淘汰。在不久的将来,你将被迫在 OpenGL ES 中只使用着色器。因此,如果你正处于 OpenGL ES 职业生涯的早期,我建议你尽最大努力使用着色器。

游戏如何工作

在开发游戏或游戏循环时,代码需要在特定的时间以特定的顺序执行。了解这个执行流对于理解应该如何设置代码是至关重要的。

以下部分将概述一个基本的游戏流程或游戏循环。

一个基本的游戏循环

每个视频游戏的核心是游戏引擎,游戏引擎的一部分是游戏循环。顾名思义,游戏引擎就是为游戏提供动力的代码。每一款游戏,无论是哪种类型的游戏——无论是 RPG、第一人称射击游戏、平台游戏,甚至是 RTS——都需要一个全功能的游戏引擎来运行。

游戏引擎通常在它自己的线程上运行,给它尽可能多的资源。游戏需要运行的所有任务,从图形到声音,都在游戏引擎中处理。

注意任何一款游戏的引擎都是为了通用而设计的。这使得它可以在多种情况下使用和重用,可能用于不同的游戏。

一个非常流行的多用途游戏引擎是虚幻引擎。虚幻引擎,最初由 Epic 在 1998 年左右为其第一人称射击游戏《虚幻》开发,已经在数百款游戏中使用。虚幻引擎很容易适应各种游戏类型,而不仅仅是第一人称射击游戏。这种通用结构和灵活性使得虚幻引擎不仅受到专业人士的欢迎,也受到临时开发人员的欢迎。

在你的游戏开发中,你可能使用了第三方游戏引擎。安卓有很多免费和收费的。如果你想建立自己的游戏引擎,这本书会对你有很大的帮助。

第三方游戏引擎中的许多进程变得模糊不清,您可能无法访问调试功能,或者您可能无法修改引擎中的代码。当你遇到问题时,你通常不得不求助于开发引擎的公司,最初的开发者可能需要花时间来修复它——如果他们真的能修复它的话。如果你正在考虑使用第三方游戏引擎,这可能是一个主要的缺点。

构建自己的游戏引擎的体验是无可替代的。这本书假设你正在这样做。本书剩余部分将要解决的许多问题都假设你正试图在 Android 上编写一个游戏引擎,并且遇到了一些常见的问题。

那么游戏引擎到底是做什么的呢?游戏引擎处理游戏执行的所有繁重工作,从播放音效和背景音乐到在屏幕上渲染图形。以下是一个典型的游戏引擎将执行的功能的部分列表。

  • 图形渲染
  • 动画
  • 声音
  • 冲突检出
  • 人工智能
  • 物理学(非碰撞)
  • 线程和内存管理
  • 建立关系网
  • 命令解释程序

游戏引擎的核心是游戏循环。虽然引擎可以处理任何事情,从设置一次性顶点缓冲区和检索图像,游戏循环服务于游戏的实际代码执行。

所有游戏都在一个代码循环中执行。这个循环执行得越快,游戏运行得就越好,对玩家的反应就越快,屏幕上的动作就越流畅。在游戏循环中执行在屏幕上绘图、移动游戏对象、计算分数、检测碰撞以及验证或无效项目所需的所有代码。

一个游戏循环就是一组在连续循环中执行的代码。该循环在游戏开始时开始,并且在游戏停止之前不会停止执行(有一些例外)。让我们来看看一个游戏循环在每一次迭代中都应该做的事情。典型的游戏循环可以执行以下操作:

  • 解释输入设备的命令
  • 跟踪人物和/或背景,以确保没有人移动到他们不应该移动到的地方
  • 测试对象之间的碰撞
  • 根据需要移动背景
  • 绘制背景
  • 画任意数量的固定物品
  • 计算任何移动物体的物理性质
  • 移动任何重新放置的武器/子弹/物品
  • 拔出武器/子弹/物品
  • 独立移动角色
  • 画人物
  • 播放音效
  • 剥离连续背景音乐的线程
  • 追踪玩家的分数
  • 跟踪和管理联网或多个玩家

这不是一个全面的列表,但它是一个相当好的列表,列出了游戏循环中所有要做的事情。

精炼和优化你所有的游戏代码是非常重要的。游戏循环中的代码越优化,它执行所有调用的速度就越快,从而给你最好的游戏体验。在下一节中,我们将了解 Android 作为一个平台是如何处理游戏引擎和游戏循环的。

安卓和游戏引擎

Android 打包了一个功能强大、功能全面的图形 API,称为 OpenGL ES。但是 OpenGL ES 对于游戏开发来说是绝对必要的吗?与其费尽周折去学习一个相当低级的 API,比如 OpenGL ES,你能不能只写一个有核心 Android API 调用的游戏?

简而言之,要让游戏高效运行,它不能依赖核心 Android API 调用来完成这种繁重的工作。是的,大多数 Android 都有核心调用,可以处理列表中的每一项。然而,Android 的渲染、声音和存储系统是为一般任务而构建的,并适应任何数量的不可预测的用途,而不是专门针对任何一个。不可预测性意味着一件事:开销。运行游戏所需的核心 Android API 调用伴随着大量无关代码。如果你正在编写商业应用,这是可以接受的,但是如果你正在编写游戏,就不可以了。开销增加了代码的速度,游戏需要更强大的功能。

为了让游戏流畅快速地运行,代码需要绕过核心 Android API 调用中固有的开销;也就是说,一个游戏应该直接与图形硬件通信以执行图形功能,直接与声卡通信以播放声音效果,等等。如果你使用通过核心 Android API 提供的标准内存、图形和声音系统,你的游戏可以与系统上运行的所有其他 Android 应用线程化。这将使游戏看起来起伏不定,运行非常缓慢。

正因如此,游戏引擎和游戏循环几乎都是用低级语言或特定 API 编写的,比如 OpenGL ES。正如我们将在第二章中提到的,低级语言为系统硬件提供了一条更直接的途径。游戏引擎需要能够从引擎获取代码和命令,并将它们直接传递给硬件。这使得游戏能够快速运行,并具有它需要的所有控制,能够提供有益的体验。

摘要

在这一章中,我们讨论了你需要什么工具来充分利用这本书。Android 版本的 Jelly Bean、Eclipse Kepler 和一些基本的 Java 和/或游戏开发经验将在本书的剩余部分帮助你。我们还讲述了 OpenGL ES 版本 1 和 2 / 3 之间的区别,以及固定管道和着色器之间的区别。

在接下来的几章中,我们将开始研究一个典型游戏引擎中的一些问题。更具体地说,我们将看看加载图像的不同方式可能出现的问题。有许多不同的图像格式和一些不同的方法来加载这些图像并显示在屏幕上。如果你尝试过,你可能会遇到一些意想不到的结果。