将近来学的box2D总结下,暂时没有涉及到关节,等有时间了再弄吧!这次就通过一个自己写的个实例来总结吧!
首先我得说明下这次编程的目标:在屏幕上画出若干个对象,其中有动态对象,也有静态对象,利用box2D模拟物理世界,使动态对象受到重力作用,静态对象不能重力作用,但能与动态对象发生碰撞,使之状态发生改变,另外,为了能够便于观察到更多的效果,屏幕上的对象(无论动态还是静态)都可以通过用手指触摸,拉动而移动位置。
由于box2D的世界是以米为单位,而我们编程是以像素为单位,这就牵涉到单位换算的问题,而且在产生一个Body对象时需要较多的步骤,有较多的函数需要换算,这不免对我们编程带来一定的麻烦,故我就想到将box2d中的Body对象进行再一次封装,本来是打算通过继承Body对象,但由于Box2d是采用的工厂模式来产生对象,而不能直接new出对象,这样即便继承的Body,也不能使用,所以我又想到构造一个BodyFactory对象,由它来产生Body对象,
BodyFactory.java:
package com.example.box2dtest; import org.jbox2d.collision.CircleDef; import org.jbox2d.collision.PolygonDef; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.BodyDef; import org.jbox2d.dynamics.World; public class BodyFactory { private World world; public BodyFactory(World w) {//构造函数需将要在其中产生的Body的World对象提交进来 world = w; } public Body outBox(float x, float y, float w, float h, boolean isStatic) {//产生一个矩形对象Body,该函数大部分采用网上流传的方法构建一个Box,但有一处需注意,那就是body.m_userData是自己写的一个类,后面有详细说明 PolygonDef pd = new PolygonDef(); if (isStatic) { pd.density = 0; } else { pd.density = 1; } pd.friction = 0.8f; pd.restitution = 0.3f; pd.setAsBox(w / 2 / BodyConstant.RATE, h / 2 / BodyConstant.RATE); BodyDef bd = new BodyDef(); bd.position.set((x + w / 2) / BodyConstant.RATE, (y + h / 2) / BodyConstant.RATE); Body body = world.createBody(bd); body.createShape(pd); BodyDetail bodyDetail = new BodyDetail(); bodyDetail.shape = BodyDetail.BOX; bodyDetail.width = w; bodyDetail.height = h; bodyDetail.isStatic = isStatic; bodyDetail.body = body; bodyDetail.sd = pd; body.m_userData = bodyDetail; body.setMassFromShapes(); return body; } public Body outCircle(float x, float y, float r, boolean isStatic) {//产生一个圆形对象Body CircleDef pd = new CircleDef(); if (isStatic) { pd.density = 0; } else { pd.density = 1; } pd.friction = 0.8f; pd.restitution = 0.3f; pd.radius = r / BodyConstant.RATE; BodyDef bd = new BodyDef(); bd.position.set(x / BodyConstant.RATE, y / BodyConstant.RATE); Body body = world.createBody(bd); body.createShape(pd); BodyDetail bodyDetail = new BodyDetail(); bodyDetail.shape = BodyDetail.CIRCLE; bodyDetail.width = r; bodyDetail.height = r; bodyDetail.isStatic = isStatic; bodyDetail.body = body; bodyDetail.sd = pd; body.m_userData = bodyDetail; body.setMassFromShapes(); return body; } public void setBoundary(float x, float y, float w, float h) {//通过用四个矩形对象设置边界 outBox(x, y, w, 1, true); outBox(x, y + h, w, 1, true); outBox(x, y, 1, h, true); outBox(x + w, y, 1, h, true); } }
可以观察到上代码中有一个BodyDetail对象,为什么会有这个对象呢?由于最终我们会采用遍历world中所有Body对象来绘制屏幕,这就带来个问题,那就是遍历的时候,我们获取到的是Body对象,而这个Body到底是圆,还是矩形,还是其他形状?它的大小宽高又是多少?这些东西可能能由Body对象中自带的方法,属性获取,但网上的资料太少了,我没找到,但即便能够获取,也有不能获取的信息,比如颜色等,所以我们还是自己来构造个类附带Body的信息,哦,对了,body.m_userData这个属性是一个Object对象,所以它允许我们携带任何信息:
package com.example.box2dtest; import org.jbox2d.collision.ShapeDef; import org.jbox2d.dynamics.Body; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; public class BodyDetail { public static final int BOX = 1, CIRCLE = 2; public int shape; public float width, height; public boolean isStatic = false; public Body body; public ShapeDef sd; private float density_bnk = 0; private Paint paint; private float angle, centerX, centerY, X, Y; public BodyDetail() { paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Style.STROKE); } public void setStatic(boolean b) { if (b) { if (density_bnk == 0) { density_bnk = sd.density; } sd.density = 0f; } else sd.density = density_bnk; body.destroyShape(body.m_shapeList);//此处需注意,这是更改body的shape的方法,即先删除以前的shape,然后再create新shape,在网上找了很久,都没有找到方法,硬是自己观察函数名才发现的,嘻! body.createShape(sd); body.setMassFromShapes(); } public boolean getStatic() { if (sd.density == 0f) { return true; } return false; } public void draw(Canvas canvas) { angle = (float) (body.getAngle() * 180 / Math.PI); centerX = body.getPosition().x * BodyConstant.RATE; centerY = body.getPosition().y * BodyConstant.RATE; X = centerX - width / 2; Y = centerY - height / 2; canvas.save(); canvas.rotate(angle, centerX, centerY); switch (shape) { case BOX: canvas.drawRect(X, Y, X + width, Y + height, paint); break; case CIRCLE: canvas.drawCircle(centerX, centerY, width, paint); break; default: break; } canvas.restore(); } public boolean contain(float x, float y) { switch (shape) { case BOX: if (x - X < width && x - X > 0 && y - Y > 0 && y - Y < height) { return true; } break; case CIRCLE: if ((x - X) * (x - X) + (y - Y) * (y - Y) < width * width) { return true; } break; default: break; } return false; } }
可以发现这各类中,含有不少的属性和方法,这些方法在后面都有用途,其用途,我想通过其名称就能了解,上诉代码用到了BodyConstant这个类,这个类很简单,用于保存一些常量,这个程序中尽管只有一个,但为了让程序层次分明,还是值得的.
public class BodyConstant { public static final float RATE = 30f; }
这个RATE常量就是设定的像素与米的转换关系,就这个而言就是30像素等效于物理世界的1米
这样一来,铺垫都打好了,接下来就来看下MySurfaceView:
package com.example.box2dtest; import org.jbox2d.collision.AABB; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.World; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.util.Log; 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 { private Thread th; private SurfaceHolder sfh; private Canvas canvas; private Paint paint; private boolean flag; private boolean touch = false; private Body touchBody; private BodyDetail touchBodyDetail; World world; AABB aabb; Vec2 gravity; float timeStep = 1f / 60f; final int iterations = 10; @Override public boolean onTouchEvent(MotionEvent event) {//覆盖 onTouchEvent实现屏幕触摸监听 // TODO Auto-generated method stub super.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_DOWN) { touchBody = world.getBodyList(); touchBodyDetail = (BodyDetail) touchBody.m_userData; for (int i = 1; i < world.getBodyCount(); i++) {//遍历body,利用contain方法来判定是否选中一个对象,若选中,将其设置为静态,避免其是动态对象在选中状态时还在模拟物理世界运动 touchBodyDetail = (BodyDetail) touchBody.m_userData; if (touchBodyDetail.contain(event.getX(), event.getY())) { touchBodyDetail.setStatic(true); touch = true; break; } touchBody = touchBody.m_next; } } else if (event.getAction() == MotionEvent.ACTION_MOVE && touch == true) { touchBody.setXForm( new Vec2(event.getX() / BodyConstant.RATE, event.getY() / BodyConstant.RATE), touchBody.getAngle());//根据手指移动的坐标来设定选中的对象的新坐标,并保持先前的角度 } else if (event.getAction() == MotionEvent.ACTION_UP && touch == true) { touchBodyDetail.setStatic(false); touch = false; } return true; } public MySurfaceView(Context context) { super(context); this.setKeepScreenOn(true); sfh = this.getHolder(); sfh.addCallback(this); paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Style.STROKE); setFocusable(true); } public World createWorld(float x1, float y1, float x2, float y2) { aabb = new AABB(); gravity = new Vec2(0f, 10f); aabb.lowerBound.set(x1 / BodyConstant.RATE, y1 / BodyConstant.RATE); aabb.upperBound.set(x2 / BodyConstant.RATE, y2 / BodyConstant.RATE); return new World(aabb, gravity, false); } public void surfaceCreated(SurfaceHolder holder) { world = createWorld(-20, -20, getWidth() + 20, getHeight() + 20); BodyFactory bodyFactory = new BodyFactory(world); bodyFactory.outBox(0, getHeight() - 100, 110, 10, true);//利用bodyFactory产生几个对象 bodyFactory.outBox(100, 10, 40, 20, false); bodyFactory.outBox(getWidth() - 200, getHeight() - 50, 90, 10, true); bodyFactory.setBoundary(0, 0, getWidth(), getHeight()); bodyFactory.outCircle(150, 200, 50, true); flag = true; th = new Thread(this); th.start(); } public void myDraw() { try { canvas = sfh.lockCanvas(); if (canvas != null) { canvas.drawColor(Color.WHITE); Body body = world.getBodyList(); for (int i = 1; i < world.getBodyCount(); i++) {//遍历World中的body,注:i是从1开始的,即即使world中没有body,getBodyCount()也会返回1 BodyDetail bodyDetail = (BodyDetail) body.m_userData;// bodyDetail.draw(canvas);//调用bodyDetail的draw方法在canvas画出图形 body = body.m_next; } } } catch (Exception e) { Log.e("Error", "Error!"); } finally { if (canvas != null) sfh.unlockCanvasAndPost(canvas); } } public void Logic() { world.step(timeStep, iterations);//在每次刷帧时都应调用step } public void run() { while (flag) { myDraw(); Logic(); try { Thread.sleep((long) timeStep * 1000); } catch (Exception ex) { Log.e("Error", "Error!"); } } } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } public void surfaceDestroyed(SurfaceHolder holder) { flag = false; } }
这个类继承surfaceView,作为控件在屏幕上展现,在细节上都有注释,就不多罗嗦了,最后:
import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); this.requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new MySurfaceView(this)); } }
主类设置view,运行即可,啊,终于完了,纯手打,累死了,由于水平有限,可能有诸多错误,望不吝指正,谢谢!
附上效果图吧:
本文出自 “文剑小调的随笔记” 博客,转载请与作者联系!