android 游戏导引(2. 游戏的基础设置)
上一节已经学习了一个基本的 OpenGL 框架了,今天这一节就进一步设置一下 2D 游戏相关的东西了。对 2D 游戏的喜爱甚于 3D。 相信大多数人也是吧。
Table of Contents
1 游戏全屏显示
2行代码搞定,一个让应用去掉自己的标题栏,一个设置全屏,放在应用的创建函数 OnCreate 中:
public class GlGame extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN , WindowManager.LayoutParams. FLAG_FULLSCREEN); // ... 其他略 } }
2 设置 OpenGL 的 2D 环境
我们先要设计好要建立的世界定位方式,就是坐标系,一般的 opengl 程序是标准的笛卡尔坐标系,即左下角为原点,x横向右延伸,y纵向上延伸。像著名的 cocos2d 引擎就是此种方式。我比较偏好另一套就是左上角为原点的描述方式,所以采用了此套坐标系:
GLSurfaceView 的 Renderer,给出了三个接口:创建时,大小改变时,绘制。我们在三个函数中采取的操作如下:
- 创建时, onSurfaceCreated, 进行 OpenGL 的基本设置,如阴影平滑,深度测试啊神马的。
- 大小改变时,onSurfaceChanged, 进行投影,视口等设置。
- 绘制,onDrawFrame, 我们后续的游戏绘制主要在这个函数了。
2.1 onSurfaceCreated
不多说,都是基本设置。
public void onSurfaceCreated(GL10 gl, EGLConfig config) { // 告诉系统对透视进行修正 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); // 背景黑色 gl.glClearColor(0, 0, 0, 1); // 启用阴影平滑 gl.glShadeModel(GL10.GL_SMOOTH); // 设置深度缓存 gl.glClearDepthf(1.0f); // 启用深度测试 gl.glEnable(GL10.GL_DEPTH_TEST); // 所做深度测试的类型 gl.glDepthFunc(GL10.GL_LEQUAL); }
2.2 onSurfaceChanged
public void onSurfaceChanged(GL10 gl, int width, int height) { // 设置 OpenGL 场景的大小 gl.glViewport(0, 0, width, height); // 设置投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION); // 重置投影矩阵 gl.glLoadIdentity(); // 设置视口的大小 // gl.glOrthof(0, width, height, 0, -1000, 1000); GLU.gluOrtho2D(gl, 0, width, height, 0); // 重置模型矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); }
代码中先用 glViewPort 设定了场景的大小,我们所有的opengl世界都被投射到这个场景中,在这里,我们将场景的大小设置为窗口 Surface 的大小。
继而设置了投影矩阵,投影矩阵定义了 opengl 世界怎样反应在你的场景中,在 OpenGL 中又两种投影方式: 透视和正交。透视比较接近我们现实的方式了,你的眼睛发出的光形成一个夹角,离你的眼睛越近,东西越大,范围越小;反之离眼睛越远,东西越小,视野越开阔。因此多用于 3D 中。 而正交却是世界中的物体按照平行的光线投射到一张纸上(你的画布),仿佛被压缩在上面,无论这个物体在世界中多远,投射结果还是原来的大小, 2d 游戏多用此种投影。
设置透视投影有 glFrustum 和 glu 库的 gluPerspective, 都可以设置上面透视投影中的角度,远近等参数。还可以 gluLookAt 来设置眼睛(相机)的位置。
设置正交投影有 glOrtho 和 glu 库的 gluOrth2D :
glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far); |
gluOrth2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top); |
glu版本的y轴参数不用我们设置了,因为不会用到。由于我们的坐标系以左上角为原点,所以 left 和 top 参数为 0。
glMatrixMode() 函数指定了其下面的代码操作的是何种矩阵。 我们这里用到两类矩阵变换:投影变换和模型视图变换。投影变换中我们更改了世界空间的裁切范围,在模型视图变换中,我们可以移动和旋转世界中的物体,所以我们在绘制的时候就是在模型视图矩阵变换中。 glLoadIdentity() 用于重置矩阵,清除上次的残留信息。
代码最后设定为模型视图变换,便于我们在绘制函数中移动和变换物体模型了。
2.3 onDrawFrame
先说明一下 GLSurfaceView 的 Renderer 绘制是在独立的线程中完成的,这个函数被不断的循环调用。
前面已经设置为模型视图矩阵了,每次 onDrawFrame 的时候,我们都要 glLoadIdentity 重置一次,清除上次绘制造成的残留信息。
public void onDrawFrame(GL10 gl) { // 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 重置模型矩阵 gl.glLoadIdentity(); //... 要添加的具体绘制代码 }
3 三角形-测试
好了,上面已经设置好了,我们先来个简单的测试吧,在你的窗口中绘制一个红色三角形。先定义好三角形的三个角度,左上角为 (60,200), 右上角为 (180, 200), 下角为 (120,300)。
private FloatBuffer triggerBuffer = FloatBuffer.wrap(new float[]{ 60,200, // 左上角 180, 200, // 右上角 120,300, // 下顶角 }); public void onDrawFrame(GL10 gl) { // 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 重置模型矩阵 gl.glLoadIdentity(); gl.glPushMatrix(); gl.glColor4f(1, 0, 0, 0); // 允许设置顶点 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(2, GL10.GL_FLOAT, 0, triggerBuffer); gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3); // 取消设置顶点 gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glPopMatrix(); }
主要代码包围在了 glPushMatrix() 和 glPopMatrix() 中,OpenGL内部维护了各种矩阵栈,栈的操作就是 push 和 pop 了。这里我们的操作是在模型视图矩阵变换中进行的,所以操作的是模型视图栈。之所以压栈,我们不想绘制三角形的矩阵信息污染到外层的矩阵中,比如颜色设置等。所以大家在绘制一个单位体的时候尽量使用 push 和 pop 操作。
先用 glColor4f 设置绘制颜色为红色。opengles 中少了 glBegin, glEnd 类标准函数,采用了数组顶点来设坐标方式,主要还是性能考虑吧。默认是关掉这个选项的,所以先开启,不用了再关闭: glEnableClientState(GL10.GLVERTEXARRAY), glDisableClientState(GL10.GLVERTEXARRAY).
指定数组顶点用 glVertexPointer, 指定完了就可以用 glDrawArray() 来将指定的数组中的顶点绘制出来了。这里有必要罗嗦一下这两个函数了,因为这两个函数用到的太多了。先看第一个:
void glVertexPointer (int size, int type, int stride, Buffer pointer) |
参数:
- size
- 每个顶点有几个数值描述。必须取值2,3,4之一。初始值是 4.
- type
- 数组中每个顶点坐标的类型。取值:GLBYTE, GLSHORT, GLFIXED, GLFLOAT。初始值为 GLFLOAT。
- stride
- 数组中每个顶点间的间隔,步长(字节位移)。取值若为 0 表示数组是连续的。初始值为 0。
- pointer
- 就是你的数组了,存储着每个顶点的坐标值。初始值为 0。
注意了, type 中告诉 opengl 你的数组类型。 GLBYTE, GLSHORT, GLFLOAT 对应 byte[], short[], float[]. GLFIXED 对应 int[]. 有一个特别的地方, GLFIXED 描述的时候,大家的点坐标单位是 0x10000, 比如一个点是 (60, 120), 用 GLFIXED 的时候,需要设置为 (60 * 0x10000, 120 * 0x10000), 所以经常看到 int one = 0x10000 的编程语句, 这个数值就是这么来的。
再来看 glDrawArray:
void glDrawArrays (int mode, int first, int count); |
参数:
- mode
- 指定你要绘制何种图元, opengl 中的图元就这几个: GLPOINTS, GLLINESTRIP, GLLINELOOP, GLLINES, GLTRIANGLESTRIP, GLTRIANGLEFAN, GLTRIANGLES.
- first
- 在已制定的数组中的开始位置(索引位置)
- count
- 点的绘制次数, 比如我们绘制一个三角形,就是绘制三个顶点,即此参数为 3。
这两个函数就介绍这么多了,足以应付这个程序了。有问题可以查官方函数文档: http://www.khronos.org/opengles/sdk/1.1/docs/man/
好了,代码就介绍完了。一个三角形大家应该会绘制了吧,大家可以试试其他的图元,点,线,四边形,多边形等等。
学习愉快。