4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Unity(C#)】OculusQuest(Oculus Integration)で手のアニメーションを制限する方法

Last updated at Posted at 2019-06-29

指の動きを制限したい

今までViveしか触ったことがなかったため、
OculusTouchコントローラーで人差し指と親指が動かせると知った時には感動しました。

しかし、過去にViveで遊んできたコンテンツ、作成してきたコンテンツの中で
「ここで人差し指とか親指が動かせたらな~」と思ったことは さほどありませんでした。

実際にOculusQuestで開発を進めていると、
指が動くとかえって邪魔になるという場面にすら遭遇しました。

なので、開発に応じて簡単に指の動きの制限を切り替えられる機能は無いのか? と思い調べました。

着地点は下記実装です。
①コブシを握る
②握ったコブシを開く
③必要に応じて指を動かせるようにする

Hand

Handクラスの中に指のアニメーションのオンオフ切り替え機能がありました。
PrefabはOculus-SampleFramework-Core-CustomHands-CustomHandLeft(Right)にあります。
Inspectorはこんな感じです。

CustomHand.PNG

この中で今回のアニメーションの切り替えに関係しているプロパティがあります。
Handの中のDefault Grab Poseです。
初期状態ではPrefabの中のHandPoseDefaultPfがアタッチされています。
HandPoseDefaultPfのInspectorはこうです。
Pose.PNG
このAllow PointingAllow Thumbs Upが人差し指、親指の制御を担っています。
今回は使いませんが、Pose IDも便利な機能を持っていて、物に応じてつかんだ時のポーズを設定できます。

コード

少しHandメソッドの中を見ていきます。

Hand
       private void UpdateAnimStates()
        {
            bool grabbing = m_grabber.grabbedObject != null;
            HandPose grabPose = m_defaultGrabPose;
            if (grabbing)
            {
                HandPose customPose = m_grabber.grabbedObject.GetComponent<HandPose>();
                if (customPose != null) grabPose = customPose;
            }
            // Pose
            HandPoseId handPoseId = grabPose.PoseId;
            m_animator.SetInteger(m_animParamIndexPose, (int)handPoseId);

            // Flex
            // blend between open hand and fully closed fist
            float flex = OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, m_controller);
            m_animator.SetFloat(m_animParamIndexFlex, flex);

            // Point
            bool canPoint = !grabbing || grabPose.AllowPointing;
            float point = canPoint ? m_pointBlend : 0.0f;
            m_animator.SetLayerWeight(m_animLayerIndexPoint, point);

            // Thumbs up
            bool canThumbsUp = !grabbing || grabPose.AllowThumbsUp;
            float thumbsUp = canThumbsUp ? m_thumbsUpBlend : 0.0f;
            m_animator.SetLayerWeight(m_animLayerIndexThumb, thumbsUp);

            float pinch = OVRInput.Get(OVRInput.Axis1D.PrimaryIndexTrigger, m_controller);
            m_animator.SetFloat("Pinch", pinch);
        }

下記の箇所で掴んだ物に応じたアニメーション制御を可能にしています。
掴んだ物にHandPoseをアタッチしてPose Idを設定することで可能です。

物ごとにポーズを設定が可能
        HandPose grabPose = m_defaultGrabPose;
        if (grabbing)
        {
            HandPose customPose = m_grabber.grabbedObject.GetComponent<HandPose>();
            if (customPose != null) grabPose = customPose
        }

今回の目的は、
①コブシを握る
②握ったコブシを開く
③必要に応じて指を動かせるようにする
だけに制限することなので使いませんが非常に便利かと思います。

修正する箇所は下記のcanPointcanThumbsUpです。

          // Point
          //bool canPoint = !grabbing || grabPose.AllowPointing;
            bool canPoint = grabPose.AllowPointing && !grabbing;
            float point = canPoint ? m_pointBlend : 0.0f;
            m_animator.SetLayerWeight(m_animLayerIndexPoint, point);

          // Thumbs up
          //bool canThumbsUp = !grabbing || grabPose.AllowThumbsUp;
            bool canThumbsUp = grabPose.AllowThumbsUp && !grabbing;
            float thumbsUp = canThumbsUp ? m_thumbsUpBlend : 0.0f;
            m_animator.SetLayerWeight(m_animLayerIndexThumb, thumbsUp);

短絡評価1を使用しているのでややこしくみえますが、
改良前のコード(コメントアウトしてある箇所)は、先にgrabbing(物をつかんでいるか)の評価を行っているので、
canPointcanThumbsUpのTrue、Falseに関わらず指が動いてしまいます。
つまり、改良前のコード(コメントアウトしてある箇所)は物をつかんだ時に指が動くかどうか
のみを制限しているということです。

改良後のコードでは根本的に指を動かすことを制限するために、判定を逆にしています。
これにより、
①コブシを握る
②握ったコブシを開く
③必要に応じて指を動かせるようにする
という制限を与える事が可能になりました。

より厳密に

制限を加えることができましたが、まだ厳密には制限しきれていません。
画像のように、つまむ動作に関しては反映されてしまいます。
OkHand.PNG

これはアニメーターの設定がゴリゴリになっていることが原因です。
AnimeBlend.PNG

ほとんど使わないのでこうします。
SimpleBelnd.PNG

右手がアニメーションを整理する前、左手が整理した後です。
見ても何も伝わらないと思いますのでアニメーターの設定変更と最後のコードのコピペをして試してみてほしいです。
Hand.gif

最終的なコード

いらないと思ったものはガンガン削除しました。
一応、別のScriptとして作成しました。

HandAction
using System.Linq;
using UnityEngine;
# if UNITY_EDITOR
using UnityEngine.SceneManagement;
# endif

namespace OVRTouchSample
{
    [RequireComponent(typeof(OVRGrabber))]
    [RequireComponent(typeof(HandPose))]
    public class HandAction : MonoBehaviour
    {
        public const string ANIM_LAYER_NAME_POINT = "Point Layer";
        public const string ANIM_LAYER_NAME_THUMB = "Thumb Layer";
        public const string ANIM_PARAM_NAME_FLEX = "Flex";
        public const float THRESH_COLLISION_FLEX = 0.9f;

        public const float INPUT_RATE_CHANGE = 20.0f;

        [SerializeField]
        private OVRInput.Controller m_controller;
        [SerializeField]
        private Animator m_animator = null;

        private Collider[] m_colliders = null;
        private bool m_collisionEnabled = true;
        private OVRGrabber m_grabber;
        private HandPose grabPose;

        private int m_animLayerIndexThumb = -1;
        private int m_animLayerIndexPoint = -1;
        private int m_animParamIndexFlex = -1;
       
        private bool m_isPointing = false;
        private bool m_isGivingThumbsUp = false;
        private float m_pointBlend = 0.0f;
        private float m_thumbsUpBlend = 0.0f;

        private void Start()
        {
            m_grabber = GetComponent<OVRGrabber>();
            grabPose = this.gameObject.GetComponent<HandPose>();

            // Collision starts disabled. We'll enable it for certain cases such as making a fist.
            m_colliders = this.GetComponentsInChildren<Collider>().Where(childCollider => !childCollider.isTrigger).ToArray();
            
            // Get animator layer indices by name, for later use switching between hand visuals
            m_animLayerIndexPoint = m_animator.GetLayerIndex(ANIM_LAYER_NAME_POINT);
            m_animLayerIndexThumb = m_animator.GetLayerIndex(ANIM_LAYER_NAME_THUMB);
            m_animParamIndexFlex = Animator.StringToHash(ANIM_PARAM_NAME_FLEX);

# if UNITY_EDITOR
            OVRPlugin.SendEvent("custom_hand", (SceneManager.GetActiveScene().name == "CustomHands").ToString(), "sample_framework");
# endif
        }

        private void Update()
        {
            UpdateCapTouchStates();

            m_pointBlend = InputValueRateChange(m_isPointing, m_pointBlend);
            m_thumbsUpBlend = InputValueRateChange(m_isGivingThumbsUp, m_thumbsUpBlend);

            float flex = OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, m_controller);

            bool collisionEnabled = m_grabber.grabbedObject == null && flex >= THRESH_COLLISION_FLEX;
            
            UpdateAnimStates();
        }

        // Just checking the state of the index and thumb cap touch sensors, but with a little bit of
        // debouncing.
        private void UpdateCapTouchStates()
        {
            m_isPointing = !OVRInput.Get(OVRInput.NearTouch.PrimaryIndexTrigger, m_controller);
            m_isGivingThumbsUp = !OVRInput.Get(OVRInput.NearTouch.PrimaryThumbButtons, m_controller);
        }

        private float InputValueRateChange(bool isDown, float value)
        {
            float rateDelta = Time.deltaTime * INPUT_RATE_CHANGE;
            float sign = isDown ? 1.0f : -1.0f;
            return Mathf.Clamp01(value + rateDelta * sign);
        }

        private void UpdateAnimStates()
        {
            bool grabbing = m_grabber.grabbedObject != null;
   
            // Flex
            float flex = OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, m_controller);
            m_animator.SetFloat(m_animParamIndexFlex, flex);

            // Point
            bool canPoint = grabPose.AllowPointing&&!grabbing;
            float point = canPoint ? m_pointBlend : 0.0f;
            m_animator.SetLayerWeight(m_animLayerIndexPoint, point);

            // Thumbs up
            bool canThumbsUp =  grabPose.AllowThumbsUp && !grabbing;
            float thumbsUp = canThumbsUp ? m_thumbsUpBlend : 0.0f;
            m_animator.SetLayerWeight(m_animLayerIndexThumb, thumbsUp);

        }
    }
}

なんかOculusの権利関係ややこしそうなので、
コピーライティング書いときます。

Copyright © Facebook Technologies, LLC and
its affiliates. All rights reserved.
  1. 参考リンク

4
5
1

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
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?