文章中涉及的很多概念,都是来自《Box2D中文手册》。有统一的解释方便理解
形状不知道物体的存在,并可独立于物理模拟而被使用。因此 Box2D 提供 Fixture 类,用于将形状附加到物体上。一个物体可以有零个或多个 fixture。拥有多个 fixture 的物体有时被叫做组合物体。 fixture具有下列属性:
关联的形状broad-phase 代理密度(density)、摩擦(friction)、恢复(restitution)碰撞筛选标记(collision filtering flags)指向父物体的指针传感器标记(sensor flag)创建夹具,必须先初始化和夹具定义FixtureDef,并将定义传到父物体中, 先看夹具定义:
/** A fixture definition is used to create a fixture. This class defines an abstract fixture definition. You can reuse fixture * definitions safely. * @author mzechner */ public class FixtureDef { /** The shape, this must be set. The shape will be cloned, so you can create the shape on the stack. */ public Shape shape; /** The friction coefficient, usually in the range [0,1]. **/ public float friction = 0.2f; /** The restitution (elasticity) usually in the range [0,1]. **/ public float restitution = 0; /** The density, usually in kg/m^2. **/ public float density = 0; /** A sensor shape collects contact information but never generates a collision response. */ public boolean isSensor = false; /** Contact filtering data. **/ public final Filter filter = new Filter(); }上面列出的FixtureDef属性,其实就是上面文字描述的代码形式,在实际操作中我们是这样创建的: FixtureDef fixtureDef = new FixtureDef(); fixtureDef.shape = circleShape; fixtureDef.density = 1.0f; fixtureDef.restitution = 0.6f; body.createFixture(fixtureDef); 这会创建 fixture,并将它附加到物体之上。不需要保存 fixture 的指针,因为当它的父物体被摧毁时, fixture 也会自动被摧毁。 可以在单个物体上创建多个 fixture。 可以摧毁父物体上的 fixture,来模拟一个可分裂开的物体。
fixture 的密度用来计算父物体的质量属性。密度值可以为零或者是正数。所有的 fixture 都应该使用相似的密度,这样做可以改善堆叠稳定性。 当设置密度的时候,物体的质量不会立即改变。你必须调用 resetMassData ,使之生效。 fixture.setDensity (float density) body.resetMassData ()
摩擦可以使对象逼真地沿其它对象滑动。摩擦参数通常会设置在 0 到 1 之间,但也可是任意的非负数, 0 意味着没有摩擦, 1 会产生强摩擦。
恢复可以使对象弹起。恢复的值通常设置在 0 到 1 之间。想象一个小球掉落到桌子上,值为 0 表示着小球不会弹起, 这称为非弹性碰撞。值为 1 表示小球的速度跟原来一样,只是方向相反,这称为完全弹性碰撞。
碰撞筛选是为了防止某些 fixture 之间发生碰撞。比如,创造了一个骑自行车的角色。希望自行车与地形之间有碰撞,角色与地形有碰撞,但你不希望角色和自行车之间发生碰撞 (因为它们必须重叠)。 Box2D 通过种群和分组来支持这样的碰撞筛选。 Box2D 支持 16 个种群。任意 fixture 你都可以指定它属于哪个种群。你还可以指定这个 fixture 可以和其它哪些种群发生碰撞。例如,你可以在一个多人游戏中指定玩家之间不会碰撞,怪物之间也不会碰撞,但是玩家和怪物会发生碰撞。这是通过掩码来完成的,例如: playerFixtureDef.filter.categoryBits = 0x0002 monsterFixtureDef.filter.categoryBits = 0x0004 playerFixtureDef.filter.maskBits = 0x0004 monsterFixtureDef.filter.maskBits = 0x0002 下面是产生碰撞的规则:
short catA = fixtureA.filter.categoryBits ; short maskA = fixtureA.filter.maskBits; short catB = fixtureB.filter.categoryBits short maskB = fixtureB.filter.maskBits; if((catA & catB) != 0 && (catB & maskA) != 0) { // fixtures can collide }碰撞分组让你指定一个整数的组索引。你可以融同一个组的所有fixture总是相互碰撞(正索引)或者永远不碰撞(负索引)。组索引通常用于一些以某种方式关联的事物,就像自行车的那些不见。在下面例子中,fixture1和fixture2总是碰撞,而fixture3和fixture4永远不会碰撞
fixture1Def.filter.groupIndex = 2; fixture2Def.filter.groupIndex = 2; fixture3Def.filter.groupIndex = -8; fixture4Def.filter.groupIndex = -8;如果组索引不同,碰撞筛选会按照种群和掩码来进行。换句话说,分组筛选和种群筛选相比,具有更高的优选级。 注意在Box2D中还有其它的碰撞筛选,这里有一个列表:
static上的fixture只会与dynamic物体上的fixture发生碰撞kinematic物体只会和dynamic物体发生碰撞同一个物体上的fixture永远不会相互碰撞如果两个物体用关节连接起来,物体上面的fixture可以选择启用或者禁止它们之间的相互碰撞有时候游戏逻辑需要判断两个 fixture 是否相交,而不想有碰撞反应。这可以通过传感器(sensor)来完成。传感器也是个 fixture,但它只会侦测碰撞,而不产生其它反应。 可以将任意 fixture 标记为传感器。传感器可以是 static、 kinematic 或 dynamic 的。记住,每个物体上可以有多个 fixture,传感器和实体 fixture 是可以混合存在的。而且,只有至少一个物体是dynamic 的,传感器才会产生接触事件,而 kinematic 与 kinematic 、 kinematic 与 static ,或者static 与 static 之间都不会产生接触事件。
public class BodyShapeA extends ApplicationAdapter { World world; Box2DDebugRenderer box2DDebugRenderer; OrthographicCamera camera; float scene_width = 12.8f; float scene_height = 7.2f; @Override public void create() { world = new World(new Vector2(0.0f, -9.8f), true); box2DDebugRenderer = new Box2DDebugRenderer(); camera = new OrthographicCamera(scene_width, scene_height); camera.position.set(scene_width / 2, scene_height / 2, 0); camera.update(); createStaticBox(); Gdx.input.setInputProcessor(new InputHandA()); } @Override public void render() { world.step( 1/ 60f, 6, 2); Gdx.gl.glClearColor(0.39f, 0.58f, 0.92f, 1.0f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); box2DDebugRenderer.render(world, camera.combined); } @Override public void dispose() { world.dispose(); box2DDebugRenderer.dispose(); } private void createStaticBox() { // 创建地面 createBorderBox(scene_width * 0.5f, 0.2f, scene_width * 0.5f, 0.2f, 0); // 创建倾斜矩形,这里创建矩形,然后用angle来控制 createBorderBox(3.0f, 5.0f, 0.1f, 1.3f, MathUtils.PI / 3); // 创建池子 createBorderBox(5.0f, 2.5f, 0.1f, 1.0f, 0); createBorderBox(9.0f, 2.5f, 0.1f, 1.0f, 0); createBorderBox(7.0f, 1.6f, 2.1f, 0.1f, 0); } private void createBorderBox(float px, float py, float w, float h, float angle) { BodyDef bodyDef = new BodyDef(); bodyDef.position.set(px, py); bodyDef.angle = angle; Body body = world.createBody(bodyDef); PolygonShape polygonShape = new PolygonShape(); polygonShape.setAsBox(w, h); body.createFixture(polygonShape, 0); polygonShape.dispose(); } private void createCircle(float x, float y) { BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.DynamicBody; bodyDef.position.set(x, y); Body body = world.createBody(bodyDef); CircleShape circleShape = new CircleShape(); circleShape.setRadius(0.2f); FixtureDef fixtureDef = new FixtureDef(); fixtureDef.shape = circleShape; fixtureDef.density = 1.0f; fixtureDef.restitution = 0.6f; body.createFixture(fixtureDef); circleShape.dispose(); } private class InputHandA extends InputAdapter { @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { // 在屏幕点击出创建响应的圆形Body Vector3 vector3 = new Vector3(screenX, screenY, 0); camera.unproject(vector3); createCircle(vector3.x, vector3.y); return true; } } }