将近来学的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,运行即可,啊,终于完了,纯手打,累死了,由于水平有限,可能有诸多错误,望不吝指正,谢谢!

附上效果图吧: