将近来学的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,运行即可,啊,终于完了,纯手打,累死了,由于水平有限,可能有诸多错误,望不吝指正,谢谢!
附上效果图吧:
转载于:https://blog.51cto.com/rainlee/1315589