box2d中的单位是以米(m)为单位,但我们游戏中的渲染是以像素(px)为单位的。所以就有一个在渲染的时候的同步问题。由于box2d只负责计算数据,所以就有两种方式来进行两者之间的同步。
1.以box2d中的m为基准
2.以精灵的绘制px为基准
libgdx例子中默认的是以box2d的数据作为基准。具体例子参考:https://code.google.com/p/libgdx/source/browse/trunk/tests/gdx-tests/src/com/badlogic/gdx/tests/Box2DTest.java
其中,如注释中所写的,例子建立了一个(48x32)的照相机,假如我们的手机分辨率是480x320,位置(1,1)就相当于手机屏幕的(10,10):
// setup the camera. In Box2D we operate on a
// meter scale, pixels won't do it. So we use
// an orthographic camera with a viewport of
// 48 meters in width and 32 meters in height.
// We also position the camera so that it
// looks at (0,16) (that's where the middle of the
// screen will be located).
camera = new OrthographicCamera(48, 32);
camera.position.set(0, 16, 0);
然后,按照常规方式建立动画精灵。例子中是建立了一块渲染区域(TextureRegion)。
是时候让精灵动起来了,程序在render方法里,对精灵赋予了box2d的一系列同步数据,包括位移,大小,以及旋转角度。
for (int i = 0; i < boxes.size(); i++) {
Body box = boxes.get(i);
Vector2 position = box.getPosition(); // that's the box's center position
float angle = MathUtils.radiansToDegrees * box.getAngle(); // the rotation angle around the center
batch.draw(textureRegion, position.x - 1, position.y - 1, // the bottom left corner of the box, unrotated
1f, 1f, // the rotation center relative to the bottom left corner of the box
2, 2, // the width and height of the box
1, 1, // the scale on the x- and y-axis
angle); // the rotation angle
}
上面程序是一个什么效果呢?我们也说过,这种方式是以m为单位的,所以,在480x320的机器上1m就相当于10px,我们的程序,画了一个以
((position.x-1)*10,(position.y-1)*10) 为起始点,长度为20px的正方形精灵。当然,我们以正方形的中心为旋转中心,给予精灵一个与box2d相同的旋转角度。
这种方式是可以工作的,也比较容易理解。但是,在实际应用中,会有很多问题。
第一,我们的图像和精灵,通常是以像素px来设计的。我们建立的照相机,也希望以像素px来进行操作。这样就统一了单位,就可以所见即可得的来设计我们的游戏。
第二,对于不规则的box2d图形,旋转中心,大小,角度的同步,以上方法不见得能够非常简洁
第三,每个精灵的操作,都需要这么缩一下,实在是冗余
鉴于多种不方便的操作,以像素为基准的方式应该更形象一些。
所以以像素px为单位的方式建立的照相机貌似如下:
camera = new OrthographicCamera(480, 320);
camera.position.set(240, 160, 0);
精灵,该多大还是多大,我们把box2d的body建立根据精灵来就可以了。
建立一个同步类 PhysicalObject ,建立以下变量:
/**
* the world body , call it directly . its not null
*/
public Body body ;
/**
* the object such as a sprite, or its a AnimationSprite
*/
public AdvanceSprite object;
/**
* This is a very important synchronous variables.
* Used to box2d and animation will sync up. At the same time,
* can draw box2d debugging interface.
*/
public static final float RADIO = 32.00000000f;
/**
* The default restitution of the rigid body
* @see com.badlogic.gdx.physics.box2d.FixtureDef#restitution
*/
public static final float DEFAULT_restitution = 0.3f;
/**
* The default angularDamping of the rigid body
* @see com.badlogic.gdx.physics.box2d.BodyDef#angularDamping
*/
public static final float DEFAULT_angularDamping = (float) (Math.PI / 2);
/**
* The default linerDamping of the rigid body
* @see com.badlogic.gdx.physics.box2d.BodyDef#linearDamping
*/
public static final float DEFAULT_linearDamping = 0.3f;
/**
* The default Friction of the rigid body
* @see com.badlogic.gdx.physics.box2d.FixtureDef#friction
*/
public static final float DEFAULT_friction = 0.4f;
/**
* The default density of the rigid body
* @see com.badlogic.gdx.physics.box2d.FixtureDef#density
*/
public static final float DEFAULT_density = 1f;
/**
* if the physical object is recycled
*/
private boolean visiable = false;
/**
* scale
*/
protected float scale = 1.0f;
protected Vector2 offset = new Vector2();
上一段代码:
protected void init(){
if(null == this.body || null==this.object) {
Gdx.app.error("C2d", "the body and the sprite must be not null");
System.exit(0);
}
this.body.setSleepingAllowed(true);
this.body.setAwake(false);
this.body.setActive(false);
float shapeHalfWidth = this.object.getWidth()/2;
float shapeHalfHeight = this.object.getHeight()/2;
this.scale = this.object.getScaleX();
final MassData massData = this.body.getMassData();
this.object.setOrigin(-massData.center.x*RADIO+shapeHalfWidth,-massData.center.y*RADIO+shapeHalfHeight);
offset.set(-massData.center.x*RADIO+shapeHalfWidth,-massData.center.y*RADIO+shapeHalfHeight);
/* move it to the sprite's positon */
//fix bug: instead of use the (-640,-640) out screen vector . we
//use the sprite's position so the when the init method called again and again,
//its no effect to the whole pysicalObject
this.body.setTransform(
new Vector2(this.object.getX(),this.object.getY()).add(offset).mul(1/RADIO),
object.getRotation()*MathUtils.degreesToRadians);
}
设计这个类的初衷,是我们想要以操作精灵的方式来操作PhysicalObject,也就是,我们不管圆形矩形还是不规则多边形,都以它在世界中的左下角,也就是(0,0)坐标来进行绘制,当物体旋转的时候,我们依然可以围绕它的中心进行旋转,当然这个动作的同步是隐藏的。
你可以像操作精灵一样操作物理物体,而不用担心其同步问题。
注意初始化方法里。我们有一个非常重要的变量 offset.它保存了物理body的中心和精灵的位置之间的偏移度,这个偏移度在物理body是不规则图形的时候尤其中哦纲要,在圆形和矩形中,偏移量为0.这个偏移量是以MassData来进行计算的。
接下来是最重要的渲染部分,我们有一个非常重要的同步函数,如下:
private void syncObjectAndBody(){
final Vector2 position = body.getPosition();
this.object.setPosition(position.x*PhysicalObject.RADIO -offset.x,position.y*PhysicalObject.RADIO -offset.y);
this.object.setRotation(MathUtils.radiansToDegrees * this.body.getAngle());
}
RADIO是为了协调精灵和box2d设置的同步比率,如这里的RADIO=32意思就是1m代表32px。同样的,位置和角度的设定依然是根据物理body的,在计算位置的时候,别忘了加上它的偏移量。
这样,我们就可以用像素px来同步所有的物理操作了。
如下两个函数告诉你怎么根据精灵来得到圆形body的半径和矩形body的高宽。
/** get the the box shape due to the Sprite object */
public static Vector2 getBoxShapeVector(Sprite object){
return newVector2(object.getWidth()*object.getScaleX(),object.getHeight()*object.getScaleY()).mul(1/PhysicalObject.RADIO/2);
}
/** get the radius of the circle shape due to the Sprite object */
public static float getCircleShapeRadius(Sprite object){
return object.getWidth() * object.getScaleX()/PhysicalObject.RADIO/2;
}