在自学Android开发近半年之后,终于选定了毕业设计的课题,做一个Angry Bird。
开发了一个简单的demo,目前只有发射和碰撞检测功能,其他功能还需不断完善。
首先要向Himi表示感谢,正是看了Himi的《Android游戏编程之从零开始》,才第一次知道了JBox2D物理学引擎,才第一次使用了物理学引擎(见识鄙陋,不好意思)。
因为AngryBird和Cut the rope是我最喜欢的两款手机游戏,故当我知道了AngryBird是使用JBox2D开发的时,便决定抱着向前辈致敬的态度,自己也来简单的实现下了。
引擎使用的是最新的JBox2D-2.1.2, 一开始创建world时总是报错,说是slf4j有问题,感谢z1074971432,正是通过他的解答,才使得新版的JBox2D-2.1.2可以顺利的在android上跑起来。
下面是比较重要的部分代码
package com.ritterliu.angryBirdTwo; import org.jbox2d.callbacks.ContactImpulse; import org.jbox2d.callbacks.ContactListener; import org.jbox2d.collision.Manifold; import org.jbox2d.collision.shapes.CircleShape; import org.jbox2d.collision.shapes.PolygonShape; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.BodyDef; import org.jbox2d.dynamics.BodyType; import org.jbox2d.dynamics.FixtureDef; import org.jbox2d.dynamics.World; import org.jbox2d.dynamics.contacts.Contact; import com.ritterliu.angryBird.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.SurfaceHolder.Callback; public class MySurfaceView extends SurfaceView implements Callback,Runnable,ContactListener{ private SurfaceHolder sfh; /**总的运行线程*/ private Thread th; /**线程运行标志位*/ private boolean flag; private Canvas canvas; private Paint paint; private int screenW,screenH; /**Bird类,用以绘画出小鸟*/ Bird bird; /**touchEvent的优化,避免真机调试时频繁响应*/ byte[] lock = new byte[0]; private final int timePause=50; /**物理世界声明*/ World world; // AABB aabb; //新版的JBox2D已经不需要AABB区域了 Vec2 gravity; private final float RATE=30.0f; //物理世界与屏幕环境缩放比列 float timeStep=1f/60f; /**新的JBox2D增加到两个控制迭代,参数均按照官方manual上的参数设置的 */ int velocityIterations = 10; int positionIterations = 8; public MySurfaceView(Context context) { super(context); sfh=this.getHolder(); sfh.addCallback(this); paint=new Paint(); paint.setStyle(Style.STROKE); paint.setAntiAlias(true); // aabb=new AABB(); //旧版JBox2D的创建方法 // aabb.lowerBound.set(-100, -100); // aabb.upperBound.set(100,100); /**重力初始化*/ gravity=new Vec2(0,10f); /**创建物理世界*/ world=new World(gravity, true); /**增加物理世界中的碰撞监听*/ world.setContactListener(this); } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub /**得到屏幕大小*/ this.screenW=this.getWidth(); this.screenH=this.getHeight(); /**初始化小鸟位置*/ AngryBirdActivity.startX=100; AngryBirdActivity.startY=screenH-100; /**初始化橡皮筋长度*/ AngryBirdActivity.touchDistance=0.2f*screenH; Bitmap bmpBird=BitmapFactory.decodeResource(this.getResources(), R.drawable.smallbird); bird=new Bird(AngryBirdActivity.startX,AngryBirdActivity.startY,bmpBird.getHeight()/2f,bmpBird,Type.redBird); /** 创建四周的边框,设置 isStatic为true,即在物理世界中是静止的, * Type设置为ground,避免被击毁 * */ createPolygon(5, 5, this.getWidth() - 10, 2, true,Type.ground); createPolygon(5, this.getHeight() - 10, this.getWidth() - 10, 2, true,Type.ground); createPolygon(5, 5, 2, this.getHeight() - 10, true,Type.ground); createPolygon(this.getWidth() - 10, 5, 2, this.getHeight() - 10, true,Type.ground); /**创建6个方形,isStatic设置为false,即在物理世界中是动态,收外力作用影响 */ for(int i=0;i<6;i++) { createPolygon(screenW-150,screenH-50-20*i,20,20, false,Type.wood); } /**创建一个长条型,也是动态的 */ createPolygon(screenW-180,screenH-50-20*6-10,80,10, false,Type.wood); /**启动线程*/ flag=true; th=new Thread(this); th.start(); } /**创建圆形的body*/ public Body createCircle(float x,float y,float r,boolean isStatic) { /**设置body形状*/ CircleShape circle = new CircleShape(); /**半径,要将屏幕的参数转化到物理世界中 */ circle.m_radius = r/RATE; /**设置FixtureDef */ FixtureDef fDef=new FixtureDef(); if(isStatic) { /**密度为0时,在物理世界中不受外力影响,为静止的 */ fDef.density=0; } else { /**密度不为0时,在物理世界中会受外力影响 */ fDef.density=1; } /**设置摩擦力,范围为 0~1 */ fDef.friction=1.0f; /**设置物体碰撞的回复力,值越大,物体越有弹性 */ fDef.restitution=0.3f; /**添加形状*/ fDef.shape=circle; /**设置BodyDef */ BodyDef bodyDef=new BodyDef(); /**此处一定要设置,即使density不为0, * 若此处不将body.type设置为BodyType.DYNAMIC,物体亦会静止 * */ bodyDef.type=isStatic?BodyType.STATIC:BodyType.DYNAMIC; /**设置body位置,要将屏幕的参数转化到物理世界中 */ bodyDef.position.set((x)/RATE, (y)/RATE); /**创建body*/ Body body=world.createBody(bodyDef); /**添加 m_userData */ body.m_userData=bird; // body.createShape(fDef); //旧版JBox2D的创建方法 /**为body创建Fixture*/ body.createFixture(fDef); // body.setMassFromShapes(); //旧版JBox2D的创建方法 return body; } public Body createPolygon(float x,float y,float width,float height,boolean isStatic,Type type) { PolygonShape polygon =new PolygonShape(); polygon.setAsBox(width/2/RATE, height/2/RATE); FixtureDef fDef=new FixtureDef(); if(isStatic) { fDef.density=0; } else { fDef.density=1; } fDef.friction=1.0f; fDef.restitution=0.0f; fDef.shape=polygon; BodyDef bodyDef=new BodyDef(); bodyDef.type=isStatic?BodyType.STATIC:BodyType.DYNAMIC;//new bodyDef.position.set((x+width/2)/RATE,(y+height/2)/RATE ); Body body=world.createBody(bodyDef); body.m_userData=new MyRect(x,y,width,height,type); // body.createShape(polygonDef); // body.setMassFromShapes(); body.createFixture(fDef); return body; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub flag=false; } public void draw() { try { canvas=sfh.lockCanvas(); if(canvas!=null) { /**用白色填充画布*/ canvas.drawColor(Color.WHITE); /**画出小鸟*/ bird.draw(canvas, paint); /**如果小鸟还没被发射,画出拖动的橡皮筋轨迹*/ if(!bird.getIsReleased()) { canvas.drawLine(AngryBirdActivity.startX, AngryBirdActivity.startY, bird.getX(), bird.getY(), paint); } /**遍历物理世界,画出Rect */ Body body = world.getBodyList(); for (int i = 1; i < world.getBodyCount(); i++) { if ((body.m_userData) instanceof MyRect) { MyRect rect = (MyRect) (body.m_userData); rect.draw(canvas, paint); } body = body.m_next; } } } catch(Exception ex) { ex.printStackTrace(); } finally { if(canvas!=null) { sfh.unlockCanvasAndPost(canvas); } } } public void logic() { world.step(timeStep, velocityIterations,positionIterations);// 物理世界进行模拟 /**遍历物理世界中的body,将物理世界仿真出的值反馈给屏幕, * 改变bird和rect的参数 * */ Body body = world.getBodyList(); for (int i = 1; i < world.getBodyCount(); i++) { if ((body.m_userData) instanceof MyRect) { MyRect rect = (MyRect) (body.m_userData); rect.setX(body.getPosition().x * RATE - (rect.getWidth()/2)); rect.setY(body.getPosition().y * RATE - (rect.getHeight()/2)); rect.setAngle((float)(body.getAngle()*180/Math.PI)); } else if ((body.m_userData) instanceof Bird) { Bird bird = (Bird) (body.m_userData); bird.setX(body.getPosition().x * RATE ); bird.setY(body.getPosition().y * RATE ); bird.setAngle((float)(body.getAngle()*180/Math.PI)); } else // body.m_userData==null时,将body销毁,表示被击毁 { world.destroyBody(body); } body = body.m_next; } /**发射小鸟,且只有一次,发射过后,不能再拖动了*/ if(bird.getIsReleased()&&!bird.getApplyForce()) { /**发射时才创建一个body*/ Body birdBody=createCircle(bird.getX(),bird.getY(),bird.getR(),false); /**设置bullet属性为true,否则速度过快时可能会有穿越现象 */ birdBody.setBullet(true); /**发射力量控制*/ float forceRate=50f; /**根据橡皮筋长度和角度设置发射力*/ float angle=(float) Math.atan2(bird.getY()-AngryBirdActivity.startY,bird.getX()-AngryBirdActivity.startX); float forceX=-(float) (Math.sqrt(Math.pow(bird.getX()-AngryBirdActivity.startX, 2))*Math.cos(angle)); float forceY=-(float) (Math.sqrt(Math.pow(bird.getY()-AngryBirdActivity.startY, 2))*Math.sin(angle)); Vec2 force=new Vec2(forceX*forceRate,forceY*forceRate); /**对body应用作用力 */ birdBody.applyForce(force, birdBody.getWorldCenter()); /**设置已经作用过力,发射后,不能再拖动了 */ bird.setApplyForce(true); } } @Override public void run() { while(flag) { long start=System.currentTimeMillis(); draw(); logic(); long end=System.currentTimeMillis(); try { if(end-start<50) { Thread.sleep(50-(end-start)); } } catch(Exception ex) { ex.printStackTrace(); } } } public boolean onTouchEvent(MotionEvent event) { if(event.getAction()==MotionEvent.ACTION_DOWN) { /**小鸟未发射时点击它*/ if(bird.isPressed(event)&&!bird.getIsReleased()) { bird.setIsPressed(true); } } else if(event.getAction()==MotionEvent.ACTION_MOVE) { /**小鸟未发射时拖动 */ if(bird.getIsPressed()) { bird.move(event); } } else if(event.getAction()==MotionEvent.ACTION_UP) { if(bird.getIsPressed()) { bird.setIsReleased(true); bird.setIsPressed(false); } } /**对touchEvent的优化,防止真机调试时过于频繁的响应 */ synchronized(lock) { try { lock.wait(timePause); } catch(Exception ex) { ex.printStackTrace(); } } return true; } @Override public void beginContact(Contact arg0) { // TODO Auto-generated method stub } @Override public void endContact(Contact arg0) { // TODO Auto-generated method stub } @Override public void postSolve(Contact arg0, ContactImpulse arg1) { // TODO Auto-generated method stub /**碰撞事件的检测,参数是调试出来的 */ if(arg1.normalImpulses[0]>5) { if ( (arg0.getFixtureA().getBody().getUserData())instanceof MyRect) { MyRect rect=(MyRect)(arg0.getFixtureA().getBody().getUserData()); /**只有这几种类型会被击毁 */ if(rect.getType()==Type.stone ||rect.getType()==Type.wood ||rect.getType()==Type.pig ||rect.getType()==Type.glass) { arg0.getFixtureA().getBody().m_userData=null; } } if ( (arg0.getFixtureB().getBody().getUserData())instanceof MyRect) { MyRect rect=(MyRect)(arg0.getFixtureB().getBody().getUserData()); if(rect.getType()==Type.stone ||rect.getType()==Type.wood ||rect.getType()==Type.pig ||rect.getType()==Type.glass) { arg0.getFixtureB().getBody().m_userData=null; } } } } @Override public void preSolve(Contact arg0, Manifold arg1) { // TODO Auto-generated method stub } }这个简单的demo只能算是对JBox2D的初步运用,实现了基本的小鸟的发射和物体的击毁,还有很多功能没有完善,比如说屏幕的zoom in/out,小鸟发射后的镜头跟随等等。正如一个前辈所说的,做一个游戏不难,做好一个游戏很难,下面还会不断进行完善的。
to be continued...
完整源码下载