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


【前言】

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

 

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

 

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

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

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

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

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

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

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


山寨腾讯“爱消除”游戏之奖励特效

山寨腾讯“爱消除”游戏之自动提示特效

山寨腾讯“爱消除”游戏之交换特效优化

山寨腾讯“爱消除”游戏之声音效果

山寨腾讯“爱消除”游戏之生命特效

山寨腾讯“爱消除”游戏之获得生命特效

山寨腾讯“爱消除”游戏之失去生命特效


【广而告之】山寨腾讯“爱消除”游戏已经正式入驻GitHub,项目地址是:https://github.com/3125788/CrazyLink

欢迎广大寨友一起来完善这个游戏。




 

第一天

【课程内容】

今天我们将基于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.png7个素材中的其中一个。

 

 

 

2.1基本原理

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

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

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

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

如下图所示:

 

 

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

为了清晰起见,这里的定义没有加上偏移量deltaXdeltaY

注意:顶点坐标数据采用的笛卡尔坐标系,其坐标值得范围是任意的;而纹理顶点数据所采用的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 (2n次方),即32x32256x512等。

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

如下图示:

 

 

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());//设置本地字节顺序

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值