缩放 Box2D 圆形及其碰撞检测?
Scale Box2D Circle Shape and it's Collision Detection?
如果一个圆圈在启动阶段拉伸并变长(增加高度和减少宽度)如何在缩放时动态调整碰撞形状(在 Box2D 中)?是否可以通过所有步骤的每一帧完全拉伸并返回圆形来完成?
同样的,在弹跳的时候,压扁了一点,需要检测多宽少高。
想象一下,这是一个游戏中的英雄角色,他有点跳投能力,甚至可以在半空中跳起来'double jumps'。但大部分伸长和挤压发生在地面相互作用期间。像这样:
我们有两个问题:
动态改变夹具形状
Box2D 支持各种类型的形状,例如:
- CircleShape(简单)
- 多边形(简单)
- ChainShape、EdgeShape(复杂)
这里的情况取决于你这里需要什么类型的形状。如果 "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();
创建椭圆
虽然 Box2D 支持各种形状但没有椭圆类型,您需要使用一些技术自行生成它。在互联网上描述了很多方法 - 我将向您展示如何生成椭圆作为 ChainShape
实例。
我将使用 parametric equation for ellipse 和 0 <= 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.
上阅读更多内容
如果一个圆圈在启动阶段拉伸并变长(增加高度和减少宽度)如何在缩放时动态调整碰撞形状(在 Box2D 中)?是否可以通过所有步骤的每一帧完全拉伸并返回圆形来完成?
同样的,在弹跳的时候,压扁了一点,需要检测多宽少高。
想象一下,这是一个游戏中的英雄角色,他有点跳投能力,甚至可以在半空中跳起来'double jumps'。但大部分伸长和挤压发生在地面相互作用期间。像这样:
我们有两个问题:
动态改变夹具形状
Box2D 支持各种类型的形状,例如:
- CircleShape(简单)
- 多边形(简单)
- ChainShape、EdgeShape(复杂)
这里的情况取决于你这里需要什么类型的形状。如果 "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();
创建椭圆
虽然 Box2D 支持各种形状但没有椭圆类型,您需要使用一些技术自行生成它。在互联网上描述了很多方法 - 我将向您展示如何生成椭圆作为
ChainShape
实例。我将使用 parametric equation for ellipse 和
0 <= 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.
上阅读更多内容