【Animation Rigging】プレハブ内の各種ConstraintのSourceObjectsを実行中に変更する方法


はじめに

Animation RiggingパッケージのTwoBoneIKConstraintMulti-AimConstraintなど各種Constraintを持つPrefabをインスタンスする際、SourceObject(Target等)にシーン内のオブジェクトを参照する方法を紹介します。

環境

  • Unity 2019.4.9f1
  • Animation Rigging preview-0.2.7

できない例

例えば以下のようなコードで、プレハブをインスタンスした後、Multi-AimConstraintsourceObjectsにターゲットをセットしようとします。

Spawn.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations.Rigging;

public class Spawn : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private Transform target;
    private GameObject spawndAvatar;

   private void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            // インスタンス
            spawndAvatar = Instantiate(prefab, transform.position, Quaternion.identity);

            // RigBuilderの3つ目に登録されているRigの子供を列挙
            foreach (Transform lookat in spawndAvatar.GetComponent<RigBuilder>().layers[2].rig.transform)
            {
                // sourceObjectsにターゲットを設定する
                var source = lookat.GetComponent<MultiAimConstraint>().data.sourceObjects;
                source.SetTransform(0, target);
                lookat.GetComponent<MultiAimConstraint>().data.sourceObjects = source;
            }
        }
    }
}

HeadRig以下の3つのオブジェクトについているMulti-AimConstraintについてSourceObjectを設定します。
RigBuilder

階層

この時、Inspector上では正しく設定されているように見えてもConstraintは効いてくれません。


MainCameraを設定しているのでカメラ目線になってくれるはず……

※単眼キャラクターです。苦手な方はご注意を

つーん
ちなみにこの子はオダマキちゃんといいます。単眼なのは私が好きだからです。

方法1 RigBuilderをビルドする

こちらで見つけた方法です。
https://forum.unity.com/threads/multi-aim-constraint-set-source-at-runtime.944559/

先ほどのコードに一行付け足すだけです。

Spawn.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations.Rigging;

public class Spawn : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private Transform target;
    private GameObject spawndAvatar;

   private void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            // インスタンス
            spawndAvatar = Instantiate(prefab, transform.position, Quaternion.identity);

            // RigBuilderの3つ目に登録されているRigの子供を列挙
            foreach (Transform lookat in spawndAvatar.GetComponent<RigBuilder>().layers[2].rig.transform)
            {
                // sourceObjectsにターゲットを設定する
                var source = lookat.GetComponent<MultiAimConstraint>().data.sourceObjects;
                source.SetTransform(0, target);
                lookat.GetComponent<MultiAimConstraint>().data.sourceObjects = source;
            }

            // 設定後にRigBuilderをビルドしなおす。
            spawndAvatar.GetComponent<RigBuilder>().Build();
        }
    }
}

怖い……

方法2 生成する前のprefabに代入する

いじってたら見つけた対症療法。インスタンスの際にされているであろうビルドの前にターゲットを設定する方法です。

後述しますがシーンごとに挙動が変わってしまう恐れがあるので複数シーンで使う場合はおすすめしません。単一シーンで使う場合はこの後のおまけの章で紹介する方法の方が簡単だと思います。

結論使わない方がいいです。

Spawn.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations.Rigging;

public class Spawn : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private Transform target;
    private GameObject spawndAvatar;

   private void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            // インスタンス前にprefabでセットしてしまう
            foreach (Transform lookat in spawndAvatar.GetComponent<RigBuilder>().layers[2].rig.transform)
            {
                var source = lookat.GetComponent<MultiAimConstraint>().data.sourceObjects;
                source.SetTransform(0, target);
                lookat.GetComponent<MultiAimConstraint>().data.sourceObjects = source;
            }

            // インスタンス
            spawndAvatar = Instantiate(prefab, transform.position, Quaternion.identity);
        }
    }
}

これを試した後、ターゲットをセットするforeachブロックを消してインスタンスを実行しても、なんとターゲットがセットされました

project内のprefabなのでもちろんシーン内への参照は持てませんし、prefabを開いて確認してもNoneになっています。怖い……

(prefabをシーンにドラッグアンドドロップをすると参照が生えるので内部的には保持している状態なのでしょうか。別のシーンに持っていくともちろんNoneのままです。)

おまけ そもそもプレハブをシーン中に置いておけば解決するのでは?

『projectのプレハブなのでシーン内のオブジェクト参照できない→実行中にスクリプトから入れなければならない』

でお困りの場合は、プレハブをシーン内に置いてしまえば解決する場合も多いと思います。

シーン内のオブジェクトでもprojectのプレハブと同様にインスタンスすることができます。「動いてしまうと困る」「StartやAwakeを実行してほしくない」場合は非アクティブなGameObjectの子にしてしまえばいいです。インスタンス時にアクティブにする必要もないですし。

参考