一、准备工作
首先,实现爆炸类:
package com.game.view; import static com.game.common.Constant.bomb_width; import static com.game.common.Constant.initTexture; import android.opengl.GLES20; import android.util.Log; import com.game.activity.R; import com.game.common.MatrixState; import com.game.role.RoleTexRect; import com.game.thread.GameThread; import com.game.view.MySurfaceView; public class bomb { public GameThread gameThread; MySurfaceView gameView; public int bombTextId_1;//爆炸效果图片 public static RoleTexRect bombRect;//爆炸矩形范围 public static boolean isDrawbomb;//视图中是否绘制爆炸图 public static float baozha_ratio;//爆炸图的缩放比例 public bomb(MySurfaceView gameView) { this.gameView = gameView; } public void iniBomb() { setBombTextId_1(); startGamethread(); bombRect=new RoleTexRect(this.gameView,1.5f,bomb_width,bomb_width); } private void setBombTextId_1(){ this.bombTextId_1=initTexture( this.gameView.getResources(),R.drawable.baozaoxiaoguo,false);//爆炸效果图片 } public int getBombTextId_1(){ return bombTextId_1; } private void startGamethread(){ gameThread = new GameThread(); gameThread.start(); Log.i("zebin", "启动游戏线程。初始化完毕"); } //绘制炸弹 x,y,z 为炸弹的三维坐标 public void drawBomb(float x,float y,float z){ GLES20.glEnable(GLES20.GL_BLEND); GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); Log.i("zebin:","绘制爆炸的正交投影完毕"+isDrawbomb); if(isDrawbomb)//这里绘制导弹下落后的爆炸效果图 { MatrixState.pushMatrix(); Log.i("zebin:","绘制爆炸效果"+x+" "+y+" "+z); MatrixState.translate(x,y,z);//在这里输入爆炸的坐标位置。 MatrixState.scale(baozha_ratio, baozha_ratio,1); bombRect.drawSelf(bombTextId_1); MatrixState.popMatrix(); } GLES20.glDisable(GLES20.GL_BLEND); } }
接着,矩形类(fanz版):
package com.game.role; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.opengl.GLES20; import android.opengl.GLUtils; import com.game.common.MatrixState; import com.game.common.ShaderUtil; import com.game.view.MySurfaceView; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; //表示纹理矩形的类(含法线贴图) public class RoleTexRect { int mProgram;//自定义渲染管线程序id int muMVPMatrixHandle;//总变换矩阵引用id int muMMatrixHandle;//位置、旋转变换矩阵 int maCameraHandle; //摄像机位置属性引用id int maPositionHandle; //顶点位置属性引用id int maTexCoorHandle; //顶点纹理坐标属性引用id int uTexHandle;//外观纹理属性引用id String mVertexShader;//顶点着色器 String mFragmentShader;//片元着色器 FloatBuffer mVertexBuffer;//顶点坐标数据缓冲 FloatBuffer mTextureBuffer;//顶点纹理坐标数据缓冲 int vCount=0; public RoleTexRect(){ } public RoleTexRect(MySurfaceView mv,float size,float width,float height) { //初始化顶点坐标与着色数据 initVertexData(size,width,height); //初始化shader intShader(mv); } //初始化顶点坐标与纹理数据的方法 public void initVertexData(float UNIT_SIZE,float width,float height) { //顶点坐标数据的初始化================begin============================ vCount=6; float vertices[]=new float[] { -width*UNIT_SIZE,height*UNIT_SIZE,1f, -width*UNIT_SIZE,-height*UNIT_SIZE,1f, width*UNIT_SIZE,height*UNIT_SIZE,1f, -width*UNIT_SIZE,-height*UNIT_SIZE,1f, width*UNIT_SIZE,-height*UNIT_SIZE,1f, width*UNIT_SIZE,height*UNIT_SIZE,1f }; //创建顶点坐标数据缓冲 //vertices.length*4是因为一个整数四个字节 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4); vbb.order(ByteOrder.nativeOrder());//设置字节顺序 mVertexBuffer = vbb.asFloatBuffer();//转换为int型缓冲 mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据 mVertexBuffer.position(0);//设置缓冲区起始位置 //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题 //顶点坐标数据的初始化================end============================ //顶点纹理数据的初始化================begin============================ float textures[]=new float[] { 0,0,0,1,1,0, 0,1,1,1,1,0 }; //创建顶点纹理数据缓冲 ByteBuffer tbb = ByteBuffer.allocateDirect(textures.length*4); tbb.order(ByteOrder.nativeOrder());//设置字节顺序 mTextureBuffer= tbb.asFloatBuffer();//转换为Float型缓冲 mTextureBuffer.put(textures);//向缓冲区中放入顶点着色数据 mTextureBuffer.position(0);//设置缓冲区起始位置 //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题 //顶点纹理数据的初始化================end============================ } //初始化shader public void intShader(MySurfaceView mv) { //加载顶点着色器的脚本内容 mVertexShader= ShaderUtil.loadFromAssetsFile("role/vertex_botton.sh", mv.getResources()); ShaderUtil.checkGlError("==ss=="); //加载片元着色器的脚本内容 mFragmentShader= ShaderUtil.loadFromAssetsFile("role/frag_botton.sh", mv.getResources()); //基于顶点着色器与片元着色器创建程序 ShaderUtil.checkGlError("==ss=="); mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader); //获取程序中顶点位置属性引用id maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); //获取程序中顶点经纬度属性引用id maTexCoorHandle=GLES20.glGetAttribLocation(mProgram, "aTexCoor"); //获取程序中总变换矩阵引用id muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); //获取位置、旋转变换矩阵引用id } public void drawSelf(int texId) { //制定使用某套shader程序 GLES20.glUseProgram(mProgram); //将最终变换矩阵传入shader程序 GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0); //为画笔指定顶点位置数据 GLES20.glVertexAttribPointer ( maPositionHandle, 3, GLES20.GL_FLOAT, false, 3*4, mVertexBuffer ); //为画笔指定顶点纹理坐标数据 GLES20.glVertexAttribPointer ( maTexCoorHandle, 2, GLES20.GL_FLOAT, false, 2*4, mTextureBuffer ); //允许顶点位置数据数组 GLES20.glEnableVertexAttribArray(maPositionHandle); GLES20.glEnableVertexAttribArray(maTexCoorHandle); //绑定纹理 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId); GLES20.glUniform1i(uTexHandle, 0); //绘制三角形 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount); } public static int initTexture(MySurfaceView mv,int drawableId)// textureId { // 生成纹理ID int[] textures = new int[1]; GLES20.glGenTextures(1, // 产生的纹理id的数量 textures, // 纹理id的数组 0 // 偏移量 ); int textureId = textures[0]; GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); // 通过输入流加载图片===============begin=================== InputStream is = mv.getResources().openRawResource(drawableId); Bitmap bitmapTmp; try { bitmapTmp = BitmapFactory.decodeStream(is); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } // 通过输入流加载图片===============end===================== // 实际加载纹理 GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, // 纹理类型,在OpenGL // ES中必须为GL10.GL_TEXTURE_2D 0, // 纹理的层次,0表示基本图像层,可以理解为直接贴图 bitmapTmp, // 纹理图像 0 // 纹理边框尺寸 ); bitmapTmp.recycle(); // 纹理加载成功后释放图片 return textureId; } }其他工具类:
1、MatrixState
package com.game.common; import android.opengl.Matrix; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.Stack; //存储系统矩阵状态的类 public class MatrixState { private static float[] mProjMatrix = new float[16];//4x4矩阵 投影用 static float[] mVMatrix = new float[16];//摄像机位置朝向9参数矩阵 static float[] mMVPMatrix;//最后起作用的总变换矩阵 public static float[] lightLocationRed=new float[]{0,0,0};//红色定位光光源位置 public static float[] lightLocationGreenBlue=new float[]{0,0,0};//天蓝色定位光光源位置 public static FloatBuffer cameraFB; public static FloatBuffer lightPositionFBRed; public static FloatBuffer lightPositionFBGreenBlue; public static float[] lightLocation=new float[]{0,0,0};//定位光光源位置 public static FloatBuffer lightPositionFB; //设置摄像机 public static void setCamera ( float cx, //摄像机位置x float cy, //摄像机位置y float cz, //摄像机位置z float tx, //摄像机目标点x float ty, //摄像机目标点y float tz, //摄像机目标点z float upx, //摄像机UP向量X分量 float upy, //摄像机UP向量Y分量 float upz //摄像机UP向量Z分量 ) { Matrix.setLookAtM ( mVMatrix, 0, cx, cy, cz, tx, ty, tz, upx, upy, upz ); float[] cameraLocation=new float[3];//摄像机位置 cameraLocation[0]=cx; cameraLocation[1]=cy; cameraLocation[2]=cz; //摄像机位置矩阵 ByteBuffer llbb = ByteBuffer.allocateDirect(3*4); llbb.order(ByteOrder.nativeOrder());//设置字节顺序 cameraFB=llbb.asFloatBuffer(); cameraFB.put(cameraLocation); cameraFB.position(0); } //设置透视投影参数 public static void setProjectFrustum ( float left, //near面的left float right, //near面的right float bottom, //near面的bottom float top, //near面的top float near, //near面距离 float far //far面距离 ) { Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far); } //设置正交投影参数 public static void setProjectOrtho ( float left, //near面的left float right, //near面的right float bottom, //near面的bottom float top, //near面的top float near, //near面距离 float far //far面距离 ) { Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far); } //获取具体物体的总变换矩阵 public static float[] getFinalMatrix() { float[] mMVPMatrix=new float[16]; Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, currMatrix, 0); Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0); return mMVPMatrix; } //设置灯光位置的方法 public static void setLightLocation(float x,float y,float z) { lightLocation[0]=x; lightLocation[1]=y; lightLocation[2]=z; ByteBuffer llbb = ByteBuffer.allocateDirect(3*4); llbb.order(ByteOrder.nativeOrder());//设置字节顺序 lightPositionFB=llbb.asFloatBuffer(); lightPositionFB.put(lightLocation); lightPositionFB.position(0); } //设置红色灯光位置的方法 public static void setLightLocationRed(float x,float y,float z) { lightLocationRed[0]=x; lightLocationRed[1]=y; lightLocationRed[2]=z; ByteBuffer llbb = ByteBuffer.allocateDirect(3*4); llbb.order(ByteOrder.nativeOrder());//设置字节顺序 lightPositionFBRed=llbb.asFloatBuffer(); lightPositionFBRed.put(lightLocationRed); lightPositionFBRed.position(0); } //设置天蓝色灯光位置的方法 public static void setLightLocationGreenBlue(float x,float y,float z) { lightLocationGreenBlue[0]=x; lightLocationGreenBlue[1]=y; lightLocationGreenBlue[2]=z; ByteBuffer llbb = ByteBuffer.allocateDirect(3*4); llbb.order(ByteOrder.nativeOrder());//设置字节顺序 lightPositionFBGreenBlue=llbb.asFloatBuffer(); lightPositionFBGreenBlue.put(lightLocationGreenBlue); lightPositionFBGreenBlue.position(0); } public static Stack<float[]> mStack=new Stack<float[]>();//保护变换矩阵的栈 static float[] currMatrix;//当前变换矩阵 public static void setInitStack()//获取不变换初始矩阵 { currMatrix=new float[16]; Matrix.setRotateM(currMatrix, 0, 0, 1, 0, 0); } public static void pushMatrix()//保护变换矩阵 { mStack.push(currMatrix.clone()); } public static void popMatrix()//恢复变换矩阵 { currMatrix=mStack.pop(); } public static void translate(float x,float y,float z)//设置沿xyz轴移动 { Matrix.translateM(currMatrix, 0, x, y, z); } public static void rotate(float angle,float x,float y,float z)//设置绕xyz轴移动 { Matrix.rotateM(currMatrix,0,angle,x,y,z); } public static void scale(float x,float y,float z){ Matrix.scaleM(currMatrix, 0, x, y, z); } //插入自带矩阵 public static void matrix(float[] self) { float[] result=new float[16]; Matrix.multiplyMM(result,0,currMatrix,0,self,0); currMatrix=result; } public static float[] getMMatrix() { return currMatrix; } //用于一帧内的摄像机矩阵 private static float[] mVMatrixForSpecFrame = new float[16];//摄像机位置朝向9参数矩阵 public static void copyMVMatrix() { for(int i=0;i<16;i++) { mVMatrixForSpecFrame[i]=mVMatrix[i]; } } }2、ShaderUtil
package com.game.common; import android.content.res.Resources; import android.opengl.GLES20; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.InputStream; //加载顶点Shader与片元Shader的工具类 public class ShaderUtil { //加载制定shader的方法 public static int loadShader ( int shaderType, //shader的类型 GLES20.GL_VERTEX_SHADER GLES20.GL_FRAGMENT_SHADER String source //shader的脚本字符串 ) { //创建一个新shader int shader = GLES20.glCreateShader(shaderType); //若创建成功则加载shader if (shader != 0) { //加载shader的源代码 GLES20.glShaderSource(shader, source); //编译shader GLES20.glCompileShader(shader); //存放编译成功shader数量的数组 int[] compiled = new int[1]; //获取Shader的编译情况 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) {//若编译失败则显示错误日志并删除此shader Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":"); Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader = 0; } } return shader; } //创建shader程序的方法 public static int createProgram(String vertexSource, String fragmentSource) { //加载顶点着色器 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); if (vertexShader == 0) { return 0; } //加载片元着色器 int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); if (pixelShader == 0) { return 0; } //创建程序 int program = GLES20.glCreateProgram(); //若程序创建成功则向程序中加入顶点着色器与片元着色器 if (program != 0) { //向程序中加入顶点着色器 GLES20.glAttachShader(program, vertexShader); checkGlError("glAttachShader"); //向程序中加入片元着色器 GLES20.glAttachShader(program, pixelShader); checkGlError("glAttachShader"); //链接程序 GLES20.glLinkProgram(program); //存放链接成功program数量的数组 int[] linkStatus = new int[1]; //获取program的链接情况 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); //若链接失败则报错并删除程序 if (linkStatus[0] != GLES20.GL_TRUE) { Log.e("ES20_ERROR", "Could not link program: "); Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); program = 0; } } return program; } //检查每一步操作是否有错误的方法 public static void checkGlError(String op) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { Log.e("ES20_ERROR", op + ": glError " + error); throw new RuntimeException(op + ": glError " + error); } } //从sh脚本中加载shader内容的方法 public static String loadFromAssetsFile(String fname,Resources r) { String result=null; try { InputStream in=r.getAssets().open(fname); int ch=0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while((ch=in.read())!=-1) { baos.write(ch); } byte[] buff=baos.toByteArray(); baos.close(); in.close(); result=new String(buff,"UTF-8"); result=result.replaceAll("\\r\\n","\n"); } catch(Exception e) { e.printStackTrace(); } return result; } }
二、实现
在MySurfaceView.Java中
初始化:bomb bomb = new bomb(MySurfaceView.this);
在某处将爆炸设为true:isDrawbomb = true;
在onSurfaceCreated()初始化爆炸的素材: bomb.iniBomb();
在onDrawFrame()范围内绘画炸弹:
MatrixState.pushMatrix();
bomb.drawBomb(1,0,1);
MatrixState.popMatrix();
其中输入的参数为其x,y,z坐标。