读书人

寨子腾讯“爱消除”游戏7日教程

发布时间: 2013-10-24 18:27:21 作者: rapoo

山寨腾讯“爱消除”游戏7日教程


【前言】

最近,“爱消除”游戏异常的火爆,它正是山寨之王的作品。从今天开始,我们将连续7天,每天一个小时,用OpenGL ES技术,按照解决一般问题的思路,进入有趣的山寨之旅。

学习本教程最好的方法,就是运行附件的代码,对于不理解的地方再看看课程是如何解释的。

第一天

【课程内容】

今天我们将基于OpenGL ES搭建一个简单的游戏框架,并绘制出第一个图案。

【源代码下载地址】http://download.csdn.net/detail/elong_2009/6444773

1、设计程序框架

实现这个游戏的框架非常简单,仅包含一个activity,一个渲染视图及若干个渲染类对象。主框架代码加起来只有120行。

1.1 CrazyLinkActivity 类

该类通过创建OpenGL ES渲染视图,实现OpenGL图形的显示。

package elong.CrazyLink;

import android.app.Activity;

import android.os.Bundle;

public class CrazyLinkActivity extends Activity {

CrazyLinkGLSurfaceView mGLSurfaceView;

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mGLSurfaceView = new CrazyLinkGLSurfaceView(this);

setContentView(mGLSurfaceView);

mGLSurfaceView.requestFocus();

mGLSurfaceView.setFocusableInTouchMode(true);

}

@Override

protected void onResume() {

// TODO Auto-generated method stub

super.onResume();

mGLSurfaceView.onResume();

}

@Override

protected void onPause() {

// TODO Auto-generated method stub

super.onPause();

mGLSurfaceView.onPause();

}

}

1.2 CrazyLinkGLSurfaceView 类

该类提供了一个OpenGL ES场景渲染器,通过onDrawFrame 方法,将要绘制的图案渲染后输出。

package elong.CrazyLink;

import java.io.IOException;

import elong.CrazyLink.Draw.DrawAnimal;

import java.io.InputStream;

import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;

import elong.CrazyLink.R;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.opengl.GLSurfaceView;

import android.opengl.GLUtils;

public class CrazyLinkGLSurfaceView extends GLSurfaceView{

private SceneRenderer mRenderer;//场景渲染器

static int animalTextureId;//动物素材纹理id

public CrazyLinkGLSurfaceView(CrazyLinkActivity activity) {

super(activity);

mRenderer = new SceneRenderer();//创建场景渲染器

setRenderer(mRenderer); //设置渲染器

setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染

}

private class SceneRenderer implements GLSurfaceView.Renderer

{

DrawAnimal drawAnimal;

public void onDrawFrame(GL10 gl) {

gl.glShadeModel(GL10.GL_SMOOTH);//着色模式为平滑着色

gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);//清除颜色缓冲区及深度缓冲区

gl.glMatrixMode(GL10.GL_MODELVIEW);//设置矩阵为模式矩阵

gl.glLoadIdentity(); //设置当前矩阵为单位矩阵

gl.glTranslatef(0f, 0f, -2.0f); //调整Z轴,可以调整图像显示的大小

drawAnimal.draw(gl,1,0,0); //在这里绘制需要显示的素材对象

}

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

gl.glViewport(0, 0, width, height); //设置当前矩阵为投影矩阵

gl.glMatrixMode(GL10.GL_PROJECTION); //设置当前矩阵为单位矩阵

gl.glLoadIdentity(); //计算透视投影的比例

float ratio = (float) width / height; //调用此方法计算产生透视投影矩阵

gl.glFrustumf(-ratio, ratio, -1, 1, 1, 100);

}

public void onSurfaceCreated(GL10 gl, EGLConfig config) {

gl.glDisable(GL10.GL_DITHER);//关闭抗抖动

gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_FASTEST);//设置特定Hint项目的模式,这里为设置为使用快速模式

gl.glClearColor(0,0,0,0); //设置屏幕背景色黑色RGBA

gl.glShadeModel(GL10.GL_SMOOTH); //设置着色模型为平滑着色

gl.glEnable(GL10.GL_DEPTH_TEST);//启用深度测试

animalTextureId = initTexture(gl, R.drawable.animal);//初始化纹理对象

drawAnimal = new DrawAnimal(animalTextureId);//参加动物素材对象

}

}

public int initTexture(GL10 gl, int drawableId)

{

int[] textures = new int[1];

gl.glGenTextures(1, textures, 0);

int currTextureId = textures[0];

gl.glBindTexture(GL10.GL_TEXTURE_2D, currTextureId);

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_CLAMP_TO_EDGE);//指定S坐标轴贴图模式

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);//指定T坐标轴贴图模式

InputStream is = this.getResources().openRawResource(drawableId);

Bitmap bitmapTmp;

try{

bitmapTmp = BitmapFactory.decodeStream(is);

}

finally{

try{

is.close();

}

catch (IOException e) {

e.printStackTrace();

}

}

GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmapTmp, 0);

bitmapTmp.recycle();

return currTextureId;

}

}

以上实现了游戏框架的主要内容。今后的课程,都会基于这个基本框架来设计代码。

1.3 DrawAnimal 类

本课程专门设计一系列的渲染类,用于对特定对象或场景的绘制,凡是Draw打头的类,都是渲染类。后续课程还会陆续提到更多的渲染类。所有渲染类,都集中放在package elong.CrazyLink.Draw包中。

渲染类的核心用途就是将一个对象或动作抽象出来,通过渲染类对象,实现特定对象或动作的绘制。在后续的应用中,我们会将多个渲染类合并一起使用,以达到显示特定场景或特效的目的。

每一个渲染类都会有一个draw方法,该方法实现对特定对象或场景的绘制。要实现一个新的场景或效果,您只需用设计好渲染类的draw方法即可。

DrawAnimal 类的代码请参考附件。部分代码在下节还会有讲解。

2、绘制第一个图案

我们要绘制的第一个图案效果如下图,这是素材图片animal.png中7个素材中的其中一个。

寨子腾讯“爱消除”游戏7日教程

2.1基本原理

很显然,我们要绘制的是一张正方形的图片,通过纹理贴图,可以很容易地实现。需要注意的是,在OpenGL ES中,并没用提供绘制正方形的操作,绘制一个正方形,需要转换成绘制两个三角形。

这里需要关注的一个技术细节是背面裁剪,这是OpenGL ES的一项功能,含义是,打开此功能后,视角在三角形的背面时不渲染此三角形(即无法看到此三角形),该功能可以提高渲染的效率。

因此,我们需要保证在观察方向上渲染三角形,否则就有可能会看不到所绘制的图像。很多初学者经常会遇到这个问题,如果您绘制的三角形没有按预期出现,您可能需要检查是不是这个原因引起的。

以下是确定一个三角形正反面的方法:通常情况下,当面对一个三角形时,如果顶点的顺序是逆时针的,则位于三角形的正面;反之就是反面。

如下图所示:

寨子腾讯“爱消除”游戏7日教程

2.2顶点坐标数据

知道了这个原理之后,我们就可以设计正方形的顶点数据了,如下就是顶点坐标数据的定义:

int vertices[]=new int[]//顶点坐标数据数组

{

-32*UNIT_SIZE,32*UNIT_SIZE,0,

-32*UNIT_SIZE,-32*UNIT_SIZE,0,

32*UNIT_SIZE,-32*UNIT_SIZE,0,

32*UNIT_SIZE,-32*UNIT_SIZE,0,

32*UNIT_SIZE,32*UNIT_SIZE,0,

-32*UNIT_SIZE,32*UNIT_SIZE,0

};

附件的代码中还根据显示位置(col,row)计算了偏移量

int deltaX = ((col-3)*64*UNIT_SIZE);

int deltaY = ((row-3)*64*UNIT_SIZE);

为了清晰起见,这里的定义没有加上偏移量deltaX及deltaY。

注意:顶点坐标数据采用的笛卡尔坐标系,其坐标值得范围是任意的;而纹理顶点数据所采用的S-T坐标系,其坐标取值范围是0.0~1.0

2.3 纹理顶点坐标数据

对应的,我们需要为正方形的每个顶点设置对应的纹理顶点坐标,如下定义:

float textureCoors[]=new float[]//顶点纹理S、T坐标值数组

{

(witch - 1) * textureRatio,0,

(witch - 1) * textureRatio,1,

witch * textureRatio,1,

witch * textureRatio,1,

witch * textureRatio,0,

(witch - 1) * textureRatio,0

};

值得一提的是,由于我们将7个动物素材集中在一张图片(animal.png)中加载进来(为了满足OpenGL ES对像素的要求,实际空出了一个素材的位置,在OpenGL ES中进行纹理映射时对纹理图片的尺寸是有要求的,纹理图片的宽度和高度必须为2n (2的n次方),即32x32,256x512等。)

而实际显示的时候,我们仅想显示其中的一个素材,因此引入了一个变量textureRatio,该变量的值是textureRatio = (float)(1/8.0f),用来精确控制每个动物素材(witch:有效地范围为1~7)对应的纹理坐标。通过传入不同的witch可以渲染不同的对象。

如下图示:

寨子腾讯“爱消除”游戏7日教程

2.4 渲染类的核心方法draw

最后,介绍一下DrawAnimal类中得draw方法。在后续的课程中,用DrawXxxx命名的类都是用来渲染某个特定场景的,这种类中都会有一个公有的方法draw。

DrawAnimal中的draw方法是这样定义的:

public void draw(GL10 gl, int witch, int col, int row)

该方法可以将witch素材绘制在x=col, y=row的位置。

在这里,我们把draw方法的实现贴出来,具体含义直接参考代码的注释。在后续的课程当中,只会将新提到的知识点将代码贴出。完整的代码请参考对应的附件,不再嗦。

public void draw(GL10 gl, int witch, int col, int row)

{

initVertexBuffer(col, row); //根据col,row初始化顶点坐标

initTextureBuffer(witch); //根据witch来初始化纹理顶点数据

//gl.glTranslatef(col * textureRatio, row * textureRatio, 0);//在x=col,y=row的位置绘制选定的素材对象

//顶点坐标,允许使用顶点数组

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

//为画笔指定顶点坐标数据

gl.glVertexPointer

(

3, //每个顶点的坐标数量为3 xyz

GL10.GL_FIXED, //顶点坐标值的类型为 GL_FIXED

0, //连续顶点坐标数据之间的间隔

mVertexBuffer //顶点坐标数据

);

//纹理坐标,开启纹理

gl.glEnable(GL10.GL_TEXTURE_2D);

//允许使用纹理数组

gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

//为画笔指定纹理uv坐标数据

gl.glTexCoordPointer

(

2, //每个顶点两个纹理坐标数据 S、T

GL10.GL_FLOAT, //数据类型

0, //连续纹理坐标数据之间的间隔

mTextureBuffer //纹理坐标数据

);

gl.glBindTexture(GL10.GL_TEXTURE_2D,textureId);//为画笔绑定指定名称ID纹理

//绘制图形

gl.glDrawArrays

(

GL10.GL_TRIANGLES,

0,

vCount

);

gl.glDisable(GL10.GL_TEXTURE_2D);//关闭纹理

}

2.5考虑字节序的问题

这里额外强调的一点是:不同平台其字节序有可能不同,如果数据单元不是字节的,就一定要经过ByteBuffer进行转换,转换的关键就是要通过ByteOrder设置为nativeOrder(),以适应对应平台的字节序,否则就有可能会出现问题。

ByteBuffer cbb = ByteBuffer.allocateDirect(textureCoors.length*4);

cbb.order(ByteOrder.nativeOrder());//设置本地字节顺序

读书人网 >移动开发

热点推荐