Jbox2D介绍:
JBox2D是开源的物理引擎Box2D的Java版本,可以直接用于Android。由于JBox2D的图形渲染使用的是Processing库,因此在Android平台上使用JBox2D时,图形渲染工作只能自行开发。该引擎能够根据开发人员设定的参数,如重力、密度、摩擦系数和弹性系数等,自动地进行2D刚体物理运动的全方位模拟。每种物理引擎都有其独特的概念,在学习开源的物理引擎时,首先需要弄明白的就是其基本概念。因此,本节主要为读者复习一下物理学中的一些基本概念,并介绍JBox2D中的一些常用类与概念。
游戏是对真实世界的仿真,其中用到了许多物理学知识,如密度、质量、质心、摩擦力、扭矩以及碰撞(恢复)系数等。接下来,本小节将简要介绍用JBox2D开发游戏时经常用到的一些物理学概念。
密度
物理学中密度指的是单位体积的质量,符号为"ρ",常用单位为kg/m^3。其是物质的一种基本特性,不随物体的质量、体积的改变而改变,同种物质的密度相同。
质量
质量指的是物体中所含物质的量,即物体惯性的大小,国际单位是kg。同一物体的质量通常是一个常量,不因高度、经度或者纬度的改变而变化。但是根据爱因斯坦的相对论,同一物体的质量会随着速度的变化而改变。只有运动接近光速才能感觉到这种变化,因此在游戏中一般不考虑速度对质量的影响。
质心
物体(或物体系)的质量中心,是研究物体(或物体系)机械运动的一个重要参考点。当作用力(或合力)通过该点时,物体只作移动而不发生转动;否则在发生移动的同时物体将绕该点转动。
研究质心的运动时,可将物体的质量看作集中于质心。理论上,质心是对物体的质量分布用"加权平均法"求出的平均中心。
摩擦力
当两个互相接触的物体,如果要发生或者已经发生相对运动。就会在接触面上产生一种阻碍该相对运动的力,这种力就称之为摩擦力。其基本情况如下图所示。
提示 根据物体是否发生相对运动可以分为静摩擦力与滑动摩擦力,实际开发中可以进行简化,但若要模拟更加真实的效果就需要分别开发。
扭矩
扭矩在物理学中就是力矩的大小,等于力与力臂的乘积,国际单位是Nm(牛米)。在力臂不变的情况下,力越大,扭矩越大。基本情况如下图所示。
恢复系数
两物体碰撞后的总动能与碰撞前的总动能之间的比称之为恢复系数,其取值范围为0~1。如果恢复系数为 1,则碰撞为完全弹性碰撞,满足机械能守恒;如果恢复系数小于1并且大于0,则为非完全弹性碰撞,不满足机械能守恒,这种情况是最常见的;如果恢复系数为0,则为完全非弹性碰撞,两个物体会粘在一起。
看效果图:
javafx和jbox2d组合:
1.坐标系对应
在真实物理环境中坐标系为向右x轴为正方向,向上为y轴正方向。如下图:
在fx界面中向右x轴为正方向,向下为y轴正方向。如下图:
从图中可以看出,两个坐标系中x轴方向完全一致,但y轴方向完全相反,因此需要做坐标变换。
2.类设计(jbox2d版本为2.0.1)
PhysicalObject类设计:
这是物理对象实现的基类,包含World和Body两个重要对象属性
World对象是表示物理世界环境,Body对象表示物理世界中的刚体。
核心代码如下:
public abstract class PhysicalObject extendsParent {protectedWorld world;private boolean shouldCreate = true;private boolean shouldDestroy = false;private boolean destroyed = false;protectedBody body;public abstract voidcreatePhysicsObject();public abstract voidupdate();public voidcreatePhysicsObject(World world) {this.world =world;
createPhysicsObject();
}}
Ball类设计:
Ball类继承PhysicalObject对象,具体代码如下:
注意createPhysicsObject方法,因为球是原形,所以使用CircleDef 来描述球
public class Ball extendsPhysicalObject {public int radius = 3;private DoubleProperty xPos = new SimpleDoubleProperty(50);private DoubleProperty yPos = new SimpleDoubleProperty(6);private DoubleProperty angle = new SimpleDoubleProperty(0);private Image ball = new Image(this.getClass().getResourceAsStream("Ball.png"));public ImageView iv = newImageView(ball);;privateTimeline endAnimator;public Ball(World world, float xPos, floatyPos) {this.world =world;this.xPos.set(xPos);this.yPos.set(yPos);
init();
create();
}private voidinit() {
endAnimator= newTimeline();
KeyFrame frame1= new KeyFrame(Duration.millis(200), newKeyValue(
iv.opacityProperty(),0.1, Interpolator.LINEAR), newKeyValue(
iv.translateYProperty(),-30, Interpolator.LINEAR));
KeyFrame frame2= new KeyFrame(Duration.millis(250),new EventHandler() {
@Overridepublic voidhandle(ActionEvent event) {
setDestroyed(true);
}
});
endAnimator.getKeyFrames().addAll(frame1, frame2);
}private voidcreate() {
iv.xProperty().bind(xPos.multiply(10).subtract(ball.getWidth() / 2));
iv.yProperty().bind(yPos.multiply(10).subtract(ball.getHeight() / 2));
iv.rotateProperty().bind(angle);
getChildren().add(iv);
}
@Overridepublic voidcreatePhysicsObject() {
CircleDef cd= newCircleDef();
cd.radius=radius;
cd.density= 5.0f;
cd.friction= 0.2f;
cd.restitution= 0.75f;
BodyDef bd= newBodyDef();
bd.position.set(newVec2(xPos.floatValue(), yPos.floatValue()));
body=world.createBody(bd);
body.createShape(cd);
body.setMassFromShapes();
body.setUserData(this);
}
@Overridepublic voidmarkDestroyed() {super.markDestroyed();
endAnimator.playFromStart();
}
@Overridepublic voidupdate() {if(isShouldDestroy()) {if (body != null) {
world.destroyBody(body);
body= null;
}return;
}
xPos.set(body.getPosition().x);
yPos.set(body.getPosition().y);
angle.set(Math.toDegrees(body.getAngle()));
}
}
Bridge类设计:
Bridge类继承PhysicalObject对象,具体代码如下
注意:RevoluteJointDef为旋转关节
一个旋转关节会强制两个物体共享一个锚点,即所谓铰接点。旋转关节只有一个自由度:两个物体的相对旋转。
需深入理解可以参考详细教程。
public class Bridge extendsPhysicalObject {private DoubleProperty xPos = new SimpleDoubleProperty(50);private DoubleProperty yPos = new SimpleDoubleProperty(6);private int width = 10;private Image PLANK_IMAGE = new Image(this.getClass().getResourceAsStream("BridgePiece.png"));publicImageView iv;privateTimeline endAnimator;public Bridge(World world, float xPos, float yPos, intwidth) {this.world =world;this.xPos.set(xPos);this.yPos.set(yPos);this.width =width;
create();
}private voidcreate() {
}
@Overridepublic voidcreatePhysicsObject() {float pw = 1.2f;
PolygonDef sd= newPolygonDef();
sd.setAsBox(pw/ 2f, 0.2f);
sd.density=60f;
sd.friction= 0.2f;int numPlanks =width;
RevoluteJointDef jd= newRevoluteJointDef();
Body prevBody= null;
Body firstBody= null;
Vec2 anchor= null;for (int i = 0; i < numPlanks; i++) {
BodyDef bd= newBodyDef();
bd.position.set(xPos.floatValue()+ 1.5f *i, yPos.floatValue());
Body body=world.createBody(bd);
body.createShape(sd);
getChildren().add(newBridgePiece(PLANK_IMAGE, body));if (prevBody == null) {
firstBody=body;
}else{if (i != numPlanks - 1) {
body.setMassFromShapes();
}
anchor= new Vec2(xPos.floatValue() - pw + 1.5f *i,
yPos.floatValue());
jd.initialize(prevBody, body, anchor);
world.createJoint(jd);
}
prevBody=body;
}
Vec2 anchor1= new Vec2(xPos.floatValue() - pw + 1.5f *numPlanks,
yPos.floatValue());
jd.initialize(prevBody, firstBody, anchor1);
world.createJoint(jd);
}
@Overridepublic voidmarkDestroyed() {super.markDestroyed();
endAnimator.playFromStart();
}
@Overridepublic voidupdate() {for(Node c : getChildren()) {
((BridgePiece) c).update();
}
}
}