Box2D简单介绍

Box2D 是一个用于游戏的 2D 刚体仿真库, 是用可移植的C++写成的。程序员可以在他们的游戏里使用它,它可以使物体的运动更加真实,并让游戏世界看起来更具交互性。
Box2D 就是用物理学的方法,推导出那游戏世界物体的位置,角度等数据。而 Box2D 也仅仅推导出数据,至于得到数据之后怎么处理就是程序员自己的事情了。

Box2D单位

Box2D 使用浮点数,所以必须使用公差来保证它正常工作。这些公差已经被调谐得适合米-千克-秒(MKS)单位制。尤其是, Box2D 已被调谐得能良好地处理 0.1 到 10 米之间的移动物体。这意味着从罐头盒到公共汽车大小的对象都能良好地工作。静态的物体就算大到 50 米都没有问题。作为一个 2D 物理引擎,使用像素作为单位是很诱人的。但很不幸,那将导致不良的模拟,也可能会造成古怪的行为。一个 200 像素长的物体在 Box2D 看来就有 45 层建筑那么大。

注意 Box2D已被调谐至MKS单位。移动物体的尺寸应该保持在大约0.1到10米之前。 不要使用像素!!!!不要使用像素!!!!不要使用像素!!!!
Box2D使用的弧度,而不是度。物体的旋转角度以弧度方式存储,并可以无限放大


Box2D核心概念

形状(shape)
形状是一个 2D 的几何对象。例如圆或多边形。

刚体(rigid body)
一块十分坚硬的物质,它上面的任何两点之间的距离都是完全不变的。它们就像钻石那样坚硬。在后面的讨论中,我们用物体(body)来指代刚体。

夹具(fixture)
夹具将形状绑定到物体上,并添加密度(density)、摩擦(friction)和恢复(restitution)等材料特性。夹具还将形状放入碰撞系统(碰撞检测(Broad Phase))中,以使之能与其他形状相碰撞。
(译注:一个物体和另一物体碰撞, 碰撞后速度和碰撞前速度的比值会保持不变,这比值就叫恢复系数。 )
(译注: Broad Phase 是碰撞检测的一个子阶段, 将空间分割, 每个空间对应一个子树, 物体就放到树中,不同子树内的物体不可能相交不用去计算, 在同一个子树由对应的算法再计算出接触点等信息。因为这是远距碰撞检测,就叫 Broad Phase, 接下来还有 Narrow Phase。 )

约束(constraint)
约束(constraint)就是消除物体自由度的物理连接。一个 2D 物体有 3 个自由度(两个平移坐标和一个旋转坐标)。如果我们把一个物体钉在墙上(像摆锤那样),那我们就把它约束到了墙上。这样,此物体就只能绕着这个钉子旋转,因此这个约束消除了它 2 个自由度。

接触约束(contact constraint)
一种防止刚体穿透,并模拟摩擦和恢复的特殊约束。你不必创建接触约束,它们会自动被 Box2D创建。

关节(joint)
它是一种用于把两个或更多的物体固定到一起的约束。 Box2D 支持若干种关节类型: 旋转、棱柱、距离等等。有些关节拥有限制(limits)和马达(motors)。
关节限制(joint limit)关节限制限定了关节的运动范围。例如,人类的胳膊肘只能做某一范围角度的运动。

关节马达(joint motor)
关节马达能依照关节的自由度来驱动所连接的物体。例如,你可以使用马达来驱动胳膊肘的旋转。

世界(world)
物理世界就是相互作用的物体,夹具和约束的集合。 Box2D 支持创建多个世界,但这通常是不必要或不推荐的。

求解器(solver)
物理世界使用求解器来推算时间,求解接触和关节约束。 Box2D 的求解器是一种高性能的迭代求解器,它会顺序执行 N 次,这里的 N 是约束的个数。
(译注:即算法的复杂度为 O( N)。 )

连续碰撞(continuous collision)
求解器使用时域上的离散时间步来推算物体状态。如果没有特殊处理的话,这会导致隧穿效应。

工厂和定义

快速内存管理在Box2D API的设计中担当了一个中心角色。所以创建一个Body或一个Joint时,需要调用World的工厂函数,不应该以别的方式为这些类型分配内存。

public Body createBody (BodyDef def)
public Joint createJoint (JointDef def)

对应的销毁函数:

public void destroyBody (Body body)
public void destroyJoint (Joint joint)

当你创建物体(Body)或者关节(Joint)时,需要提供定义BodyDef/JointDef。这些定义包含了创建物体或者关节时所需要的所有信息。使用这样的方法,我们能够预防构造错误,保持较少的函数参数数量,提供有意义的默认值。

因为Fixture必须有父Body,所以必须得使用能够Body的工厂方法来创建并销毁它们。

public Fixture createFixture (FixtureDef def)
public Fixture createFixture (Shape shape, float density)

上面第二个时直接创建Static Body。

Hello Box2D

  1. 每个Box2D程序开始时都会创建一个World对象。World是个物理枢纽(physics hub),用于管理内存、对象和模拟。可以在堆栈、堆或者数据区里创建出World。
    创建Box2D的World很简单。首先,我们定义重力矢量。
    vector gravity = new Vector(0, -9.8f);
    world = new World(gravity, true);
    Box2D默认Y轴是Up的,所以需要设置 -9.8f。true表示不模拟static body,以节约内存。
  2. 创建刚体(地面盒)
  • 用位置(position),阻尼(damping)等BodyDef来定义body
  • 用world来创建body
  • 用形状(shape),摩擦(friction)、密度(density)等FixtureDef来定义fixture
  • 在Body上创建Fixture’

在代码中有代码:groundBox.setAsBox(5.0f, 2.0f);
setAsBox方法接收半个宽度和半个高度作为参数。因此在这种情况下,Body就是10米宽, 4米高。当物体的大小跟真实世界一样时,Box2D工作良好。由于浮点算法的局限性,使用Box2D模拟冰川和沙尘运动并不是一个良好的主意。

  1. Box2D使用了一种名为积分器(integrator)的数值算法。积分器在离散的时间点上模拟物理方程。同时我也需要为Box2D选取一个时间步(time step)。通常来说用于游戏的物理引擎需要至少60Hz的速度,也就是 1/60秒的时间步一个变化的时间步会导致变化的结果,这会给调试带来困难。所以不要把时间步关联到帧频。直接就是这么这样写:

float timeStep = 1.0f / 60.0f;
word.step(timeStep, 6, 2);
Box2D 模拟物体最大的速度为 n 2 (此处 n = 60),即 timeStep的倒数 2。 此处为例,我们可以知道物体的最大速度为120m/s(432km/h)

创建带Box2D的Libgdx工程

  1. 第一种方法是在创建的时候,选择Box2D,如图:
  2. 第二种修改build.gradle文件,可以参考下面代码
project(":desktop") {
    apply plugin: "java"


    dependencies {
        compile project(":core")
        compile "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion"
        compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
        compile "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop"
    }
}

project(":core") {
    apply plugin: "java"


    dependencies {
        compile "com.badlogicgames.gdx:gdx:$gdxVersion"
        compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
    }
}

project(":android") {
    apply plugin: "android"

    configurations { natives }

    dependencies {
        compile project(":core")
        compile "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion"
        natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi"
        natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a"
        natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a"
        natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86"
        natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64"
        compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
        natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-armeabi"
        natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-armeabi-v7a"
        natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-arm64-v8a"
        natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86"
        natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86_64"
    }
}

下面直接让苹果降落吧:

public class LearnBox2d extends ApplicationAdapter {

    /**
     * World掌管Box2d创建所有物理实体,动态模拟,异步查询。也包含有效的内存管理工具
     */
    World world;
    /**
     * Box2d只是模拟世界,并不会像屏幕上(Stage之类上)添加任何显示对象,但是Libgdx提供了Box2DDebugRenderer
     * 供我们模拟,里面包含了ShapeRenderer,进行模拟调试
     */
    Box2DDebugRenderer box2DDebugRenderer;
    Body body;
    OrthographicCamera camera;

    private static final float SCENE_WIDTH = 12.8f; // 13 metres wide
    private static final float SCENE_HEIGHT = 7.2f; // 7 metres high

    @Override
    public void create () {
        // 在X轴方向上受力为0, 在Y轴方向上受到向下的重力 9.8
        world = new World(new Vector2(0.0f, -9.8f), true);
        box2DDebugRenderer = new Box2DDebugRenderer();

        /** BodyDef 定义创建刚体所需要的全部数据。可以被重复使用创建不同刚体,BodyDef之后需要绑定Shape **/
        BodyDef bodyDef = new BodyDef();
        /** 动态刚体,受力之后运动会发生改变。 默认创建的是静态BodyType.StaticBody **/
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.position.set(SCENE_WIDTH / 2, SCENE_HEIGHT / 2);
//        bodyDef.linearVelocity.set(2.0f, 0f);

        body = world.createBody(bodyDef);

        // 刚体没有任何显示,Shape主要用来显示和做碰撞检测。
        PolygonShape shape = new PolygonShape(); // 多边形
        shape.setAsBox(0.5f, 0.5f);  // 半个宽度和半个高度作为参数,这样盒子就是一米宽,一米高

        // Fixture(夹具),将形状绑定到物体上,并添加密度(density)、摩擦(friction)、恢复(restitution)等材料特性
        // 还将形状放入到碰撞检测系统中(Broad Phase),以使之能与其它形状相碰撞
        // 一个物体和另一个物体碰撞,碰撞后速度和碰撞前速度的比值会保持不变,这比值就叫恢复系数
        FixtureDef fixtureDef = new FixtureDef();

        /** 当你使用 fixture 向 body 添加 shape 的时候, shape 的坐标对于 body 来说就变成本地的了。因此
         当 body 移动的时候, shape 也一起移动。 fixture 的世界变换继承自它的父 body。 fixture 没有独立
         于 body 的变换。所以我们不需要移动 body 上的 shape。不支持移动或修改 body 上的 shape。原因很简单:
         形状发生改变的物体不是刚体,而 Box2D 只是个刚体引擎。 Box2D 所做的很多假设都是基于刚体模型的。
         如果这一条被改变的话,很多事情都会出错。
         **/
        fixtureDef.shape = shape;
        fixtureDef.density = 2;
        fixtureDef.restitution = 1.0f;  // 恢复系数,物理受到反作用力的运动情况,值越大反向运动速度越快

        body.createFixture(fixtureDef);
        shape.dispose();

        camera = new OrthographicCamera(SCENE_WIDTH, SCENE_HEIGHT);
        camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
        camera.update();

        createGround();

        Gdx.app.log("FY", "x="+body.getWorldCenter().x);
    }

    @Override
    public void render () {
        world.step(1/45f, 6, 2);

        Gdx.gl.glClearColor(0.39f, 0.58f, 0.92f, 1.0f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        // camera指定了Worl的大小
        box2DDebugRenderer.render(world, camera.combined);
    }

    @Override
    public void dispose() {
        world.dispose();
        box2DDebugRenderer.dispose();
    }

    private void createGround() {

        float halfGroundWidth = SCENE_WIDTH;
        float halfGroundHeight = 0.5f;  // 0.5 * 2 = 1meter

        // Create a static body definition, 没有速度,不接受力和冲量的作用
        BodyDef groundBodyDef = new BodyDef();
        // default is StaticBody
//        groundBodyDef.type = BodyDef.BodyType.StaticBody;

        // Set the ground position (origin)
        groundBodyDef.position.set(halfGroundWidth * 0.5f, halfGroundHeight);

        // Create a body from the defintion and add it to the world
        Body groundBody = world.createBody(groundBodyDef);

        // (setAsBox takes half-width and half-height as arguments)
        PolygonShape groundBox = new PolygonShape();
        groundBox.setAsBox(halfGroundWidth * 0.5f, halfGroundHeight);

        /** Body是没有形状的,必须绑定具体形状才能显示,一般有2个方法,如下:
         *  1. createFixture (FixtureDef def) 创建带有摩擦力,恢复系数等材料特性的Body
         *  2. createFixture (Shape shape, float density) 直接根据形状创建Body,不带有材料特性,density = 0
         * **/
        groundBody.createFixture(groundBox, 1.0f);
        // Free resources
        groundBox.dispose();

    }
}

box2d使用 box2d是什么_box2d