Unity - 如果对象与子对象发生碰撞,如何锚定到对象

Unity - How to Anchor to an Object if it Collides With a Child Object

我正在开发的游戏中有一个伸缩尖峰小工具。尖刺应该是玩家的手臂附件,一旦尖刺尖端(最深的子对象)接触到某物,玩家就可以粘在墙上。本质上,我希望它“刺入”墙壁并将播放器固定到位。

我最接近的方法是将“接触对模式”设置为“启用运动学运动学对”,然后在尖端添加一个运动学 RigidBody 和一个 FixedJoint。这是尖峰本身的代码:

private IEnumerator _ExpandSequence()
    {
        mExpanding = true;
        if (mCollapsing)
        {
            yield return new WaitUntil(() => !mCollapsing);
        }

        mBaseGoal = transform.localPosition + mBaseDistance * Vector3.up;
        mConeGoal = mSpikeSections[5].localPosition + mConeDistance * Vector3.up;

        while (transform.localPosition.y > mBaseSpikeEnd || mSpikeSections[1].localPosition.y > mCylinderEnd
            || mSpikeSections[5].localPosition.y > mConeEnd)
        { //only using the first cylinder section in the condition since they all move the same distance
            
            transform.localPosition = Vector3.MoveTowards(transform.localPosition, mBaseGoal, mSpeed);

            mSpikeSections[1].localPosition = mSpikeSections[2].localPosition = mSpikeSections[3].localPosition = 
                mSpikeSections[4].localPosition = Vector3.MoveTowards(mSpikeSections[1].localPosition, Vector3.up * mCylinderDistance, mSpeed);

            mSpikeSections[5].localPosition = Vector3.MoveTowards(mSpikeSections[5].localPosition, mConeGoal, mSpeed);

            if (Mathf.Approximately(transform.localPosition.y, mBaseSpikeEnd) &&
                Mathf.Approximately(mSpikeSections[1].localPosition.y, mCylinderEnd) &&
                Mathf.Approximately(mSpikeSections[5].localPosition.y, mConeEnd))
            {
                transform.localPosition = mBaseGoal;
                mSpikeSections[5].localPosition = mConeGoal;
                break;
            }

            yield return null;
        }
        mExpanding = false;
        mExtended = true;
    }

这是尖峰碰撞的代码:

private void OnCollisionEnter(Collision col)
    {
        if (col.gameObject.tag == "Enemy" || col.gameObject.tag == "Environment")
        {
            //Anchor            
            mFj.connectedBody = col.rigidbody;
        }
        else
        {
            //TODO
        }
    }

这是玩家资产的层次结构:

目前,当尖刺与表面碰撞时,我可以在编辑器中看到连接的物体已注册

尽管如此,玩家仍会反弹并能够自由移动,就好像关节不存在一样。谁能帮我找出为什么播放器没有固定在墙上?

TL;DR:如何让具有 RigidBody 的子对象通过 FixedJoint 连接到另一个对象?

首先,在tip上创建两个FixedJoint组件,并将它们分配给脚本中的字段。将玩家的刚体分配给脚本中的一个字段。将尖端的刚体和碰撞器分配给脚本中的字段。

其次,当碰撞发生时,关闭 isKinematic,这样尖端会影响关节并受关节影响(例如,如果敌人移动,它会跟随)

第三,当你想结束锚点时,你应该能够再次将 isKinematic 设置为 true,将其两个关节连接的身体分配给 null,并在必要时禁用其碰撞器以避免

[SerializeField] FixedJoint mFixedJointTarget; // 1st fixed joint on tip
[SerializeField] FixedJoint mFixedJointPlayer; // 2nd fixed joint on tip

[SerializeField] Rigidbody myRB;     // rigidbody of tip
[SerializeField] Rigidbody playerRB; // player's rigidbody

[SerializeField] Collider myColl; // if necessary to avoid re-attachment

private void OnCollisionEnter(Collision col)
{
    if (col.gameObject.tag == "Enemy" || col.gameObject.tag == "Environment")
    {
        StartAnchor(col.rigidbody); 
    }
}

void StartAnchor(Rigidbody targetRB)
{
    myRB.isKinematic = false;

    mFixedJointTarget.connectedBody = targetRB;
    mFixedJointPlayer.connectedBody = playerRB;
}

void StopAnchor()
{
    myRB.isKinematic = true;
    mFixedJointTarget.connectedBody = null;
    mFixedJointPlayer.connectedBody = null;

    // re-enable before shooting again, of course
    // may not be necessary depending on what else you do when you stop anchoring.
    myColl.enabled = false;
}

如果你愿意,你可以对尖端永远不会运动进行同样的操作,你只需要 Destroy 关节而不是将 connectedBody 设置为空,并且还创建关节 当发生碰撞时,这可能如下所示:

//on drill with useGravity = false
public class DrillShot : MonoBehaviour
{
    FixedJoint mFixedJointTarget; // 1st fixed joint on tip
    FixedJoint mFixedJointPlayer; // 2nd fixed joint on tip

    Rigidbody rb;
    [SerializeField] Rigidbody playerRB; // player's rigidbody

    Renderer rend;

    [SerializeField] float speed = 10f;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rend = GetComponent<Renderer>();

    }

    private void OnCollisionEnter(Collision col)
    {
        if (col.gameObject.tag == "Enemy" || col.gameObject.tag == "Environment")
        {
            StartAnchor(col.rigidbody);
        }
    }

    void StartAnchor(Rigidbody targetRB)
    {
        if (mFixedJointTarget != null)
        {
            Destroy(mFixedJointTarget);
        }

        if (mFixedJointPlayer != null)
        {
            Destroy(mFixedJointPlayer);
        }

        mFixedJointPlayer = gameObject.AddComponent<FixedJoint>();
        mFixedJointPlayer.connectedBody = playerRB;

        mFixedJointTarget = gameObject.AddComponent<FixedJoint>();
        mFixedJointTarget.connectedBody = targetRB;
    }

    void StopAnchor()
    {
        if (mFixedJointTarget != null)
        {
            Destroy(mFixedJointTarget);
        }

        if (mFixedJointPlayer != null)
        {
            Destroy(mFixedJointPlayer);
        }

        rend.enabled = false;
    }

    void Shoot()
    {
        rend.enabled = true;
        transform.position = playerRB.position + Vector3.right;

        rb.velocity = Vector3.right * speed;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.E))
        {
            Shoot();
        }
        else if (Input.GetKeyDown(KeyCode.R))
        {
            StopAnchor();
        }
    }
}
// on wall, with rigidbody tagged "Environment",
// xyz freeze rotation mass 100, use gravity = false
public class MoveRandomly : MonoBehaviour
{
    Vector3 curTarget;
    Vector3 startPos;

    [SerializeField] float switchTargetPeriod = 2f;
    [SerializeField] float targetRadius = 1f;
    [SerializeField] float dampener = 0.05f;
    Rigidbody rb;
    float t = 0f;
    
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        startPos = transform.position;
    }

    void FixedUpdate()
    {
        t -= Time.deltaTime;
        if (t < 0)
        {
            t = switchTargetPeriod;
            curTarget = startPos + Random.insideUnitSphere * targetRadius;
        }

        Vector3 diff = curTarget - transform.position;
        rb.AddForce(diff * dampener, ForceMode.VelocityChange);
    }
}