上一篇简单的实现了AngryBird的发射和碰撞检测功能,下面着手解决屏幕的缩放功能以及小鸟发射后镜头跟随的效果。
先上成品截图:
1.zoom in状态
2.zoom out状态
3.小鸟发射后镜头跟随
AngryBird中有一个很有特色的功能就是zoom in/out功能,正是通过屏幕的缩放,使得我们在游戏时既能統览全局,又能近距离观察游戏细节,所以既然是要向AngryBird致敬,zoom in/out的功能是一定要实现下的。
所谓zoom in/out,就是要对canvas画布进行操作,利用canvas.drawBitmap(bmp,matrix, paint)函数,通过对矩阵matrix的值进行修改,实现屏幕缩放和移动的效果,下面是一段利用matrix实现缩放,位移,旋转的代码:
public void draw(Canvas canvas,Paint paint,float translateX,float translateY,float scaleX,float scaleY,float zoom)
{
/*位移*/
matrix.setTranslate(translateX,translateY);
/*旋转*/
matrix.postRotate(angle,translateX+this.w/2,translateY+this.h/2);
/*缩放*/
matrix.postScale(zoom, zoom,scaleX,scaleY);
canvas.drawBitmap(bmp,matrix, paint);
}
matrix是一个3*3的矩阵,里面分别有几个变量控制位移,旋转,和缩放,可参见此处。
在实现屏幕画布的缩放之后,出现了一个问题,就是绘制出的物体的坐标和物体本来的坐标有了出入。例如,点击加号键,以屏幕中心为焦点进行放大,由于matrix的作用,画出来的画布会变形,整体画布便放大了,此时,那个加号键也会放大,同时,又因为是以屏幕中心为焦点放大,故此时加号键也会同时不断远离屏幕中心,这时请注意,真正加号按钮的坐标并没有变,还是在原来的地方,只不过是画出来的图像的位置和大小发生了变化,所以此时,如果追着加号键按钮点击,会因为绘制出的加号键的位置越来越偏而点击再无效果,而点击加号键原来所在的位置,此时,那里可能只是空白的天空,却会有按键事件的响应,因为真正的按钮坐标就在那里。
一开始,我在思想上走了弯路,将加号按钮在后台的坐标和尺寸也根据画布的变化同时进行缩放和位移,但是到后来,由于画布上的东西越来越多,且变化复杂,就再也难以为继了。于是我开始思考,可能是我的思路出了问题。现在的问题是,屏幕上画出的物体的坐标并不是他们真正的坐标,所以点击无反应,于是我反过来思考,何不将点击屏幕的坐标反之转化成对应后台的坐标,于是,便对matrix矩阵进行求逆,反求出屏幕点击对应在无缩放状态时的坐标,解决了这个问题。
/**将屏幕点击坐标转化到物理世界中的坐标,
* 对屏幕缩放,位移变化矩阵进行求逆,反求出物理
* 世界中对应的坐标位置 */
public void screenToWorld(MotionEvent event)
{
if(event.getAction()==MotionEvent.ACTION_DOWN||event.getAction()==MotionEvent.ACTION_MOVE)
{
float []trans = {0,0,0,1,1,1,2,2,2};
Matrix matrix=new Matrix();
matrix.setTranslate(-offsetX,-offsetY);
matrix.postScale(zoom, zoom,screenW/2,screenH/2);
matrix.getValues(trans);
float [][]mm={
{trans[0],trans[1],trans[2]},
{trans[3],trans[4],trans[5]},
{trans[6],trans[7],trans[8]},
};
float [][]yy=MyMatrix.getN(mm);
newTouchPoint.x=yy[0][0]*touchPoint.x+yy[0][1]*touchPoint.y+yy[0][2];
newTouchPoint.y=yy[1][0]*touchPoint.x+yy[1][1]*touchPoint.y+yy[1][2];
}
}
可以注意图1和图2,围绕鸟的两个同心圆,分别表示小鸟的body范围和小鸟响应点击的作用范围。因为只要点击到小鸟附近的一定距离,即可以拖动小鸟,否则小鸟太小,有可能会半天点不上,因此要设定大一点的作用范围。
另外的两个红圆圈,大的表示点击屏幕的坐标,小的表示经过screenToWorld()函数转化后的点的位置,可以清楚的看出,虽然图1和图2虽然都是点击的减号键附近的位置,但是由于zoom值的影响,对应的真是物理世界中的坐标却是不同的(小红圆圈所示)。
至于镜头跟随效果,只要在logic()函数中设定一个增量变化值控制画布水平位移即可,完善的镜头跟随还要考虑zoom值的变化等。
public void logic()
{
...
/**小鸟发射后,屏幕移动方向与速度,实现镜头跟随的效果*/
moveToLeft=false;
currentIncrement=10;
}
/**屏幕自移动*/
if(moveToLeft)
{
// Log.d("myLog","moveToLeft");
if( (int)(screenW/2*(1-zoom)-(offsetX-currentIncrement)*zoom)<0 &&
(int)(screenW/2*(1-zoom)-(offsetX-currentIncrement)*zoom)+con.getWidth()*zoom>screenW)
{
// Log.d("myLog","moveToLeft");
offsetX-=currentIncrement;
}
}
else if(!moveToLeft)
{
// Log.d("myLog","moveToRight");
if( (int)(screenW/2*(1-zoom)-(offsetX+currentIncrement)*zoom)<0 &&
(int)(screenW/2*(1-zoom)-(offsetX+currentIncrement)*zoom)+con.getWidth()*zoom>screenW)
{
// Log.d("myLog","moveToRight");
offsetX+=currentIncrement;
}
}
...
}
当然,屏幕在缩放和位移时,也要注意边界,如不能将背景缩小的比屏幕小,等等因素都需要考虑。public boolean onTouchEvent(MotionEvent event)
{
........
/**水平位移变化量控制 */
if((int)(screenW/2*(1-zoom)-offsetX*zoom)>0-1)
{
offsetX=(int) (screenW/2*(1-zoom)/zoom);
Log.d("myLog","Zoom to the left");
}
else if((int)(screenW/2*(1-zoom)-offsetX*zoom+sky.getWidth()*zoom)<screenW+1)
{
offsetX=(int)((screenW/2*(1-zoom)+sky.getWidth()*zoom-screenW)/zoom);
Log.d("myLog","Zoom to the right");
}
/**垂直位移量固定,在屏幕3/4处,不受缩放影响 */
offsetY=(int) (con.getHeight()*3/4-screenH/2-screenH/4/zoom);
.........
}
好了,这样就实现了基本的zoom in/out和镜头跟随的功能。在真机测试,屏幕滑动时,有时感觉不太流畅,我不太清楚这是触屏事件导致的还是绘图线程导致的,以后会进一步优化。To be continued...
完整源码下载
--------------------------------------------------
更新于 2012-2-17 22:37
大概将声音,界面,关卡等做了下,上传了app store,欢迎下载试用啊!!!
还有弹道轨迹等诸多方面需要完善,敬请期待。。。性能优化也是件头疼的事。。。