Unity で持ち物の利き手切り替え対応をした話

こんにちは。亀山です。弊社ではクライアントワークで PicoVR Unity SDK を使った VR アプリの開発を行っています。こちらの SDK は少々古いですが、ネット上に情報も少なくせっかくなのでメモがてら書いておこうと思います。今回は Pico Neo2, Pico Neo3 で持ち物が右手固定だったものを利き手に持ち変えるよう修正した方法について説明します。

前提

手に物を持って動かすことができる VR アプリです。シーン上に持ち物を配置し、Parent Constraint によってユーザーの手の位置である Pvr_UnitySDK/ControllerManager/PvrController0/controller オブジェクトに追従するようにしていました。

利き手の取得

下記のメソッドでメインコントローラーの番号を取得できます。設定画面、もしくはアプリ起動前にホームの UI でトリガーを押したコントローラがメインコントローラになります。

Pvr_ControllerManager.controllerlink.GetMainControllerIndex()

Parent Constraint の weight

Parent Constraint には複数のオブジェクトを設定し、weight によってどちらに寄せるかを変化させる機能があります。今回はこの機能を利用し、右手・左手のオブジェクトを設定し、利き手によって weight を変更させることで利き手対応を行います。

まず Parent Constraint に右手・左手それぞれの位置のオブジェクトである Pvr_UnitySDK/ControllerManager/PvrController0/controllerPvr_UnitySDK/ControllerManager/PvrController1/controller を設定します。GetMainControllerIndex と合わせてそれぞれインデックス 0, 1 とします。

次に Parent Constraint の weight を GetMainControllerIndex で得られたインデックスに合わせて変更します。weight の変更は ConstraintSource の weight プロパティを更新することで行いますが、ConstraintSource を直接更新しても Parent Constraint に反映されないため、SetSource でセットしなおします。

var index = Pvr_ControllerManager.controllerlink.GetMainControllerIndex();
var sources = new List<ConstraintSource>();
parentConstraint.GetSources(sources);
var newSources = sources.Select((source, i) => new ConstraintSource()
{
    sourceTransform = source.sourceTransform,
    weight = index == i ? 1 : 0
}).ToList();
parentConstraint.SetSources(newSources);

利き手変更への対応

先程のコードを起動時に実行するだけでも良いのですが、ホーム画面に戻ってから利き手を切り替えるなど、動作中にも変化する可能性があります。これに対応するため、GetMainControllerIndex が変化したタイミングで先程の処理を実行することにします。UniRx を使って下記のように書くことができます。

public class ExampleWeapon : MonoBehaviour
{
    private void Awake()
    {
        parentConstraint = GetComponent<ParentConstraint>();

        Observable.EveryUpdate()
            .Select(_ => Pvr_ControllerManager.controllerlink.GetMainControllerIndex())
            .DistinctUntilChanged()
            .Subscribe(index =>
            {
                var sources = new List<ConstraintSource>();
                parentConstraint.GetSources(sources);

                var newSources = sources.Select((source, i) => new ConstraintSource()
                {
                    sourceTransform = source.sourceTransform,
                    weight = index == i ? 1 : 0
                }).ToList();
                parentConstraint.SetSources(newSources);
            }).AddTo(this);
    }
}