Unity旋转问题:父级旋转导致的旋转问题

3

首先,这是问题的视频: 视频链接,这是软件包 下面是层次结构的截图: enter image description here GUN是带有脚本(空游戏对象)的父级,悬挂被放置到towerRotateObj上,而枪被放置在turretRotateObj上。悬挂和枪也是空游戏对象。它们只是某种类型的组对象。 以下是代码:

public class WeaponMover : MonoBehaviour
{
    public Transform target;
    public GameObject turretRotateObj;
    public GameObject towerRotateObj;

    public float maxTowerRotationSpeed = 360.0f;
    public float maxTurretRotationSpeed = 360.0f;

    public float smoothFactorTower = 0.125f;
    public float smoothFactorTurret = 0.125f;

    public float maxTowerRotation = 130.0f;
    public float maxTurretRotation = 50.0f;

    private Vector3 m_newRotation;
    private Vector3 m_angles;
    private float m_minTowerAngle;
    private float m_maxTowerAngle;
    private float m_minTurretAngle;
    private float m_maxTurretAngle;
    private float m_velTower;
    private float m_velTurret;

    private bool m_isTransNecTower = false;
    private bool m_isTransNecTurret = false;

    // initialization
    void Start()
    {
       m_newRotation = Vector3.zero;
       m_angles = Vector3.zero;

       m_maxTowerAngle = towerRotateObj.transform.eulerAngles.y + maxTowerRotation/2;
       m_minTowerAngle = towerRotateObj.transform.eulerAngles.y - maxTowerRotation/2;

       m_maxTurretAngle = turretRotateObj.transform.eulerAngles.z + maxTurretRotation/2;
       m_minTurretAngle = turretRotateObj.transform.eulerAngles.z - maxTurretRotation/2;

       // check if rotation happens between 0/360
       // tower
       if(m_minTowerAngle <= 0.0f)
         m_minTowerAngle += 360.0f;

       if(m_maxTowerAngle >= 360.0f)
         m_maxTowerAngle -= 360.0f;

       if(m_minTowerAngle > m_maxTowerAngle)
         m_isTransNecTower = true;

       // turret
       if(m_minTurretAngle <= 0.0f)
         m_minTurretAngle += 360.0f;

       if(m_maxTurretAngle >= 360.0f)
         m_maxTurretAngle -= 360.0f;

       if(m_minTurretAngle > m_maxTurretAngle)
         m_isTransNecTurret = true;
    }

    void Update()
    {
       m_newRotation = Quaternion.LookRotation(target.position - towerRotateObj.transform.position).eulerAngles;
       m_angles = towerRotateObj.transform.rotation.eulerAngles;
       towerRotateObj.transform.rotation = Quaternion.Euler(m_angles.x,
         ClampAngle(Mathf.SmoothDampAngle(m_angles.y, 
          m_newRotation.y - 90.0f, 
          ref m_velTower, 
          smoothFactorTower, 
          maxTowerRotationSpeed), m_minTowerAngle, m_maxTowerAngle, m_isTransNecTower),
         m_angles.z);

       m_newRotation = Quaternion.LookRotation(target.position - turretRotateObj.transform.position).eulerAngles;
       m_angles = turretRotateObj.transform.rotation.eulerAngles;
       turretRotateObj.transform.rotation = Quaternion.Euler(m_angles.x,
         m_angles.y,
         ClampAngle(Mathf.SmoothDampAngle(m_angles.z, 
          -m_newRotation.x, 
          ref m_velTurret,
          smoothFactorTurret, 
          maxTurretRotationSpeed), m_minTurretAngle, maxTurretRotation, m_isTransNecTurret));
    }

    private float ClampAngle(float angle, float min, float max, bool isTranslationNecessary)
    {
       if(!isTranslationNecessary)
       {
         if(angle < min )
          return min;

         if(angle > max)
          return max;
       }
       else
       {
         if(angle > max && angle < min)
         {
          if(min - angle > angle - max)
              return max;
          else
              return min;
         }
       }

       return angle;
    }
}

所以,这是一个类似的设置,就像坦克的塔和炮一样...

我已经在这里发布了那个问题,但似乎很少有人看到帖子... 任何建议都将不胜感激!谢谢。

更新:

    m_newRotation = Quaternion.LookRotation(m_target.transform.position - towerRotateObj.transform.position).eulerAngles;
    m_newRotation.y -= 90f;
    m_angles = towerRotateObj.transform.rotation.eulerAngles;
    towerRotateObj.transform.rotation = Quaternion.Euler(m_angles.x, m_newRotation.y, m_angles.z);

    m_newRotation = Quaternion.LookRotation(m_target.transform.position - turretRotateObj.transform.position).eulerAngles;
    m_angles = turretRotateObj.transform.rotation.eulerAngles;
    turretRotateObj.transform.rotation = Quaternion.Euler(m_angles.x, m_angles.y, -m_newRotation.x);

问题仍然存在 :(

这篇文章太长了。很少有人会花费足够的时间去理解问题所在。也没有人会去寻找答案。尝试提出一个详细的问题,并只提供最相关的代码。 - Valentin Simonov
1
非常有帮助。如果我不知道问题出在哪里,该如何缩短时间?如果您观看视频,您就能看到问题所在。如果我要描述发生了什么,那么理解起来会更加耗时。 - user1347198
3个回答

2
所以我的猜测是,万向锁,就像其他答案中所说的。但是,考虑到你正在将四元数传递给欧拉角和反之亦然,我不确定我会尝试修复那个问题。如果我必须这样做,我不会像你那样去做,因为使用四元数在对象之间进行父子关系等同于巨大的头痛。
首先,在你设置对象的父子关系时,你没有使用Unity非常稳定的功能!Unity父子关系:http://docs.unity3d.com/Documentation/ScriptReference/Transform-parent.html 你的每个炮塔对象只绕单一本地轴旋转。而Unity是为处理这种情况而构建的:http://docs.unity3d.com/Documentation/ScriptReference/Transform-localRotation.html 当一个对象被父对象包含时,你可以在所需的轴上进行本地旋转。Unity会自行处理整体矩阵变换。当你修改对象的全局旋转时,你基本上是覆盖了来自父对象的变换。此时,任何层次结构都很容易随着时间而出现错误和不精确性。
编辑:有了您的软件包,我能够写出我想要的内容。
public class WeaponMover : MonoBehaviour
{
    public GameObject boat;
    public GameObject turretRotateObj;
    public GameObject towerRotateObj;
    public GameObject target;

    private Vector3 lastDirection;

    // initialization
    void Start()
    {
        lastDirection = boat.transform.forward;
    }

    void Update()
    {
        // Find direction toward our target. Use turret as origin.
        Vector3 wantedDirection = target.transform.position - turretRotateObj.transform.position;
        // Rotate our last direction toward that new best direction. Change floats to make it move faster or slower.
        lastDirection = Vector3.RotateTowards(lastDirection, wantedDirection, 0.01f, 0.01f);

        // Find the direction local to the tower as the boat can move around!
        Vector3 towerDirection = boat.transform.InverseTransformDirection(lastDirection);
        // Remove unwanted axis
        towerDirection = new Vector3(-towerDirection.z, 0, towerDirection.x);
        towerDirection.Normalize();
        // Set local rotation
        towerRotateObj.transform.localRotation = Quaternion.LookRotation(towerDirection);

        // Find the direction local to the gun, as the tower may have rotated!
        Vector3 turretDirection = towerRotateObj.transform.InverseTransformDirection(lastDirection);
        // Remove unwanted axis.
        turretDirection = new Vector3(turretDirection.x, turretDirection.y, 0);
        turretDirection.Normalize();
        // Set local rotation
        turretRotateObj.transform.localRotation = Quaternion.LookRotation(turretDirection);
    }
}

注意:我不得不将枪管移动到Z轴而不是X轴,因为我很懒。因此,如果你只是复制粘贴这段代码,枪管将指向框架,但枪支将正确跟随目标。
由于旋转现在是本地的,你可以让飞船旋转多年,枪支永远不会有任何偏移。

这是一个软件包的下载链接:http://www.file-upload.net/download-6745903/rotation.rar.html。你说的“你没有在彼此之间设置父对象”是什么意思?正如你在帖子的图片中所看到的,我正在使用父对象... - user1347198
@immerhart:那你为什么要修改全局旋转对象,而不是使用“localRotation”并只在一个轴上旋转对象呢? - LightStriker
@immerhart:这是你要的包,我能够写出我想表达的内容。我用修复代码更新了我的答案。 - LightStriker
它似乎可以工作。你能告诉我,我做错了什么吗?或者为什么会出现这种行为?我只是旋转了对象,不是本地的,但它正常工作。我只是不明白为什么会出现这种行为。 - user1347198
1
@immerhart:有几件事情...首先,你不应该拿最后一个方向并尝试修改它,因为随着时间的推移,它很容易出现不精确。通过拥有一个固定的向量,它不受任何转换空间的限制,你可以保持它的有效性,并拥有一个稳定的参考来使用。其次,在父对象存在时,你不应该修改全局旋转,除非你绝对确定这就是你想要做的。我还没有遇到过那种情况。第三,跳转欧拉角和四元数很少是一个好主意。四元数是万向节锁定证明的,但欧拉角不是。我更喜欢坚持使用向量和矩阵变换。 - LightStriker
显示剩余3条评论

0

嗯,我不确定,但似乎你遇到了万向锁问题。虽然你的计算从四元数开始,但长期来看,两次对Mathf.SmoothDampAngle的调用可能是根本原因。

所以我建议先去除所有平滑处理,然后如果这被证明是罪魁祸首,就用纯四元数方法替换这些方法,比如Quaternion.Slerp


另一个要点是,我一次只旋转一个轴而不是同时旋转所有三个轴。那么,万向锁会发生吗?此外,我只使用四元数,就像你在更新中说的一样... :| 不知道。我不擅长数学。 - user1347198

0

从Update更改为LateUpdate后,我无法重现您的问题。也许我没有像您那样频繁旋转,但我认为零件的旋转在炮塔旋转之前和之后被应用,导致误差累积。


如果出现这种情况,游戏运行时也会累积错误,并且这个错误将会在任何老化测试中都变得明显。代码应该建立在这样的基础上:偏移量或错误绝不能随着时间的推移而累积。 - LightStriker
这就是切换到LateUpdate所做的事情。 - Calvin
无论如何,它都会在LateUpdate上执行,因为它正在修改先前的全局旋转。以这种方式完成时,必定会存在不精确性,因为您在每个帧上堆叠浮点不精确性,并且永远无法摆脱它。 - LightStriker

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