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

附上效果图吧:

112238934.jpg

112240505.jpg

112242488.jpg