2.Libgdx扩展学习之Box2D_刚体和形状

Posted zqiang_55

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.Libgdx扩展学习之Box2D_刚体和形状相关的知识,希望对你有一定的参考价值。

文章中涉及的很多概念,都是来自《Box2D中文手册》。有统一的解释方便理解。

物体(刚体/Body)

概念介绍


物体具有位置和速度。可以将力(forces)、扭矩(torques)、冲量(impulses)应用到物体上。 物体可以是静态的(static)、运动但不受力的(kinematic)或动态的(dynamic)。这是物体的类型定义:
1.Static Body
static 物体在模拟时不会运动,就好像它具有无穷大的质量。在 Box2D 内部,会将 static 物体的质量和质量的倒数存储为零。 static 物体可以让用户手动移动。它的速度为零,另外也不会和其它static 或 kinematic 物体相互碰撞。
2. Kinematic Body
kinematic 物体在模拟时以一定的速度运动,但不受力的作用。它们可以让用户手动移动,但通常的做法是设置一定的速度来移动它。 kinematic 物体的行为表现就好像它具有无穷大的质量,Box2D 将它的质量和质量的倒数存储为零。
3. Dynamic Body
dynamic 物体被完全模拟。它们可以让用户手动移动,但通常它们都是受力的作用而运动。dynamic 物体可以和其它所有类型的物体相互碰撞。 dynamic 物体的质量总是有限大的,非零的。如果你试图将它的质量设置为零,它会自动地将质量修改成一千克,并且它不会转动。

物体定义


在创建物体之前你需要先创建物体定义(BodyDef)。物体定义含有创建并初始化物体所需的数据。Box2D 会从物体定义中复制数据,并不会保存它的指针。这意味着你可以重复使用同一个物体定义去创建多个物体。
Libgdx也提供给我们一个Java版本的BodyDef,现在可以先简单浏览一下代码:

public class BodyDef 
    /** The body type. static: zero mass, zero velocity, may be manually moved kinematic: zero mass, non-zero velocity set by user,
     * moved by solver dynamic: positive mass, non-zero velocity determined by forces, moved by solver */
    public enum BodyType 
        StaticBody(0), KinematicBody(1), DynamicBody(2);

        private int value;

        private BodyType (int value) 
            this.value = value;
        

        public int getValue () 
            return value;
        
    ;

    /** The body type: static, kinematic, or dynamic. Note: if a dynamic body would have zero mass, the mass is set to one. **/
    public BodyType type = BodyType.StaticBody;

    /** The world position of the body. Avoid creating bodies at the origin since this can lead to many overlapping shapes. **/
    public final Vector2 position = new Vector2();

    /** The world angle of the body in radians. **/
    public float angle = 0;

    /** The linear velocity of the body's origin in world co-ordinates. **/
    public final Vector2 linearVelocity = new Vector2();

    /** The angular velocity of the body. **/
    public float angularVelocity = 0;

    /** Linear damping is use to reduce the linear velocity. The damping parameter can be larger than 1.0f but the damping effect
     * becomes sensitive to the time step when the damping parameter is large. **/
    public float linearDamping = 0;

    /** Angular damping is use to reduce the angular velocity. The damping parameter can be larger than 1.0f but the damping effect
     * becomes sensitive to the time step when the damping parameter is large. **/
    public float angularDamping = 0;

    /** Set this flag to false if this body should never fall asleep. Note that this increases CPU usage. **/
    public boolean allowSleep = true;

    /** Is this body initially awake or sleeping? **/
    public boolean awake = true;

    /** Should this body be prevented from rotating? Useful for characters. **/
    public boolean fixedRotation = false;

    /** Is this a fast moving body that should be prevented from tunneling through other moving bodies? Note that all bodies are
     * prevented from tunneling through kinematic and static bodies. This setting is only considered on dynamic bodies.
     * @warning You should use this flag sparingly since it increases processing time. **/
    public boolean bullet = false;

    /** Does this body start out active? **/
    public boolean active = true;

    /** Scale the gravity applied to this body. **/
    public float gravityScale = 1;
物体类型

前面介绍过,物体有3中类型: static、kinematic、和dynamic。在创建时就应该确定号物体类型,否则以后修改,代价很高
bodyDef.type = BodyType.Dynamic

位置和角度

物体定义提供了一个在创建时初始化位置的机会。 这比在World原点创建物体后再移动到某个位置更高效。

不要在原点创建物体后再移动它。如果在原点上同时创建了几个物体,性能会很差。

阻尼

阻尼用于减小物体在世界中的速度。阻尼跟摩擦有所不同,摩擦仅在物体有接触的时候才会发生。阻尼并不能取代摩擦,往往这两个效果需要同时使用。
阻尼参数的范围可以在 0 到无穷大之间, 0 表示没有阻尼,无穷大表示满阻尼。通常来说,阻尼的值应 该在 0 到 0.1 之间。通常我不使用线性阻尼, 因为它会使物体看起来有点漂浮。
bodyDef.linearDamping = 0.0f;
bodyDef.angularDamping = 0.01f;
阻尼类似稳定性与性能, 在值较小的时候阻尼效应几乎不依赖于时间步,值较大的时候阻尼效应将随着时间步而变化。如果你使用固定的时间步(推荐)这就不是问题了。

休眠参数

当 Box2D 确定一个物体(或一组物体)已停止移动时,物体就会进入休眠状态。休眠物体只消耗很小的 CPU 开销。如果一个醒着的物体接触到了一个休眠中的物体,那么休眠中的物体就会醒过来。当物体上的关节或触点被摧毁的时候,它们同样会醒过来。你也可以手动地唤醒物体。
通过物体定义,你可以指定一个物体是否可以休眠,或者创建一个休眠的物体。
bodyDef.allowSleep = true;
bodyDef.awake = true;

固定旋转

想让一个刚体,比如某个角色,具有固定的旋转角。这样物体即使在负载下,也不会旋转。你可以设置 fixedRotation 来达到这个目的:
bodyDef.fixedRotation = true;
固定旋转标记使得转动惯量和它的倒数被设置成零。

子弹

游戏模拟通常以一定帧率(frame rate)产生一系列的图片。这就是所谓的离散模拟。在离散模拟中,在一个时间步内刚体可能移动较大距离。如果一个物理引擎没有处理好大幅度的运动,你就可能会看见一些物体错误地穿过了彼此。这被称为隧穿效应(tunneling)。
默认情况下, Box2D 会通过连续碰撞检测(CCD)来防止动态物体穿越静态物体。因此在 Box2D 中,高速移动的物体可以标记成子弹(bullet)。子弹跟 static 或者 dynamic 物体之间都会执行 CCD。你需要按照游戏的设计来决定哪些物体是子弹。如果你决定一个物体应该按照子弹去处理,可使用下面的设置。子弹标记只影响 dynamic 物体。
bodyDef.bullet = true;

活动状态

你可能希望创建一个物体并不参与碰撞和动态模拟。这状态跟休眠有点类似,但并不会被其它物体唤醒,它上面的 fixture 也不会 被放到 broad-phase 中。也就是说,物体不会参于碰撞检测,光线投射(ray casts)等等。你可以创建一个非活动的物体,之后再激活它。
bodyDef.active = true;
关节也可以连接到非活动的物体。但这些关节并不会被模拟。你要小心,当激活物体时,它的关节不会被扭曲(distorted)。
注意,激活一个物体和重新创建一个物体的开销差不多。因此你不应该在流世界( streaming worlds)中使用激活,而应该用创建和销毁来节省内存。
(译注: streaming worlds 是指该世界中的大多数物体是动态创建的,而不是一开始就有的。 )

重力因子

可以使用重力因子来调整单个物体上的重力。这需要足够的细心,增加的重力会降低稳定性。
bodyDef.gravityScale = 0.0f;

使用物体


创建完一个物体之后,你可以对它进行许多操作。其中包括设置质量属性,访问其位置和速度,施加力,以及转换点和向量,即BodyDef里面定义的数据都可以后期更改。

质量数据

每个物体都有质量(标量)、质心(二维向量)和转动惯性(标量)。对于 static 物体,它的质量和转动惯性都被设为零。当物体设置成固定旋转(fixed rotation),它的转动惯性也是零。

public class MassData 
    /** The mass of the shape, usually in kilograms. **/
    public float mass;  // 质量(标量)

    /** The position of the shape's centroid relative to the shape's origin. **/
    public final Vector2 center = new Vector2(); // 质心(二维向量)

    /** The rotational inertia of the shape about the local origin. **/
    public float I; // 转动惯性(标量)

通常情况下,当 fixture 添加到物体上时,物体的质量属性会自动地确定。你也可以在运行时(runtime)调整物体的质量。当你有特殊的游戏方案需要改变质量时,可以这样做。
setMassData (MassData data)

形状(碰撞检测)


形状描述了可相互碰撞的几何对象,它的使用独立于物理模拟。知道如何创建shape,并将之附加到刚体上。
Shape 是个基类, Box2D 的各种形状都实现了这个基类。此基类定义了几个函数:

  • 判断一个点与形状是否有重叠(碰撞)
  • 在形状上执行光线投射(ray cast)
  • 计算形状的AABB
  • 计算形状的质量
    每个形状都有成员变量:类型(type)和半径(radius)。对于多边形,半径也是有意义的。

    注意 shape并不知道body,也与力学系统无关。Shape 采用一种紧凑格式来进行存储,这种格式经过尺寸和性能的优化。因此, shape 并不方便移动,你必须通过手动的设置形状顶点来移动 shape。然而,当使用 fixture 将 shape 添加到 body 上之后, shape 就会和他的宿主 body 一起移动

  • 当一个shape没有添加到body上时,它的顶点用世界坐标系来表示。

  • 当一个shape添加到body上时,它的顶点用局部坐标系来表示。
        // Body位置为(1, 1), 多边形和Body绑定在一起,那边座标都要+1
        BodyDef bodyDef = new BodyDef();
        bodyDef.position.set(1, 1);
        Body body1 = world.createBody(bodyDef);

        PolygonShape polygonShape = new PolygonShape();

        Vector2[] vector2s = new Vector2[4];
        vector2s[0] = new Vector2(0, 0);
        vector2s[1] = new Vector2(0.0f, 1.0f);
        vector2s[2] = new Vector2(1.0f, 1.0f);
        vector2s[3] = new Vector2(1.0f, 0.0f);
        polygonShape.set(vector2s);
        body1.createFixture(polygonShape, 0);
圆形

圆形有位置和半径。圆形是实心的,没有办法使圆形变成空心。
CircleShape circleShape = new CircleShape();
circleShape.setRadius(0.5f);
circleShape.setPosition(new Vector2(3.0f, 3.0f));

多边形

Box2D 的多边形是实心的凸(Convex)多边形。多边形是实心的,而不是空心的。一个多边形必须有 3 个或以上的顶点。多边形的顶点以逆时针( counter clockwise winding, CCW)的顺序存储。
Vector2 vetices[3]
vertices[0] = new Vector2(0.0f, 0.0f)
vertices[1] = new Vector2(1.0f, 0.0f)
vertices[2] = new Vector2(0.0f, 1.0f)
PolygonShape polygon = new PolygonShape()
polygon.set(vetices)
另外,多边形有一些方便的方法来创建box
public void setAsBox (float hx, float hy)

边框形状(EdgeShape)

边框形状由一些线段组成。可辅助为你的游戏创建一个形状自由的静态环境。边框形状的主要限制在于它们能够与圆形和多边形碰撞,但它们之间却不会碰撞。 Box2D 使用的碰撞算法要求两个碰撞物体中至少有一个有体积。边框形状没有体积。所以边框形状之间的碰撞是不可能的。
EdgeShape edgeShape = new EdgeShape();
edgeShape.set(5.4f, 3.0f, 7.3f, 2.0f);

链接形状(ChainShape)

链接形状提供了一种有效的方式,来将许多边框连接在一起,用以构建你的静态游戏世界。链接形状自动消除幽灵碰撞,并提供两侧的碰撞。

/**
 * 主要介绍刚体(rigid body)和形状(shap)
 *
 * 刚体主要有3类 1. Static Body  静态刚体.零质量,没有运动,不接受反作用力等,可以人为移动
 *  2. Kinematic Body 运动刚体,零质量,求解器(solver)控制速度
 *  3. Dynamic Body 动态刚体,有质量,速度受力影响,求解器控制速度
 *
 *  Shape 主要用作碰撞检测,可以在任何时候创建。不再使用时要销毁 dispose(),比如可以在调用body.createFixture() 之后销毁
 *   1. Circle  圆形
 *   2. Edge
 *   3. Polygon 多边形
 *   4. Chain 链形
 */
public class BodyShape extends ApplicationAdapter 

    World world;
    Box2DDebugRenderer box2DDebugRenderer;
    Body rectBody, circleBody, chainBody, edgeBody;

    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();

        BodyDef bodyDef = new BodyDef();
        // 默认是Static Body,只有定义Dynamic 才会受重力影响
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.position.set(1f, 3f);
//        bodyDef.angularVelocity = 1.0f;
        rectBody = world.createBody(bodyDef);
        circleBody = world.createBody(bodyDef);
        chainBody = world.createBody(bodyDef);
        edgeBody = world.createBody(bodyDef);

        // 正方形
        PolygonShape polygonShape = new PolygonShape();
        polygonShape.setAsBox(0.6f, 0.3f);
        Gdx.app.log("BodyShape", "VertexCount = " + polygonShape.getVertexCount());

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = polygonShape;

        fixtureDef.density  = 2;
        fixtureDef.restitution = 1.0f;
        rectBody.createFixture(fixtureDef);
        polygonShape.dispose();

        CircleShape circleShape = new CircleShape();
        circleShape.setRadius(0.5f);
        circleShape.setPosition(new Vector2(3.0f, 3.0f));

        fixtureDef.shape = circleShape;
        fixtureDef.isSensor = false;
        circleBody.createFixture(fixtureDef);
        circleShape.dispose();

        EdgeShape edgeShape = new EdgeShape();
        edgeShape.set(5.4f, 3.0f, 7.3f, 2.0f);

        fixtureDef.shape = edgeShape;
        edgeBody.createFixture(fixtureDef);
        edgeShape.dispose();

        ChainShape chainShape = new ChainShape();
        float[] ver = 8.0f, 2, 8.9f, 3, 10.4f, 3, 11.4f, 4;
        chainShape.createChain(ver);
//        chainShape.createLoop(ver);

        fixtureDef.shape = chainShape;
        chainBody.createFixture(fixtureDef);
        chainShape.dispose();

        camera = new OrthographicCamera(scene_width, scene_height);
        camera.position.set(scene_width / 2, scene_height / 2, 0);
        camera.update();

        /** 创建测试地面盒子 **/
        createGround();
    

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

    @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);
    

    private void createGround() 
        BodyDef goundBodyDef = new BodyDef();
        goundBodyDef.position.set(scene_width * 0.5f, 0.5f);

        Body groundBody = world.createBody(goundBodyDef);

        PolygonShape groundBox = new PolygonShape();
        groundBox.setAsBox(scene_width * 0.5f, 0.5f);

        groundBody.createFixture(groundBox, 0);
        groundBox.dispose();
    

以上是关于2.Libgdx扩展学习之Box2D_刚体和形状的主要内容,如果未能解决你的问题,请参考以下文章

2.Libgdx扩展学习之Box2D_刚体和形状

5.Libgdx扩展学习之Box2D_刚体的运动和贴图

5.Libgdx扩展学习之Box2D_刚体的运动和贴图

5.Libgdx扩展学习之Box2D_刚体的运动和贴图

4.Libgdx扩展学习之Box2D_创建多边形刚体和圆角矩形

4.Libgdx扩展学习之Box2D_创建多边形刚体和圆角矩形