如何防止碰撞体相互穿透?

21

我在控制游戏对象时遇到了一个问题,无法将它们保留在一个固定的空间内。当它们到达边缘时,会有一些瞬间的阻力,但然后它们就会穿过墙壁。

我正在使用玩家的 Box Collider 以及关卡墙壁的 Mesh Collider。我遇到了两个问题:一个是玩家角色(太空飞船)由玩家控制移动,另一个是由玩家发射的匀速移动的 Projectile(抛射物)。

这是我的玩家移动代码,在 FixedUpdate() 函数中运行。

//Movement
    haxis = Input.GetAxis("Horizontal") * speed;
    vaxis = Input.GetAxis("Vertical") * speed;

    moveVector.x = haxis;
    moveVector.z = vaxis;

    if(moveVector.magnitude > 1)
    {
        moveVector.Normalize();
    }

    rigidbody.MovePosition(transform.position + moveVector * speed);

子弹被赋予了速度,引擎会计算它们的移动。它们使用Box Collider,并将其设为触发器,因此它们没有物理效应。但我使用OnTriggerEnter来销毁它们。

//Projectiles without physics collisiions
function OnTriggerEnter (other : Collider) {
    Destroy(gameObject);
}

当子弹碰到网格碰撞器的墙时,有些但不是全部的子弹将被摧毁。玩家有时候会撞上它并停下来,但通常可以突破它。我该如何使网格碰撞器的碰撞每次都有效?


我甚至从墙壁的网格中创建了一个更简单的网格碰撞器,但并没有起到帮助作用。 - CLo
你使用的屏幕网格碰撞器有多宽?子弹和玩家可以移动多快?如果在一帧内,子弹或玩家的移动距离大于碰撞体,那就会有问题了。此外,我建议不要手动移动刚体。这只会让物理引擎感到困惑,并防止优化。相反,请移动gameobject的变换。 - Elideb
@Elideb 碰撞体大约有8个字符和20个子弹宽。它们不会在单个帧中完全穿过碰撞体。如果您移动变换,Unity将忽略所有物理,而rigidbody.MovePosition则考虑了物理因素。 - CLo
你说得对,我把关系搞反了。在计算速度时,你是否考虑了deltaTime?你的角色是否会一遍又一遍地撞墙直到穿过去?你的角色在OnCollision时会做什么? - Elideb
@Elideb MovePosition()的调用是作为FixedUpdate()的结果,所以不需要使用deltaTime。我还没有找到关于碰撞工作/失败的真正行为模式。 - CLo
8个回答

14

与高速移动的物体碰撞总是一个问题。确保检测到所有碰撞的好方法是使用射线投射而不是依赖于物理模拟。这对于子弹或小物体很有效,但对于大物体将不会产生良好的结果。 http://unity3d.com/support/documentation/ScriptReference/Physics.Raycast.html

类似伪代码(我这里没有代码自动完成和不好的记忆):

void FixedUpdate()
{
    Vector3 direction = new Vector3(transform.position - lastPosition);
    Ray ray = new Ray(lastPosition, direction);
    RaycastHit hit;
    if (Physics.Raycast(ray, hit, direction.magnitude))
    {
        // Do something if hit
    }

    this.lastPosition = transform.position;
}

提交了一次编辑,但应该是Ray(lastPosition,direction),否则您可能会在网格碰撞器内部,而Ray不会从碰撞器内部击中。看起来是一个不错的方法,我会检查一下。 - CLo
collider.Raycast 检测单个碰撞器 - 我已经修改为 Physics.Raycast。就性能而言,我认为它不会有太大的影响,但需要进行测试。您可以使用 Physics.Raycast 的 layerMask 参数来确保它只针对您想要的进行测试(请参阅文档)。 - Petrucio
1
先生,您是我的新神!非常感谢! - Taranasus
你可以使用 Rigidbody.maxDepenetrationVelocity 来处理快速移动的大型物体。 - ghanbari
@ghanbari 可能值得写一篇新答案。这是一个五年前的问题,因此更新的答案会很有帮助。 - CLo
显示剩余3条评论

14

我有一个弹球原型,也在同样的领域给我带来了很多麻烦。这些都是我已经采取的步骤,几乎(但还没有完全)解决这些问题:

对于快速移动的物体:

  • 将刚体的Interpolate设置为'Interpolate'(这不影响实际的物理模拟,但可以正确更新对象的渲染 - 仅从渲染角度重要的对象,如玩家或弹球,而不是抛射物使用此选项)

  • 将碰撞检测设置为连续动态

  • 将DontGoThroughThings脚本 (https://www.auto.tuwien.ac.at/wordpress/?p=260)附加到对象上。这个脚本巧妙地使用了我在另一个答案中发布的Raycasting解决方案,将冒犯性的对象拉回到碰撞点之前。

编辑->项目设置->物理中:

  • 将最小穿透量惩罚值设置为非常低的值。我将我的设置为0.001

  • 将Solver Iteration Count设置为更高的值。我将我的设置为50,但您可能可以使用更少的值。

所有这些都会对性能产生惩罚,但这是不可避免的。默认值在性能上较软,但并不真正用于小型和快速移动对象的适当模拟。


嗨,我找不到DontGoThroughtThings脚本,你能提供一下吗?也许用户已经从上面的链接中删除了它。 - Crazy Developer
我找到了“DontGoThroughThings”脚本的链接: http://code.google.com/p/lava-in-antarctica/source/browse/trunk/DontGoThroughThings.cs?spec=svn2&r=2 - Crazy Developer
我正在使用Unity 5,阅读了这篇文章后,虽然我找不到“最小穿透惩罚”,但我尝试将“默认接触偏移”更改为1。现在,快速移动的物体可以被检测到 :) - Kunalxigxag
“Collision Detection: Dynamic” 对于我很有效。使用Unity 2017. - nipponese

6

1
我非常确定,如果网格碰撞器的三角形数量超过一定数量,就无法将其设置为连续的。即使如此,在角色、子弹甚至网格上设置连续/连续动态也没有效果。 - CLo

1

所以我一直没有能够让网格碰撞器工作。我使用简单的盒子碰撞器创建了一个复合碰撞器,它按预期完美地工作。

其他使用简单网格碰撞器的测试也得出了相同的结果。

看起来最好的解决方案是使用简单的盒形/球形碰撞器构建复合碰撞器。

针对我的特定情况,我编写了一个向导,用于创建管道形状的复合碰撞器。

@script AddComponentMenu("Colliders/Pipe Collider");
class WizardCreatePipeCollider extends ScriptableWizard
{
    public var outterRadius : float = 200;
    public var innerRadius : float = 190;
    public var sections : int = 12;
    public var height : float = 20;

    @MenuItem("GameObject/Colliders/Create Pipe Collider")
    static function CreateWizard()
    {
        ScriptableWizard.DisplayWizard.<WizardCreatePipeCollider>("Create Pipe Collider");
    }

    public function OnWizardUpdate() {
        helpString = "Creates a Pipe Collider";
    }

    public function OnWizardCreate() {
        var theta : float = 360f / sections;
        var width : float = outterRadius - innerRadius;

        var sectionLength : float = 2 * outterRadius * Mathf.Sin((theta / 2) * Mathf.Deg2Rad);

        var container : GameObject = new GameObject("Pipe Collider");
        var section : GameObject;
        var sectionCollider : GameObject;
        var boxCollider : BoxCollider;

        for(var i = 0; i < sections; i++)
        {
            section = new GameObject("Section " + (i + 1));

            sectionCollider = new GameObject("SectionCollider " + (i + 1));
            section.transform.parent = container.transform;
            sectionCollider.transform.parent = section.transform;

            section.transform.localPosition = Vector3.zero;
            section.transform.localRotation.eulerAngles.y = i * theta;

            boxCollider = sectionCollider.AddComponent.<BoxCollider>();
            boxCollider.center = Vector3.zero;
            boxCollider.size = new Vector3(width, height, sectionLength);

            sectionCollider.transform.localPosition = new Vector3(innerRadius + (width / 2), 0, 0);
        }
    }
}

1

旧问题,但也许对某些人有所帮助。

进入项目设置 > 时间,尝试将固定时间步长和最大允许时间步长除以二或除以四。

我曾经遇到过这样的问题,我的玩家能够挤过比玩家碰撞器更小的开口,而这解决了这个问题。它还有助于停止快速移动的物体。


1
  • 编辑 ---> 项目设置 ---> 时间... 减少 "Fixed Timestep" 值. 这将解决问题,但可能会对性能产生负面影响。

  • 另一个解决方案是计算坐标(例如,您有一个球和墙。球会撞到墙上。因此,根据这些坐标计算墙的坐标并设置撞击过程)。


1

1.) 永远不要使用网格碰撞器,而是使用盒形和胶囊体碰撞器的组合。

2.) 检查刚体中的约束条件。如果勾选“冻结位置X”,则它将在X轴上穿过物体。(y轴同理)。


为什么不使用网格碰撞体? - Ramon Dias
1
网格碰撞器很难处理,更多的网格意味着更多的处理器使用,这个问题很老了,所以在那个时候,在廉价手机上处理网格更加困难,特别是安卓手机。 - Simran Singh

-1

尝试将模型设置为环境和静态。这解决了我的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接