LoginSignup
6
7

More than 3 years have passed since last update.

Unityで親子関係にないGameObject同士を親子みたいに移動・回転させる

Last updated at Posted at 2019-08-25

やりたいこと

こんな感じで、親子関係にないGameObject同士を、親子みたいに動かしたかった。
virtual_child.gif
VRコンテンツ開発中に、手でつかむものをControllerのGameObjectの子にしたくなかったのがきっかけ。

スクリプト

VirtualChildBehaviour
public class VirtualChildBehaviour : MonoBehaviour
{
    // 手のローカル座標を用いた、ターゲットへの相対位置
    private Vector3 _relavivePosition;
    // virtualParentのローカル座標を用いた、virtualParent.rotation -> virtualChild.rotationへの回転軸
    private Vector3 _parentLocalAxis;
    // virtualParent.rotation -> virtualChild.rotationへの回転量
    private float _rotationAngle;

    public Transform VirtualParent { get; private set; } = null;

    public Transform VirtualChild => transform;

    public bool ExistVirtualParent => VirtualParent != null;

    /// <summary>
    /// 親子関係のような振る舞いをさせたいオブジェクトを登録する
    /// </summary>
    /// <param name="virtualParent"></param>
    /// <returns></returns>
    public void RegisterParent(Transform virtualParent)
    {
        if (ExistVirtualParent)
        {
            Debug.LogWarning($"[VirtualChildBehaviour] '{VirtualChild.name}'はすでに'{VirtualParent.name}'の子供として登録されています。");
            return;
        }

        VirtualParent = virtualParent;

        // VirtualParentのローカル座標系で表した場合のターゲットオブジェクトの位置
        _relavivePosition = VirtualParent.InverseTransformPoint(VirtualChild.position);

        // 登録直後の状態のVirtualParentのrotationをVirtualChildのrotationに一致させるための回転行列を求める。
        // 求めたい回転行列をAとし, VirtualChild, VirtualParentのrotationをC,Pとすると、C = A * Pであり、
        // 両辺からPの逆行列(Inverse(P))をかけると A = C * Inverse(P)
        var rotationMatrix = VirtualChild.rotation * Quaternion.Inverse(VirtualParent.rotation);

        // rotationMatrixはグローバル座標を使って表現されているため、virtualParentが少しでも回転した後は使えないが、
        // Quaternionから回転量(angle)と回転軸(axis)を抽出すると、angleはそのまま使える
        rotationMatrix.ToAngleAxis(out _rotationAngle, out Vector3 grobalAxis);

        // axisはVirtualParentのローカル座標で表現しておき、
        // 使うタイミングでVirtualParentのrotationをもとにグローバル座標へ変換し直す
        _parentLocalAxis = VirtualParent.InverseTransformVector(grobalAxis);
    }

    /// <summary>
    /// バーチャルな親子関係を破棄する
    /// </summary>
    /// <returns></returns>
    public void UnregisterParent()
    {
        VirtualParent = null;
    }

    private void LateUpdate()
    {
        if (ExistVirtualParent == false) return;

        // ローカル座標 -> グローバル座標へ変換
        var position = VirtualParent.TransformPoint(_relavivePosition);
        var grobalAxis = VirtualParent.TransformVector(_parentLocalAxis);

        // (回転行列) * 親のrotation
        var rotation = Quaternion.AngleAxis(_rotationAngle, grobalAxis) * VirtualParent.rotation;

        VirtualChild.SetPositionAndRotation(position, rotation);
    }
}
VirtualChildBehaviourSample
[RequireComponent(typeof(VirtualChildBehaviour))]
public class VirtualChildBehaviourSample : MonoBehaviour
{
    private VirtualChildBehaviour _virtualChild;
    public Transform virtualParent;

    private void Awake()
    {
        _virtualChild = GetComponent<VirtualChildBehaviour>();
        _virtualChild.RegisterParent(virtualParent);
    }
}

Childのように動かしたいGameObjectの方にVirtualChildBehaviourSample、VirtualChildBehaviourをアタッチして、インスペクターからvirtualParentに親にしたいGameObjectを登録すればOK。

Scaleの変化には未対応。

あとがき

めちゃくちゃ混乱して、これだけで2日くらいかかってしまった。
圧倒的数学力不足。

参考

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7