RayCasting的Box2DXBox2D物理引擎的C#端口问题

问题描述

我对Box2D物理引擎的C#端口有一个非常奇怪的问题。

我基本上是使用名为Box2DX的C#端口(最初托管在这里:https://code.google.com/archive/p/box2dx/,并导出到此GitHub存储库:https://github.com/colgreen/box2dx

现在,问题是,仅当所有动态物体都位于模拟物理世界中的特定位置时,光线投射才能按预期工作。很难解释,因此我在引擎随附的TestBed项目中创建了一个测试类,以便精确地重新创建问题。

我将世界设置为包含3个代表地面的静态物体。我还添加了一个大框来代表“玩家”角色和一个小框。它们都是密度为1的动态物体。

在此图像中,您可以看到光线投射未拾取任何固定装置的设置(由红线表示的光线从空中开始,在大型地面固定装置内结束)。它应该检测盒子或至少是静态接地夹具。

RayCast Not Working

现在,奇怪的是,当“玩家”的身体定位在射线源的“上方”的那一刻,射线广播确实起作用了,如下所示:

RayCast Working

每当“盒子”物体移动到光线上方时,都会发生同样的事情。

RayCast working again

请注意,我也从“玩家”身体的底部垂直向下投射了三道蓝光。 这些射线的行为也很奇怪。在上面的第一个图像中,您可以看到所有三条光线都能正确检测到地面固定装置。但是,一旦玩家的AABB位置如下图所示,光线将不再起作用:

Blue RayCasts not working

基本上,当玩家的AABB移到站立的任何身体的左边缘上方时,蓝光便停止工作。

Blue Rays Not Working

我已经花了无数个小时来摆弄这个东西,但我不知道是什么原因导致了这种奇怪的行为。我认为这只是世界上在实际C#端口中查询的一个错误,与我的特定设置无关。

作为参考,这是我来自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,Position = new Box2DX.Common.Vec2(12,2)
            //Position = new Box2DX.Common.Vec2(12,-30)
        };

        PolygonDef polygonDefIslandB = new PolygonDef
        {
            Density = 0,Restitution = 0
        };

        polygonDefIslandB.SetAsBox(9,4);

        Body bodyIslandB = _world.CreateBody(islandBodyDefB);
        Shape shapeIslandB = bodyIslandB.CreateShape(polygonDefIslandB);

        bodyIslandB.SetMassFromShapes();

        BodyDef islandBodyDefC = new BodyDef
        {
            AllowSleep = false,Position = new Box2DX.Common.Vec2(32,15)
            //Position = new Box2DX.Common.Vec2(32,-17)
        };

        PolygonDef polygonDefIslandC = new PolygonDef
        {
            Density = 0,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,Position = new Box2DX.Common.Vec2(18,25)
            //Position = new Box2DX.Common.Vec2(18,-1)
        };

        PolygonDef boxDef = new PolygonDef
        {
            Density = 1,Friction = 0.2f,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,Position = new Box2DX.Common.Vec2(5,21)
            //Position = new Box2DX.Common.Vec2(5,-4)
        };

        PolygonDef playerShapeDef = new PolygonDef
        {
            Density = 1,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(),_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));

        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 },null);

            if (shape != null)
            {

                Vec2 dir = end - origin;

                end = origin + dir * lambda;

            }

            _debugDraw.DrawSegment(origin,end,new Color(0,1,1));

        }


    }

}

有人遇到过这个问题吗?我不知道该如何解决。我需要光线投射才能为我的游戏项目可靠地工作。任何帮助将不胜感激。

谢谢! :)

解决方法

在这里只是一个疯狂的猜测,因为我从未使用过box2d的C#变体。我发现功能RaycastOne可疑...在原始的box2d中不存在。如果您阅读了Raycast here的文档,它说b2RayCastCallback对于路径中找到的每个灯具都会被调用多次。 RaycastOne尝试将其简化为仅返回第一个灯具。

我的想法是,通过移动这些盒子,您可以以某种方式更改灯具的内部组织方式,因此RaycastOne报告的第一个灯具就变成了另一个灯具。

我将尝试替换RaycastOne并改为实现b2RayCastCallback,然后您可以更好地控制要响应的灯具。幸运的话,可以解决这个问题。

,

更新

我最终将项目迁移到Aether.Physics2D: https://github.com/tainicom/Aether.Physics2D

它是Box2D的一个更干净,更优化的端口,并且该错误不会在此处发生。因此,如果有人发现自己为此感到疯狂,那么这似乎确实是移植库中的错误。如果您想省心一些,请切换到其他实现。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...