在山寨腾讯“爱消除”游戏项目中,我们使用了两种简单的屏幕拾取技术,这在第五天的教程及山寨腾讯“爱消除”游戏之菜单特效里已经有介绍。
很显然,对于一些有复杂不规则图案,或有层次感的场面,上面的技术并不能满足要求。今天我们将介绍另外一种屏幕拾取的方法。
如下图所示,这个是《教你玩魔方》游戏里的一个场景。
显然,由于以下的原因,用前面介绍的两种技术将很难完成准确的屏幕拾取。
1、中间的魔方体是可以随意缩放的;
2、魔方的每一个面都是可以转动的;
3、每个面在转动过程中,位置都是随时变化的;
4、转动结束后,面上每一个方块都会发生变化;
5、每个方块都有6个面,有些面是可见的,有些面是不可见的,触摸到不同的面,产生的动作效果应该是不一样的。
好,下面详细地看一下DisplaySingleCube函数,该函数分别将方块的6个面渲染出来。
1、DisplaySingleCube用于显示一个小方块,一个完整的魔方需要调用27次这个函数,最后一个参数cubeIndex指定了方块的索引(0~26)
2、DisplaySingleCube函数中的渲染操作分为DRAW和SELECT_PLATE两种模式,在屏幕拾取时,要将模式设置为SELECT_PLATE
3、DRAW模式下,使用纹理渲染;SELECT_PLATE模式下,使用色彩渲染,每个面分别渲染出不同的颜色,详细看下面代码的注释;根据选定的颜色,就可以知道触摸到的是哪一个面了。 //显示单个方块
private void DisplaySingleCube(ST_DreamCube dreamcube, float fX, float fY,float fZ,float fRX,float fRY,float fRZ, int cubeIndex)
{
E_WORKMODE eMode = GetGLMode();
E_COLOR cU, cD, cF, cB, cL, cR;
cU = dreamcube.cube_status[cubeIndex].Color.U;
cD = dreamcube.cube_status[cubeIndex].Color.D;
cF = dreamcube.cube_status[cubeIndex].Color.F;
cB = dreamcube.cube_status[cubeIndex].Color.B;
cL = dreamcube.cube_status[cubeIndex].Color.L;
cR = dreamcube.cube_status[cubeIndex].Color.R;
BindVertexAndTexture(cubeBuff, cubetexBuff);
mGl.glLoadIdentity(); //可不要
GLU.gluLookAt(mGl,
-5.0f, 5.0f, 5.0f,
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f);
mGl.glScalef(mCfg.cubeConfig.fZoomFactor, mCfg.cubeConfig.fZoomFactor, mCfg.cubeConfig.fZoomFactor);
mGl.glTranslatef((float)0.3, (float)0.3-1, (float)-0.3);
mGl.glFrontFace(GL10.GL_CCW);
if (mCube.fYDegree != 0)
{
mGl.glRotatef(mCube.fYDegree, 0.0f, 1.0f, 0.0f);
}
if (fRX != 0 || fRY != 0 || fRZ != 0)
{
mGl.glRotatef(fRotate, fRX, fRY, fRZ);
}
mGl.glTranslatef(fX, fY, fZ);
// FRONT AND BACK
if (eMode == E_WORKMODE.DRAW)
{
mGl.glBindTexture(GL10.GL_TEXTURE_2D, texture[cF.ordinal()]);
}
else if (eMode == E_WORKMODE.SELECT_PLATE)
{
mGl.glColor4f(0, 0, 1, 1); //RGB(0,0,1) 蓝
}
mGl.glNormal3f(0.0f, 0.0f, 1.0f);
mGl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
if (eMode == E_WORKMODE.DRAW)
{
mGl.glBindTexture(GL10.GL_TEXTURE_2D, texture[cB.ordinal()]);
}
else if (eMode == E_WORKMODE.SELECT_PLATE)
{
mGl.glColor4f(0, 1, 0, 1); //RGB(0,1,0) 绿
}
mGl.glNormal3f(0.0f, 0.0f, -1.0f);
mGl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4);
// LEFT AND RIGHT
if (eMode == E_WORKMODE.DRAW)
{
mGl.glBindTexture(GL10.GL_TEXTURE_2D, texture[cL.ordinal()]);
}
else if (eMode == E_WORKMODE.SELECT_PLATE)
{
mGl.glColor4f(0, 1, 1, 1); //RGB(0,0,1) 蓝+绿
}
mGl.glNormal3f(-1.0f, 0.0f, 0.0f);
mGl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8, 4);
if (eMode == E_WORKMODE.DRAW)
{
mGl.glBindTexture(GL10.GL_TEXTURE_2D, texture[cR.ordinal()]);
}
else if (eMode == E_WORKMODE.SELECT_PLATE)
{
mGl.glColor4f(1, 0, 0, 1); //RGB(0,0,1) 红
}
mGl.glNormal3f(1.0f, 0.0f, 0.0f);
mGl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12, 4);
// TOP AND BOTTOM
if (eMode == E_WORKMODE.DRAW)
{
mGl.glBindTexture(GL10.GL_TEXTURE_2D, texture[cU.ordinal()]);
}
else if (eMode == E_WORKMODE.SELECT_PLATE)
{
mGl.glColor4f(1, 0, 1, 1); //RGB(0,0,1) 红+蓝
}
mGl.glNormal3f(0.0f, 1.0f, 0.0f);
mGl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);
if (eMode == E_WORKMODE.DRAW)
{
mGl.glBindTexture(GL10.GL_TEXTURE_2D, texture[cD.ordinal()]);
}
else if (eMode == E_WORKMODE.SELECT_PLATE)
{
mGl.glColor4f(1, 1, 0, 1); //RGB(0,0,1) 红+绿
}
mGl.glNormal3f(0.0f, -1.0f, 0.0f);
mGl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);
}
接下来介绍选择物体的函数SelectObj,参数是手指触碰到屏幕的坐标值。
1、在进入SelectObj函数之前,需要将模式设置为PICK,这样DisplaySingleCube函数内部的DRAW及SELECT_PLATE将不起作用;
2、在绘制每一个小方块之前,调用glColor4f给这个方块设置颜色,每个方块都会设置成不同的颜色,详细看代码注释;
3、我们用(0,0,1)~(1,1,1)来表示不同的颜色,最多只能7种颜色,那么如何用7种颜色来识别27个方块呢?我们采用分组的方法来做到这一点的,详细见代码;
//对象选择操作
E_SELECTION SelectObj(int x, int y)
{
E_SELECTION eObj = E_SELECTION.SELE_INVALID;
int iObj = 0;
InitViewPortMain();
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
{
mCube.XChgDreamCube4Display(mCube.DREAM_CUBE_CACULATE);
mGl.glColor4f(0, 0, 1, 1); // RGB(0,0,1) 蓝色
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 24);
mGl.glColor4f(0, 1, 0, 1); // RGB(0,1,0) 绿色
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 25);
mGl.glColor4f(0, 1, 1, 1); // RGB(0,1,1) 绿色+蓝色
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 26);
mGl.glColor4f(1, 0, 0, 1); // RGB(1,0,0) 红色
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 15);
mGl.glColor4f(1, 0, 1, 1); // RGB(1,0,1) 红色+蓝色
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 16);
mGl.glColor4f(1, 1, 0, 1); // RGB(1,1,0) 红色+绿色
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 17);
iObj = GetObj(x, y); //分组1
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_BLK24;
break;
case 2:
eObj = E_SELECTION.SELE_BLK25;
break;
case 3:
eObj = E_SELECTION.SELE_BLK26;
break;
case 4:
eObj = E_SELECTION.SELE_BLK15;
break;
case 5:
eObj = E_SELECTION.SELE_BLK16;
break;
case 6:
eObj = E_SELECTION.SELE_BLK17;
break;
// default:
// eObj = -1;
// break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
mGl.glColor4f(0, 0, 1, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 6);
mGl.glColor4f(0, 1, 0, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 7);
mGl.glColor4f(0, 1, 1, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 8);
mGl.glColor4f(1, 0, 0, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 23);
mGl.glColor4f(1, 0, 1, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 14);
mGl.glColor4f(1, 1, 0, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5);
iObj = GetObj(x, y); //分组2
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_BLK6;
break;
case 2:
eObj = E_SELECTION.SELE_BLK7;
break;
case 3:
eObj = E_SELECTION.SELE_BLK8;
break;
case 4:
eObj = E_SELECTION.SELE_BLK23;
break;
case 5:
eObj = E_SELECTION.SELE_BLK14;
break;
case 6:
eObj = E_SELECTION.SELE_BLK5;
break;
// default:
// eObj = -1;
// break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
mGl.glColor4f(0, 0, 1, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 2);
mGl.glColor4f(0, 1, 0, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 11);
mGl.glColor4f(0, 1, 1, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 18);
mGl.glColor4f(1, 0, 0, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 19);
mGl.glColor4f(1, 0, 1, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 20);
mGl.glColor4f(1, 1, 0, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 21);
mGl.glColor4f(1, 1, 1, 1);
DisplaySingleCube(mCube.DREAM_CUBE_DISPLAY, 0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 22);
iObj = GetObj(x, y); //分组3
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_BLK2;
break;
case 2:
eObj = E_SELECTION.SELE_BLK11;
break;
case 3:
eObj = E_SELECTION.SELE_BLK18;
break;
case 4:
eObj = E_SELECTION.SELE_BLK19;
break;
case 5:
eObj = E_SELECTION.SELE_BLK20;
break;
case 6:
eObj = E_SELECTION.SELE_BLK21;
break;
case 7:
eObj = E_SELECTION.SELE_BLK22;
break;
// default:
// eObj = -1;
// break;
}
return eObj;
}
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
ShowUnseenPlate(mCube.DREAM_CUBE_CACULATE);
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_LEFT;
break;
case 2:
eObj = E_SELECTION.SELE_RIGHT;
break;
case 3:
eObj = E_SELECTION.SELE_DOWN;
break;
// default:
// eObj = E_Selection.SELE_INVALID;
// break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
ShowLButton();
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_MENU;
break;
case 2:
eObj = E_SELECTION.SELE_HELP;
break;
case 3:
eObj = E_SELECTION.SELE_ZOOMOUT;
break;
case 4:
eObj = E_SELECTION.SELE_DISRUPT;
break;
case 5:
eObj = E_SELECTION.SELE_AUTOPLAY;
break;
case 6:
eObj = E_SELECTION.SELE_RESTART;
break;
case 7:
eObj = E_SELECTION.SELE_STUDY;
break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
ShowRButton();
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_ZOOMIN;
break;
case 2:
eObj = E_SELECTION.SELE_SETUP;
break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
ShowProgressStars();
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_STAR;
break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
ShowCubeTip();
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.TIP_0;
break;
case 2:
eObj = E_SELECTION.TIP_1;
break;
case 3:
eObj = E_SELECTION.TIP_2;
break;
case 4:
eObj = E_SELECTION.TIP_3;
break;
case 5:
eObj = E_SELECTION.TIP_4;
break;
case 6:
eObj = E_SELECTION.TIP_5;
break;
case 7:
eObj = E_SELECTION.TIP_6;
break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
ShowTime(miCubeClockSec);
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_TIMER;
break;
}
return eObj;
}
mGl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
ShowStep(mCube.GetStepBar());
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_STEP;
break;
}
return eObj;
}
return eObj;
}
下面介绍获取对象函数GetObj:
1、根据传入的坐标值,获取指定坐标出的色彩信息;
2、将色彩信息转换为1~7范围表示的数据,然后返回该信息;
3、为了保证获取指定像素处的颜色信息准确,我们仅适用简单颜色,而且没有适用ALPHA信息。如果适用较复杂的颜色,有可能会造成识别不准确。缺点是一次能表示的物体数量很少,当有很多物体需要识别时,需要进行分组。
//获取对象
private int GetObj(int x, int y)
{
mGl.glFinish();
mGl.glFinish();
Delay(miSelectDelay);
ByteBuffer pixel = ByteBuffer.allocate(4);
byte data[] = new byte[4];
mGl.glReadPixels(x , iScreenHeight - y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixel);
data = pixel.array();
data[0] = (byte) Math.abs(data[0]);
data[1] = (byte) Math.abs(data[1]);
data[2] = (byte) Math.abs(data[2]);
data[0] = (byte) (data[0] << 2);
data[1] = (byte) (data[1] << 1);
int iObj = data[0] + data[1] + data[2];
return iObj;
}
调用GetObj得到颜色信息(1~7),根据该信息,我们可以确定手指触摸到的是哪个方块了:
iObj = GetObj(x, y);
if(iObj != 0)
{
switch(iObj)
{
case 1:
eObj = E_SELECTION.SELE_BLK24;
break;
case 2:
eObj = E_SELECTION.SELE_BLK25;
break;
case 3:
eObj = E_SELECTION.SELE_BLK26;
break;
case 4:
eObj = E_SELECTION.SELE_BLK15;
break;
case 5:
eObj = E_SELECTION.SELE_BLK16;
break;
case 6:
eObj = E_SELECTION.SELE_BLK17;
break;
// default:
// eObj = -1;
// break;
}
return eObj;
}
当触摸事件发生时,我们做了以下事情:
1、将模式设置为PICK
2、用不同颜色来渲染魔方体的各个方块,并拾取到手指触摸处的方块对象
3、将模式设置为DRAW模式
4、按正常的纹理渲染模式,将魔方体完整地渲染出来
case TOUCH:
mCubeGL.SetGLMode(E_WORKMODE.PICK);
GetTouchObj();
mCubeGL.SetGLMode(E_WORKMODE.DRAW);
mCubeGL.DisplayCube();
break;
采用这种方法,只有模式为DRAW的图案会最终显示出来,PICK或SELECT_PLATE模式下的图案是不会显示的,因此,你不需要担心PICK模式下会破坏整体的渲染效果。
好了,完整的代码请参考我的另一个开源项目《教你玩魔方》
项目地址在GITHUB,欢迎你FORK.
https://github.com/3125788/CrazyCube3D