我在Box2D物理引擎的C#端口上遇到了一个非常奇怪的问题。
我基本上使用的是名为Box2DX的C#端口(最初托管在这里:https://code.google.com/archive/p/box2dx/,并导出到这个GitHub存储库:https://github.com/colgreen/box2dx)
现在,问题是,只有当所有动力学物体都处于模拟物理世界中的特定位置时,光线投射才能按预期工作。这很难解释,所以我在引擎附带的TestBed项目中创建了一个测试类,以便准确地重新创建问题。
我将世界设置为包含3个静态物体来表示地面。我还添加了一个代表“玩家”角色的大方框和一个小方框。这两个都是密度为1的动态体。
在这个图像中,你可以看到光线投射没有拾取任何设备的设置(光线由红线表示,从半空开始,结束于大型地面设备)。它应该检测到盒子,或者至少检测到静电接地装置。
现在,奇怪的部分是,光线投射确实起作用了,当“播放器”身体定位在光线投射的原点“上方”时,如下所示:
当“长方体”身体移动到光线上方时,同样的事情也会发生。
请注意,我还从“玩家”身体的底部垂直向下投射了三条蓝色射线。这些射线的行为也很奇怪。在上面的第一幅图像中,您可以看到所有三条光线都正确地检测到了地面设备。但是,当播放器的AABB定位如下图所示时,光线投射将不再起作用:
基本上,当玩家的AABB移动到它所站的任何身体的左侧边缘时,蓝色光线就会停止工作。
我已经摆弄了无数个小时了,我不知道是什么导致了这种奇怪的行为。我认为这只是一个在实际C#端口中查询的bug,与我的特定设置无关。
作为参考,下面是来自TestBed项目的自定义测试类的代码:
public class CustomRayCastTest : Test
{
public static CustomRayCastTest Create() => new CustomRayCastTest();
private Body _player;
public CustomRayCastTest()
{
_world.Gravity = new Box2DX.Common.Vec2(0, -6.25f);
BodyDef islandBodyDefA = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(1, 15)
//Position = new Box2DX.Common.Vec2(1, -17)
};
PolygonDef polygonDefIslandA = new PolygonDef
{
Density = 0,
Friction = 0.4f,
IsSensor = false,
Restitution = 0
};
polygonDefIslandA.SetAsBox(6, 3);
Body bodyIslandA = _world.CreateBody(islandBodyDefA);
Shape shapeIslandA = bodyIslandA.CreateShape(polygonDefIslandA);
bodyIslandA.SetMassFromShapes();
BodyDef islandBodyDefB = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(12, 2)
//Position = new Box2DX.Common.Vec2(12, -30)
};
PolygonDef polygonDefIslandB = new PolygonDef
{
Density = 0,
Friction = 0.4f,
IsSensor = false,
Restitution = 0
};
polygonDefIslandB.SetAsBox(9, 4);
Body bodyIslandB = _world.CreateBody(islandBodyDefB);
Shape shapeIslandB = bodyIslandB.CreateShape(polygonDefIslandB);
bodyIslandB.SetMassFromShapes();
BodyDef islandBodyDefC = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(32, 15)
//Position = new Box2DX.Common.Vec2(32, -17)
};
PolygonDef polygonDefIslandC = new PolygonDef
{
Density = 0,
Friction = 0.4f,
IsSensor = false,
Restitution = 0
};
polygonDefIslandC.SetAsBox(6, 3);
Body bodyIslandC = _world.CreateBody(islandBodyDefC);
Shape shapeIslandC = bodyIslandC.CreateShape(polygonDefIslandC);
bodyIslandC.SetMassFromShapes();
// Box
BodyDef boxBodyDef = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(18, 25)
//Position = new Box2DX.Common.Vec2(18, -1)
};
PolygonDef boxDef = new PolygonDef
{
Density = 1,
Friction = 0.2f,
IsSensor = false,
Restitution = 0
};
boxDef.SetAsBox(1, 1);
Body boxBody = _world.CreateBody(boxBodyDef);
Shape boxShape = boxBody.CreateShape(boxDef);
boxBody.SetMassFromShapes();
// Player
BodyDef playerBodyDef = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(5, 21)
//Position = new Box2DX.Common.Vec2(5, -4)
};
PolygonDef playerShapeDef = new PolygonDef
{
Density = 1,
Friction = 0.2f,
IsSensor = false,
Restitution = 0
};
playerShapeDef.SetAsBox(1, 2);
Body playerBody = _world.CreateBody(playerBodyDef);
Shape playerShape = playerBody.CreateShape(playerShapeDef);
playerBody.SetMassFromShapes();
_player = playerBody;
}
public override void Keyboard(Keys key)
{
base.Keyboard(key);
if(key == Keys.D)
{
_player.ApplyForce(new Vec2(7f * _player.GetMass(), 0), _player.GetWorldPoint(Vec2.Zero));
}
else if(key == Keys.A)
{
_player.ApplyForce(new Vec2(-7f * _player.GetMass(), 0), _player.GetWorldPoint(Vec2.Zero));
}
if(key == Keys.W)
{
_player.ApplyImpulse(new Vec2(0, _player.GetMass() * 20f), _player.GetWorldPoint(Vec2.Zero));
}
}
public override void Step(Settings settings)
{
base.Step(settings);
Vec2 rayStart = new Vec2(22, 40);
Vec2 rayEnd = new Vec2(17, 1);
Segment ray = new Segment
{
P1 = rayStart,
P2 = rayEnd
};
var shape = _world.RaycastOne(ray, out float lambda, out Vec2 normal, false, null);
if(shape != null)
{
Vec2 dir = rayEnd - rayStart;
rayEnd = rayStart + dir * lambda;
}
_debugDraw.DrawSegment(rayStart, rayEnd, new Color(1, 0, 0));
CastPlayerRays();
}
private void CastPlayerRays()
{
const int rayCount = 3;
const float playerWidth = 2;
const float playerHeight = 4;
const float inset = 0.2f;
const float rayLength = 2;
for (int i = 0; i < rayCount; i++)
{
Vec2 origin = new Vec2(-(playerWidth / 2 - inset), -(playerHeight / 2 - inset));
origin.X += i * (playerWidth / (rayCount));
origin = _player.GetWorldPoint(origin);
Vec2 end = origin + new Vec2(0, -1) * rayLength;
var shape = _world.RaycastOne(new Segment { P1 = origin, P2 = end }, out float lambda, out Vec2 normal, false, null);
if (shape != null)
{
Vec2 dir = end - origin;
end = origin + dir * lambda;
}
_debugDraw.DrawSegment(origin, end, new Color(0, 1, 1));
}
}
}有没有人遇到过这个问题?我不知道如何解决这个问题。我需要光线投射为我的游戏项目可靠地工作。任何帮助都将不胜感激。
谢谢!:)
发布于 2020-09-12 20:33:03
这里只是一个大胆的猜测,因为我从来没有使用过box2d的C#变体。我发现函数RaycastOne可疑...它在原始box2d中不存在。如果您阅读过Raycast here的文档,就会发现在路径中找到的每个fixture都会多次调用b2RayCastCallback。RaycastOne可能会尝试将其简化为只返回第一个fixture。
我的想法是,通过移动这些框,您以某种方式改变了fixture的内部组织方式,因此RaycastOne报告的第一个fixture只是得到了一个不同的fixture。
我会尝试替换RaycastOne,而不是实现b2RayCastCallback,这样您就可以更好地控制您想要对其进行反应的fixture。只要有一点运气,就可以解决这个问题。
发布于 2020-09-12 23:57:51
更新
我最终将我的项目迁移到了Aether.Physics2D:https://github.com/tainicom/Aether.Physics2D
它是一个更干净、更优化的Box2D端口,错误不会在这里发生。因此,如果其他任何人发现自己对此感到疯狂,这似乎真的是移植库中的一个bug。如果您想节省一些精力,请切换到不同的实现。
https://stackoverflow.com/questions/63859807
复制相似问题