4
2

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 3 years have passed since last update.

Magic LeapAdvent Calendar 2020

Day 4

全集中・Magic Leapの呼吸 肆ノ型 手入力 "かすたむ じぇすちゃ"

Last updated at Posted at 2020-12-03

なにをするの?

今回作るものとしてはMagicLeapのジェスチャ( 8種類 )を組み合わせてカスタムのジェスチャを作成するというもの

[サンプル動画( twitterがおしゃかポンになったときの予備 )](https://www.youtube.com/watch?v=3loqi-mq3yo)

プロジェクトはこちらのリポジトリにあります

下準備

本記事は基本的なビルドまでの手順はなされていることを前提として記事を書いています
ビルド時に利用する認証ファイルとかZeroIterationの設定の仕方とか

開発環境

  • Lumin SDK : 0.24.1
  • MagicLeap Unity Package : 0.24.1 ( 少し古いけど Lumin SDKと合わせました、 0.24.2はUnity2020以降で利用できます )
  • MagicLeap Tool-Kit : MagicLeapToolKitのリポジトリ最新版
  • UniRx : 7.1.0
  • UniTask : 2.0.26

ProjectSettingsのXR Plug-in Management の MagicLeapの項目にチェックを入れる
image.png

今回はハンドジェスチャを利用するので ProjectSettings > MagicLeap > ManifestSettings より GestureConfig, GestureSubscribe を有効化
image.png

シーンの構築

CameraRigの作成
MagicLeap > Core > Assets > Prefabs からMain Cameraをシーンに配置し、一旦そのMain Cameraオブジェクトを任意のフォルダにプレハブとして保存する( 名前を CameraRig に変更 )
image.png

HandControllerの作成
CameraRigオブジェクトの下にHandControllerオブジェクトを作成し、 MagicLeap-Tools > Code > Input > Hands > HandInput をアタッチします
image.png


このままだと手が描画されておらず確認しづらいのでサンプルにあるHandVisualizerを利用してテストします

HandControllerオブジェクトの下に MagicLeap > Examples > Assets > Prefabs > HandVisualizer オブジェクトを配置し、HandVisualizerコンポーネントのCenterの項目にはnullを指定します( Centerが設定されている場合は球で手の各関節を表示する機能がONになり見た目上邪魔になるため )
image.png

HandVisualizserオブジェクトの子オブジェクトにLHand, RHand オブジェクトを作成し、HandInputシーンから [VISUALIZERS]オブジェクトの中のKeyPointVisualizers 以下のオブジェクトを複製、Right~ はRHandオブジェクトの子オブジェクトに Left~はLHandオブジェクトの子オブジェクトにします
image.png

上の手順を終えたものがこちら、なおCenterオブジェクトはHandVisualizerオブジェクトを複製する際についてきたものなので削除しても構いません
image.png

RHand, LHand それぞれにHandSkeletonVisualizerコンポーネント、AxisVisualizerコンポーネントをアタッチ、BoneColorはお好きな色を設定
image.png

ここまでの動作確認

なぜかGameウィンドウに描画されていなかったのでSceneViewの方のスクショ
image.png

ZeroIterationでの実機上でのスクショ
ML_20201128_19.14.58.jpg

スクリプトの作成

MagicLeapが提供しているデフォルトのジェスチャ( 8種類 )を組み合わせてカスタムジェスチャを作成するスクリプトを作ります
InteractionModel_InputMethods_Gesture_Example_Main.png

スクリプト全容( せっかちな人はこれをコピペ )

using System;
using MagicLeapTools;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.XR.MagicLeap;
using Debug = UnityEngine.Debug;
using Cysharp.Threading.Tasks;
using UniRx;


namespace AdventCalendar
{
    /// <summary>
    /// ハンドコントローラ.
    /// </summary>
    public class HandController : MonoBehaviour
    {

        struct KeyInfo
        {
            public HandPose pose;
            public float time;
        }

        
        public enum HandPose
        {
            LFinger,
            RFinger,
            LFist,
            RFist,
            LPinch,
            RPinch,
            LThumb,
            RThumb,
            LL,
            RL,
            LOpenHand,
            ROpenHand,
            LOk,
            ROk,
            LC,
            RC,
            LNoPose,
            RNoPose,
            LNoHand,
            RNoHand
        }


        private HandPose handPose;
        private ManagedHand rHand;
        private ManagedHand lHand;
        [SerializeField] private GameObject handVisualizer;
        [field: SerializeField] public bool IsGestureLogOutput { get; set; } = false;

        

        
        private async void Start()
        {
            await Setup();            
            SwitchHandVisualize();
        }


        private async UniTask Setup()
        {
            await UniTask.WaitUntil(() => HandInput.Ready);
            rHand = HandInput.Right;
            lHand = HandInput.Left;
            rHand.Gesture.OnKeyPoseChanged += OnHandGesturePoseChanged;
            lHand.Gesture.OnKeyPoseChanged += OnHandGesturePoseChanged;
                
            // 確認用にジェスチャをDebug.Logに出力.
            this.ObserveEveryValueChanged(_ => handPose).Subscribe(e =>
            {
                if (IsGestureLogOutput)
                    Debug.Log($"Key {e}");
            });
        }


        private void SwitchHandVisualize()
        {
#if UNITY_EDITOR
            handVisualizer.SetActive(true);
#elif !UNITY_EDITOR || UNITY_LUMIN
            handVisualizer.SetActive(false);
#endif
        }


        private void OnHandGesturePoseChanged(
            ManagedHand hand,
            MLHandTracking.HandKeyPose pose)
        {
            bool isLeft = hand.Hand.Type == MLHandTracking.HandType.Left;
            switch (pose)
            {
                case MLHandTracking.HandKeyPose.C: handPose = isLeft ? HandPose.LC : HandPose.RC; break;
                case MLHandTracking.HandKeyPose.Finger: handPose = isLeft ? HandPose.LFinger : HandPose.RFinger; break;
                case MLHandTracking.HandKeyPose.Fist: handPose = isLeft ? HandPose.LFist : HandPose.RFist; break;
                case MLHandTracking.HandKeyPose.L: handPose = isLeft ? HandPose.LL : HandPose.RL; break;
                case MLHandTracking.HandKeyPose.Ok: handPose = isLeft ?  HandPose.LOk : HandPose.ROk; break;
                case MLHandTracking.HandKeyPose.Pinch: handPose = isLeft ? HandPose.LPinch : HandPose.RPinch; break;
                case MLHandTracking.HandKeyPose.Thumb: handPose = isLeft ? HandPose.LThumb : HandPose.RThumb; break;
                case MLHandTracking.HandKeyPose.NoHand: handPose = isLeft ? HandPose.LNoHand : HandPose.RNoHand; break;
                case MLHandTracking.HandKeyPose.NoPose: handPose = isLeft ? HandPose.LNoPose : HandPose.RNoPose; break;
                case MLHandTracking.HandKeyPose.OpenHand: handPose = isLeft ? HandPose.LOpenHand : HandPose.ROpenHand; break;
            }
        }


        /// <summary>
        /// ジェスチャコマンドのオブザーバー作成.
        /// </summary>
        /// <param name="time"></param>
        /// <param name="poseA"></param>
        /// <param name="poseB"></param>
        /// <returns></returns>
        private IObservable<KeyInfo> CreateGestureCommandObserver(
            float time,
            HandPose poseA,
            HandPose poseB)
        {
            // 指定したキーの判定を通知するObserverを返す.
            IObservable<KeyInfo> GetInputObserver(HandPose pose)
            {
                return this.ObserveEveryValueChanged(_ => handPose)
                    .Where(k => k == pose)
                    .Select(k => new KeyInfo{pose = k, time = Time.realtimeSinceStartup});
            }

            var observer = GetInputObserver(poseA);
            observer = observer.Merge(GetInputObserver(poseB))
                .Buffer(2, 1)
                .Where(b => b[1].time - b[0].time < time)
                .Where(b => b[0].pose == poseA && b[1].pose == poseB)
                .Select(b => b[1]);

            return observer;
        }


        /// <summary>
        /// カスタムジェスチャを登録し、登録したカスタムジェスチャが発火されたら実行する.
        /// </summary>
        /// <param name="time"></param>
        /// <param name="poseA"></param>
        /// <param name="poseB"></param>
        /// <param name="callback"></param>
        public void RegisterCustomGesture(
            float time,
            HandPose poseA,
            HandPose poseB,
            UnityAction callback,
            Func<bool> option = null)
        {
            CreateGestureCommandObserver(time, poseA, poseB)
                .Subscribe(e => callback?.Invoke())
                .AddTo(this);
        }

    }
}

スクリプトの解説

先ずカスタムのジェスチャを作成するためにハンドポーズを宣言します、MagicLeapのデフォルトのHandPoseは MLHandTracking.cs 内に定義されています、このままでも使えますが使い勝手が悪い為一旦カスタムのジェスチャを定義してそちらに変換するようにします。


        public enum HandPose
        {
            LFinger,
            RFinger,
            LFist,
            RFist,
            LPinch,
            RPinch,
            LThumb,
            RThumb,
            LL,
            RL,
            LOpenHand,
            ROpenHand,
            LOk,
            ROk,
            LC,
            RC,
            LNoPose,
            RNoPose,
            LNoHand,
            RNoHand
        }


取得したハンドジェスチャをカスタムのジェスチャに変換する処理

        private void OnHandGesturePoseChanged(
            ManagedHand hand,
            MLHandTracking.HandKeyPose pose)
        {
            bool isLeft = hand.Hand.Type == MLHandTracking.HandType.Left;
            switch (pose)
            {
                case MLHandTracking.HandKeyPose.C: handPose = isLeft ? HandPose.LC : HandPose.RC; break;
                case MLHandTracking.HandKeyPose.Finger: handPose = isLeft ? HandPose.LFinger : HandPose.RFinger; break;
                case MLHandTracking.HandKeyPose.Fist: handPose = isLeft ? HandPose.LFist : HandPose.RFist; break;
                case MLHandTracking.HandKeyPose.L: handPose = isLeft ? HandPose.LL : HandPose.RL; break;
                case MLHandTracking.HandKeyPose.Ok: handPose = isLeft ?  HandPose.LOk : HandPose.ROk; break;
                case MLHandTracking.HandKeyPose.Pinch: handPose = isLeft ? HandPose.LPinch : HandPose.RPinch; break;
                case MLHandTracking.HandKeyPose.Thumb: handPose = isLeft ? HandPose.LThumb : HandPose.RThumb; break;
                case MLHandTracking.HandKeyPose.NoHand: handPose = isLeft ? HandPose.LNoHand : HandPose.RNoHand; break;
                case MLHandTracking.HandKeyPose.NoPose: handPose = isLeft ? HandPose.LNoPose : HandPose.RNoPose; break;
                case MLHandTracking.HandKeyPose.OpenHand: handPose = isLeft ? HandPose.LOpenHand : HandPose.ROpenHand; break;
            }
        }


カスタムジェスチャ購読用のObserverを作成する処理
指定時間以内に 1番目のジェスチャ -> 2番目のジェスチャ と連続で呼ばれたイベントを購読するオブザーバーを作成し、返り値で返します

   private IObservable<KeyInfo> CreateGestureCommandObserver(
            float time,
            HandPose poseA,
            HandPose poseB)
        {
            // 指定したキーの判定を通知するObserverを返す.
            IObservable<KeyInfo> GetInputObserver(HandPose pose)
            {
                return this.ObserveEveryValueChanged(_ => handPose)
                    .Where(k => k == pose)
                    .Select(k => new KeyInfo{pose = k, time = Time.realtimeSinceStartup});
            }

            var observer = GetInputObserver(poseA);
            observer = observer.Merge(GetInputObserver(poseB))
                .Buffer(2, 1)
                .Where(b => b[1].time - b[0].time < time)
                .Where(b => b[0].pose == poseA && b[1].pose == poseB)
                .Select(b => b[1]);

            return observer;
        }

動作確認

サンプル用スクリプト

Sample.cs

using UnityEngine;

namespace AdventCalendar
{
    public class Sample : MonoBehaviour
    {

        [SerializeField] private Transform camera;
        [SerializeField] private HandController handController;
        [SerializeField] private GameObject objA;
        [SerializeField] private GameObject objB;
        [SerializeField] private GameObject objC;
        
        private void Start()
        {
            // 1秒以内に右手グー( RFist ) -> 右手パー( ROpenHand )で発火 SpawnObjA()を処理する.
            handController.RegisterCustomGesture(1f, HandController.HandPose.RFist, HandController.HandPose.ROpenHand, SpawnObjA);

            // 1秒以内に左手グー( LFist ) -> 左手パー( LOpenHand )で発火 SpawnObjB()を処理する.
            handController.RegisterCustomGesture(1f, HandController.HandPose.LFist, HandController.HandPose.LOpenHand, SpawnObjB);

            // 1秒以内に左手OK ( LOk ) -> 右手OK ( ROk )で発火 SpawnObjC()を発火する.
            handController.RegisterCustomGesture(1f, HandController.HandPose.LOk, HandController.HandPose.ROk, SpawnObjC);
        }


        private void SpawnObjA()
        {
            GameObject obj = Instantiate(objA);
            obj.transform.position = camera.position + (camera.forward * 0.5f);
        }

        
        private void SpawnObjB()
        {
            GameObject obj = Instantiate(objB);
            obj.transform.position = camera.position + (camera.forward * 0.5f);
        }
        
        
        private void SpawnObjC()
        {
            GameObject obj = Instantiate(objC);
            obj.transform.position = camera.position + (camera.forward * 0.5f);
        }

    }
}

SpawnObj.cs
テスト用のオブジェクト、生成されてから5秒で破棄するだけの処理


using System.Collections;
using UnityEngine;

namespace AdventCalendar
{
    /// <summary>
    /// スポーンしたオブジェクト用スクリプト、5秒で破棄する.
    /// </summary>
    public class SpawnObj : MonoBehaviour
    {
        private void Start()
        {
            StartCoroutine(AutoDeath());
        }


        private IEnumerator AutoDeath()
        {
            yield return new WaitForSeconds(5f);
            Destroy(gameObject);
        }

    }
    
}

シーン上に適当なオブジェクト ObjA ~ ObjC を作成しプレハブにし、それぞれにSpawnObjコンポーネントをアタッチ
image.png

動作確認用に Sampleオブジェクトを作成し Sampleコンポーネントをアタッチ、シーン上に配置します、オブジェクトの参照周りは以下の画像の通り
image.png

Sample.csのStart()にてカスタムジェスチャを登録し、イベントが発火されたら登録したオブジェクトを生成するようにしています
今回のサンプルでは

  • 右Fist -> 右OpenHand で ObjA の生成
  • 左Fist -> 左OpenHand で ObjB の生成
  • 左OK -> 右OK で ObjC の生成

を行います


        private void Start()
        {
            handController.RegisterCustomGesture(1f, HandController.HandPose.RFist, HandController.HandPose.ROpenHand, SpawnObjA);
            handController.RegisterCustomGesture(1f, HandController.HandPose.LFist, HandController.HandPose.LOpenHand, SpawnObjB);
            handController.RegisterCustomGesture(1f, HandController.HandPose.LOk, HandController.HandPose.ROk, SpawnObjC);
        }

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?