缩放 Box2D 圆形及其碰撞检测?

Scale Box2D Circle Shape and it's Collision Detection?

如果一个圆圈在启动阶段拉伸并变长(增加高度和减少宽度)如何在缩放时动态调整碰撞形状(在 Box2D 中)?是否可以通过所有步骤的每一帧完全拉伸并返回圆形来完成?

同样的,在弹跳的时候,压扁了一点,需要检测多宽少高。

想象一下,这是一个游戏中的英雄角色,他有点跳投能力,甚至可以在半空中跳起来'double jumps'。但大部分伸长和挤压发生在地面相互作用期间。像这样:

我们有两个问题:

  1. 动态改变夹具形状

    Box2D 支持各种类型的形状,例如:

    • CircleShape(简单)
    • 多边形(简单)
    • ChainShapeEdgeShape(复杂)

    这里的情况取决于你这里需要什么类型的形状。如果 "simple" 类型的形状就足够了,解决方案就很简单,它会是(对于 PolygonShape)类似于

    //! I'm using LibGDX in code examples but you can translate it to other engines
    
    Body body;
    
    ...
    
    //assuming that body has one fixture - if not just keep it's reference and call it directly
    ((PolygonShape) body.getFixtureList().first().getShape() ).setAsBox(hx, hy);
    

    注意这里对于CircleShape you do not even need to cast it because Shape class包含setRadius方法

    如果您需要 "complex" 类型的形状,情况会更复杂,但仍然很容易。你处理它的方式是销毁当前夹具并用另一个形状重新创建它,比如

    Body body;
    FixtureDef fixtureDef;
    
    ...
    
    //creating fixturDef - for further usage (setting friction, density etc...)
    
    ...
    
    //again assuming that body has one fixture - but also can keep reference
    body.destroyFixture( bb1.getFixtureList().first() );
    
    ChainShape shape = new ChainShape();
    //creating shape...
    
    fixtureDef.shape = shape;
    
    body.createFixture(fixtureDef);
    
    //you must dispose shape after fixture creating!
    shape.dispose();
    
  2. 创建椭圆

    虽然 Box2D 支持各种形状但没有椭圆类型,您需要使用一些技术自行生成它。在互联网上描述了很多方法 - 我将向您展示如何生成椭圆作为 ChainShape 实例。

    我将使用 parametric equation for ellipse0 <= t < 2π(以弧度表示)。该方法如下所示:

    //overriding method to handle default STEPS value
    ChainShape createEllipse(float width, float height)
    {
        return createEllipse(width, height, 64);
    }
    
    ChainShape createEllipse(float width, float height, int STEPS)
    {
        ChainShape ellipse = new ChainShape();      
        Vector2[] verts = new Vector2[STEPS];
    
        for(int i = 0; i < STEPS; i++)
        {
            float t = (float)(i*2*Math.PI)/STEPS;
            verts[i] = new Vector2(width * (float)Math.cos(t), height * (float)Math.sin(t));
        }
    
        ellipse.createLoop(verts);
        return ellipse;
    } 
    

    其中:

    • 宽度 - 椭圆的宽度
    • 高度 - 椭圆的高度
    • STEPS - 创建椭圆的点数(更大的 STEPS = 更平滑的椭圆但生成成本更高)

    所以总结一下 - 假设你在模拟过程中知道 body 的大小(因为如果你不知道它是比这个问题更大的话题) 场景将是:

    • 检查我是否应该更换夹具(body 的尺寸是否改变了?
    • 如果
      • 销毁旧夹具
      • 生成新尺寸的新形状
      • 创建新夹具
      • 处理创建的形状

    在代码中它会是这样的:

    //in render method
    if( bodySizeChanged() ) //this method is checking somehow if you should change fixture
    {
        //again assuming that body has one fixture - but also can keep reference           
        body.destroyFixture( body.getFixtureList().first() );
    
        ChainShape ellipse = createEllipse(newWidth, newHeight);
        fixtureDef.shape = ellipse;
    
        body.createFixture(fixtureDef);
        ellipse.dispose();
    }
    

    当然每次都创建新形状不是很有效,更好的方法是在加载时创建一些 collection 形状 Array<ChainShape> 甚至 FixtureDef(例如在 show() 方法)并在渲染方法期间获得所需的 shape/fixtureDef


编辑 - 例如,如果你的动画有一个 collection 精灵,你可以为每个精灵生成另一个 collection 形状 - 但前提是前一个动画帧有另一个尺寸 不改变形状到相同的形状 - 它会是这样的:

    Sprite[] frames = new Sprite[N]; //N sprites for animation
    ChainShape[] shapes = new ChainShape[N]; //N shapes

    ...

    Vector2 lastSize = new Vector2(0,0); //or another initial value - depends on what sizes your sprites have

    for(int i = 0; i < frames.length; i++)
    {
        Sprite s = frames[i];

        if(lastSize.x != s.getWidth() || lastSize.y != s.getHeight())
        {
            shapes[i] = createEllipse(s.getWidth(), s.getHeight());
            lastSize.set(s.getWidth(), s.getHeight());
        }
        else
        {
            shapes[i] = null;
        }
    }

然后在渲染方法中

    //render method
    int index = getCurrentAnimationFrameIndex(animation); //some function to get index of current sprite of character

    if(shapes[index] != null)
        //change fixture

当然你应该记得在你的应用程序结束时处理所有not-null形状(至少如果它是Libgdx)!


我在这里发现的一个更有趣的事情是 ChainShape 在碰撞过程中没有旋转 这对我来说有点奇怪。实际上它可能对你有好处(我猜球是某​​种角色,它不应该有旋转 - 也许你甚至会在它的 body 上设置一些 setFixedRotation)但是如果你想要body 旋转的方法是定义 MassData 并将 I(惯性)设置为某个值,然后将此 MassData 附加到 body

    MassData m = new MassData();

    m.center.set(0, 0);
    m.I = 100; //example value
    body.setMassData(m); 

很遗憾,我无法告诉您应该在那里设置的确切值,但您可以在 Wikipedia site.

上阅读更多内容