Box2d是一个强大的开源物理游戏引擎,使用c/c 编写,用来模拟2D的物体运动和碰撞。Box2D内部集成了大量的物理力学和运动学计算,内部实现很复杂,但是封装性很好,暴露给开发者的接口简单友好
有人实现了Java版的Box2D后被谷歌收入,并成立了一个小组维护这个项目--JBox2D
JBox2d 反馈给开发者的结果只有坐标, 没有绘制接口,需要开发者自己绘制,需要我们自行绘制,而Box2d含有绘制
项目主页:http://www.jbox2d.org/
源码地址:https://github.com/jbox2d/jbox2d
JBox2d 文档:http://note.youdao.com/noteshare?id=c60a7f336b54603d8c001a0ec754a65f
接下来,就开始学习使用JBox2d,首先从github上下载项目到本地,解压后的目录结构如下:
这是一个maven工程,我们有两种方式编译它
1.将maven工程转换为gradle工程后,使用gradle编译
--gradle环境变量配置:https://www.jb51.net/article/140691.htm
在命令行下执行:gradle init --type pom
转换为gradle工程之后,将gradle-wrapper.properties文件中的distributionUrl改为我们as中的gradle后就可以用as打开了
点击assemble编译,或者在Terminal中执行命令:gradlew :(模块名称): (任务名称)
发现有报错,因为转换为gradle工程时有可能会有bug,将目录结构打乱
其实很简单,将包名改下就可以了,解决完所有包名问题后,再编译下
在jbox2d-librarybuildlibs目录下拿到编译完的jar,放入我们的工程中就可以使用了
2.直接使用maven工具编译
--maven下载地址:http://maven.apache.org/download.cgi(需要配置下环境变量)
在命令行下执行:mvn install
在jbox2d-librarytarget目录下拿到编译完的jar,放入我们的工程中就可以使用了
接下来,先介绍下JBox2d的一些类的概念
1.首先是世界 World类,即虚拟世界,一个容器,默认没有边界,我们所有的物体都在这个世界中运动,确定worid的边界可以用两种方式:1. AABB 2. 设置刚体边界
2.刚体 Body类,即物体,拥有两种属性,1:形状 (shape形状类);2. 抽样描述类 FixtureDef(摩擦系数 补偿系数 密度)。来表示物体的形状,质量,补偿系数(模拟物体反弹的一个系数,系数一般设为 0 到 1 之间。0 代表不反弹,1 代表完全反弹。),摩擦力
具体流程:创建世界->设置边界->世界中创建刚体->开启世界
撸码:
1.创建一个model,用来取世界中数据,首先创建世界
代码语言:javascript复制 /**
* 创建世界
*/
public void createWorld() {
if (mWorld == null) {
//竖直向下的重力向量
mWorld = new World(new Vec2(0f, 10f));
}
}
设置边界
代码语言:javascript复制 /**
* 由于世界时没有边界的,我们又要在边界有碰撞效果,所以使用刚体设置边界
*
* @param width
* @param height
*/
public void updateBounds(int width, int height) {
mWidth = width;
mHeight = height;
//创建静态刚体
BodyDef bodyDef = new BodyDef();
bodyDef.setType(BodyType.DYNAMIC);//表示这个刚体是静态的
//左侧和右侧的
//定义的形状,PolygonShape为多边形,所以可表示矩形
PolygonShape shape = new PolygonShape();
//确定为矩形,左侧和右侧刚体的高度为最大边界高度,宽度为1
shape.setAsBox(1, mHeight);
//描述
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = mDesity;
fixtureDef.friction = mFriction;//摩擦系数
fixtureDef.restitution = mRestitution; //补偿系数
//确定左侧刚体的位置
bodyDef.position.set(-1, 0);//左侧为-1,0
//通过世界创建刚体
Body body = mWorld.createBody(bodyDef);
//赋予刚体属性
body.createFixture(fixtureDef);
//确定右侧刚体的位置
bodyDef.position.set(mWidth 1, 0);//右侧为mWidth 1,0
//通过世界创建刚体并赋予属性
mWorld.createBody(bodyDef).createFixture(fixtureDef);
//上侧和下侧的刚体
//左侧和右侧刚体的高度为1,宽度为最大宽度
shape.setAsBox(mWidth, 1);
//重新赋值下形状
fixtureDef.shape = shape;
//确定上侧刚体的位置
bodyDef.position.set(0, -1);//上侧为0,-1
//通过世界创建刚体并赋予属性
mWorld.createBody(bodyDef).createFixture(fixtureDef);
//确定下侧刚体的位置
bodyDef.position.set(0, mHeight 1);//下侧为0,mHeight 1
//通过世界创建刚体并赋予属性
mWorld.createBody(bodyDef).createFixture(fixtureDef);
}
定义创建刚体的方法
代码语言:javascript复制 /**
* 根据坐标创建刚体
*
* @return
*/
public Body createBody(float x, float y, float radius) {
//创建动态刚体
BodyDef bodyDef = new BodyDef();
bodyDef.setType(BodyType.DYNAMIC);//表示这个刚体是动态的
//定义的形状,CircleShape为圆形
CircleShape shape = new CircleShape();
//设置半径
shape.setRadius(radius);
//描述
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = mDesity;
fixtureDef.friction = mFriction;//摩擦系数
fixtureDef.restitution = mRestitution; //补偿系数
//确定刚体的位置
bodyDef.position.set(x, y);
//通过世界创建刚体
Body body = mWorld.createBody(bodyDef);
//赋予刚体属性
body.createFixture(fixtureDef);
return body;
}
定义获取坐标的方法
代码语言:javascript复制 /**
* 获取坐标
*
* @param body
* @return
*/
public float[] getCoordinate(Body body) {
if (body != null) {
return new float[]{body.getPosition().x, body.getPosition().y};
}
return null;
}
定义获取旋转弧度的方法
代码语言:javascript复制 /**
* 获取弧度
*
* @param body
* @return
*/
public float getAngle(Body body) {
if (body != null) {
return (float) ((body.getAngle() / Math.PI * 180) % 360);
}
return -1f;
}
定义开启世界的方法
代码语言:javascript复制 private float dt = 1f / 60f; //模拟世界的频率
private int mVelocityIterations = 5; //速率迭代器
private int mPosiontIterations = 20; //迭代次数
/**
* 开启世界
*/
public void startWorld() {
if (mWorld != null) {
mWorld.step(dt, mVelocityIterations, mPosiontIterations);
}
}
2.新建Presenter用于将边界值等传递给model和model中数据传递给View
定义设置边界的方法
代码语言:javascript复制 /**
* 设置边界
*
* @param width
* @param height
*/
public void updateBounds(int width, int height) {
collisionModel.updateBounds(width, height);
}
定义绑定刚体的方法
代码语言:javascript复制 /**
* 绑定刚体
*
* @param view
*/
public void bindBody(View view) {
Body body = collisionModel.createBody(view.getX(), view.getY(), view.getWidth() / 2f);
view.setTag(R.id.view_body_tag, body);
}
定义设置View的坐标和旋转的方法
代码语言:javascript复制 /**
* 设置View的坐标和旋转
*
* @param view
*/
public void drawView(View view) {
Body body = (Body) view.getTag(R.id.view_body_tag);
//拿到坐标
float[] pos = collisionModel.getCoordinate(body);
if (pos != null) {
//左上角坐标
view.setX((int) (pos[0] - view.getWidth() / 2f));
view.setY((int) (pos[1] - view.getWidth() / 2f));
}
//设置旋转
float angle = collisionModel.getAngle(body);
if (angle >= 0) {
view.setRotation(angle);
}
}
定义为body设置重力方向的方法
代码语言:javascript复制 /**
* 设置重力
*
* @param x
* @param y
* @param body
*/
public void applyLinearImpulse(float x, float y, Body body) {
Vec2 impluse = new Vec2(x, y);
body.applyLinearImpulse(impluse, body.getPosition(), true); //给body做线性运动 true 运动完之后停止
}
3.自定义控件
代码语言:javascript复制/**
* 碰撞view
*/
public class CollisionView extends FrameLayout {
private CollisionPresenter collisionPresenter;
public CollisionView(@NonNull Context context) {
this(context, null);
}
public CollisionView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CollisionView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//开启ondraw方法回调
setWillNotDraw(false);
collisionPresenter = new CollisionPresenter();
collisionPresenter.setDensity(getResources().getDisplayMetrics().density);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
collisionPresenter.updateBounds(getMeasuredWidth(), getMeasuredHeight());
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
//子viwe设置body
int childCount = getChildCount();
for (int i = 0; i < childCount; i ) {
View view = getChildAt(i);
collisionPresenter.bindBody(view);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
collisionPresenter.startWorld();
int childCount = getChildCount();
for (int i = 0; i < childCount; i ) {
View view = getChildAt(i);
collisionPresenter.drawView(view);
}
invalidate();
}
public void onSensorChanged(float x, float y) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i ) {
View view = getChildAt(i);
collisionPresenter.applyLinearImpulse(x, y, view);
}
}
}
4.在Activity中添加子view和开启监听重力感应
代码语言:javascript复制public class MainActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor defaultSensor;
private CollisionView collisionView;
private int[] imgs = {
R.mipmap.share_fb,
R.mipmap.share_kongjian,
R.mipmap.share_pyq,
R.mipmap.share_qq,
R.mipmap.share_tw,
R.mipmap.share_wechat,
R.mipmap.share_weibo
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
collisionView = findViewById(R.id.collisionView);
initView();
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
private void initView() {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER;
for (int i = 0; i < imgs.length; i ) {
ImageView imageView = new ImageView(this);
imageView.setImageResource(imgs[i]);
collisionView.addView(imageView, layoutParams);
}
}
@Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, defaultSensor, SensorManager.SENSOR_DELAY_UI);
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0];
float y = event.values[1] * 2.0f;
collisionView.onSensorChanged(-x, y);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
最终效果: