20200731のUnityに関する記事は3件です。

【Unity】GithubActionsでUnityを起動してTestRunner実行後の結果を出力するまでの手順

特定のリポジトリにアセットをpushしたタイミングでバリデーションを走らせたい場面があります。

例えばバリデーションに成功した後でpushするというルールを決めたとしても保証は出来ません。それらのアセットがバリデーション済みの保証はできません。

こういう手動に頼らないといけない部分は自動化を検討したいものです。
そこでGithubActionsを使った納品物(アセット)のバリデーションを確実に走らせるようにしてみます。

今回は【Unity】TestRunnerを使ったアセットバリデーションツールを最短経路で作ってみるで作成したアセットバリデーションをGithubActionsで実行できるようにしていきます。

image.png
全体のワークフロー図です。3つのリポジトリを使っています。

先人に感謝

【Unity】GitHub Actions v2でUnity Test Runnerを走らせて、結果をSlackに報告する【入門】
基本はこちらの記事を参考に進めていってます。
とても参考になりました。ありがとうございます。

環境

  • Unity2019.4.4f1

GithubActionsでTestRunnerを走らせるまでの準備

  1. ALFファイルの作成
  2. ULFファイルの取得
  3. ULFファイルの暗号化

GithubActionsでUnityを走らせるためにはライセンス認証が必要となります。
そのためにマニュアルアクティベーションを攻略していきます。
参考 : Offline / Manual Activation

1.ALFファイルの作成

先のワークフロー図のCreateALFプライベートリポジトリでの作業です。

  1. Unityを起動してALFファルを作成
  2. 成果物として出力

このような手順のyamlを作成してGithubActionsを実行します。

CreateALF.yaml
name: Create ALF File
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    container: docker://gableroux/unity3d:2019.4.4f1
    steps:
    - run: mkdir artifact
    # 1.Unityを起動してALFファルを作成
    - run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -logfile -createManualActivationFile || exit 0
    - run: cp "Unity_v2019.4.4f1.alf" artifact
    # 2.成果物として出力
    - uses: actions/upload-artifact@master
      with:
        name: 2019.4.4f1
        path: artifact

このワークフローが成功すると、このようにALFが成果物としてダウンロードできるようになります。
image.png

自分はCI・GithubActions初学者なのでGithubActionsワークフローの構文について調査しておきます。

参考:GitHub Actionsのワークフロー構文

文法 内容 備考
name ワークフロー名 任意の名前
on 実行トリガー push, issueなど
jobs ジョブリスト
build ジョブ名 任意の名前
runs-on (必須)ジョブを実行するマシンの種類指定
container 指定コンテナ
steps 一連のタスク すべてのアクションはステップとして実行される
run OSのシェルを実行
uses ステップの一部として実行されるアクション
with usesで指定したアクションに必要なパラメータ KEY・VALUEのmap

その他構文的に分からなかった部分の調査

actions/upload-artifact

actions/upload-artifactに付随する withのの調査です。

CreateALF(抜粋).yaml
- uses: actions/upload-artifact@master
  with:
    name: 2019.4.4f1
    path: artifact

actions/upload-artifactとはGithub公式が提供するビルドの成果物をアップロードするアクションです。引き渡すパラメータにnamepathwithでセットしています。

namepathの必要性の調べ方

本家のactions/upload-artifactのソースを確認します。
https://github.com/actions/upload-artifact/blob/main/action.yml

以下その一部です。

action(抜粋).yml
inputs: 
  name:
    description: 'Artifact name'
    required: false
  path:
    description: 'A file, directory or wildcard pattern that describes what to upload'
    required: true

このようにinputs(入力)namepathをパラメータとして必要としている事が分かります。pathはrequired: trueなので必須です。

CreateALF.yamlで定義したwithの内容がactions/upload-artifactの入力情報として渡されたという事が分かり、スッキリしました。

参考 : 成果物を使用してワークフローデータを永続化する

actions/upload-artifactの後ろの「@」について

@をつけるとバージョンを指定できます。

  • @ブランチ名
  • @コミットハッシュ

という使い方が出来るようです。

参考 : バージョンされたアクションを使用する例

2.ULFファイルの取得

ULFとはUnityLicenseFileの事で、中身に認証情報が格納されています。
このファイルがオフライン認証を可能にするため重要なファイルです。このファイルを取得する方法です。

https://license.unity3d.com/manual にアクセスします。

image.png
キャプチャのように先程取得したALFファイルを突っ込んでNextボタンをクリックします。

ファイル命名バリデーションが存在する

Unity_v2019.4.4f1.alfでは命名で弾かれてしまいました。
正しくは、 Unity_v2019.4.4.alfです。
ファイル命名に注意が必要です。

その後の質問に回答し、無事にULFファイルを取得できました。
ファイル名はUnity_v2019.x.ulfです。

ULFファイルの暗号化

CreateULFプライベートリポジトリでの作業です。

先程取得したULFファイルをこのリポジトリのルートに配置します。
image.png

ULFファイル(Unity_v2019.x.ulf)はXMLなので中身が丸見えです。
参考サイトの通り、事前にopensslで暗号化して使用時に復号化して使うことにします。

事前にGithub上でパスワードを作成

Settings > Secretsから暗号化に使うパスワードを作成します。

image.png

このようにKeyとValueをセットにした鍵を作成することが出来ます。GithubActions上で以下のようにValueのパスワードにアクセスすることが出来ます。

${{ secrets.Key名  }}

実際のコードでは${{ secrets.CYPHERKEY }}でアクセスしています。

平文のULFファイルを暗号化するGithubActionsが以下です。

CreateULF.yaml
name: Create Unity License File

on: [push]

jobs:
  job:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@master
      - run: mkdir -p artifact
      - run: openssl aes-256-cbc -e -in Unity_v2019.x.ulf -out artifact/Unity_v2019.x.ulf-cipher -k ${{ secrets.CYPHERKEY }}
      - uses: actions/upload-artifact@master
        with:
          name: ciphers
          path: artifact

image.png

ワークフローが完了すると成果物として暗号化されたULFが取得できます。
ファイル名を分かりやすくUnity_v2019.x.ulf-cipherとしています。

GithubActionsでTestRunnerを実行する

TestRunnerWithGithubActionsパブリックリポジトリでの作業です。
https://github.com/baobao/unity-test-runner-with-github-actions

以前執筆した【Unity】TestRunnerを使ったアセットバリデーションツールを最短経路で作ってみるで作成したUnityプロジェクトをGithubActionsでEditorModeTestを走らせます。ソースを丸コピしています。

暗号化したULFをリポジトリのルートに配置します。
image.png

ちなみにローカルPCでコマンドラインからUnityのEditorModeを実行するコマンドは以下です。これをGithubActionsのジョブに組み込んでいきます。

'/Applications/Unity Hub/2019.4.4f1/Unity.app/Contents/MacOS/Unity' -runTests -projectPath /パス(省略)/プロジェクト名 -batchmode -testPlatform EditMode

以下の手順のジョブをyamlに書いてGithubActionsで実行してみます。

  1. 暗号化されたULFの復号化
  2. Unity起動 ULFを使ってライセンス認証
  3. Unity起動 EditorModeTestの実行
  4. 成果物をartifactディレクトリにアップロード
run-test-runner.yml
name: UnityTestRunnerWithGithubActions

on:
  push:
    branches:
    - master

jobs:
  editorTestJob:
    runs-on: ubuntu-latest
    container: docker://gableroux/unity3d:2019.4.4f1

    steps:
    - uses: actions/checkout@master
    # TestRunnerの結果を出力するディレクトリ作成
    - run: mkdir -p artifact
    # 暗号化したULFから復号化したULFを取り出す
    - run: openssl aes-256-cbc -d -in Unity_v2019.x.ulf-cipher -k ${CYPHER_KEY} >> /Unity_v2019.x.ulf
      env:
        CYPHER_KEY: ${{ secrets.cypherkey }}
    # Unity起動 ULFを使ってライセンス認証
    - run: /opt/Unity/Editor/Unity -manualLicenseFile /Unity_v2019.x.ulf -batchmode -nographics -quit || exit 0
    # Unity起動 EditorModeTestの実行
    # 結果(results.xm;)をpath/to/artifact/に出力
    - run: /opt/Unity/Editor/Unity -batchmode -nographics -silent-crashes -logFile -projectPath . -runEditorTests -editorTestsResultFile artifact/results.xml || exit 0
    # 成果物をartifactディレクトリにアップロード
    - uses: actions/upload-artifact@master
      with:
        name: test-result
        path: artifact

このGithubActionsを実行すると以下のようにTestの結果を受け取ることが出来ます。

image.png

テストの時間と無料枠

1回のテストに3分半ほど掛かるようです。

image.png

Github Freeの場合2000分/月の無料枠です。このレベルのジョブで約570回実行できるという事になります。実用的か現段階では分からないです。

ちなみにGithubActionsの無料枠を使い切った場合は、単純に使えなくなるだけのようですね。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MagicLeapのハンドトラッキングの実装

MagicLeapのハンドトラッキング実装の備忘録

基本的にはここの通りに進めていけば問題ない

前提となるもの

  • MagicLeapのmpkをビルドできる段階までのセッティングが完了している
  • The LabでのZeroIterationが可能になっている

開発環境
Windows10
Unity 2019.3.7f1
MagicLeapUnityPackage 0.24.1

出来上がり

こんな感じに各関節
手首、親指 ~ 小指までの座標の取得
ジェスチャの取得( サンプルの中では利用していない )

handtracking.gif

実装方法

下準備

シーンに配置するGameObject
大体のVRデバイスとかで主流な構成の配置にしてます
image.png

CameraRig
ただのルートオブジェクトとして利用しています

Head
MagicLeapパッケージ内 Core > Assets > Prefabs > MainCamera
のプレハブをCameraRigの子オブジェクトにしてHeadと名称を変えたものです、この中にDirectionalLightを子オブジェクトとして配置していますが配置するか否かはお好みで
image.png

Controller
MagicLeapパッケージ内 Examples > Assets > Prefabs > Controller
のプレハブをCameraRigの子オブジェクトとして配置しています
こちらの記事で紹介したコントローラの入力でホームボタンでアプリを閉じるために利用してます
image.png

LHand, RHand
今回のメイン
CameraRigの子オブジェクトとしてGameObjectを作成( Emptyとして )
Thumb( 親指 ), Index( 人差し指 ), Middle( 中指 ), Ring( 薬指 ), Pinky( 小指 )をそれぞれEmptyのGameObjectで作成し、
各指の子オブジェクトThumb, Index, Middleは3個、Ring, Pinkyは2個、SphereObjectを生成、スケールは0.01くらいに設定
image.png

スクリプト

このスクリプトはMagicLeap公式サンプルのものに手を加えたものです
以下のスクリプトをLHand, RHandにアタッチ

/// <summary>
/// ハンドトラッキング.
/// </summary>
public class HandController : MonoBehaviour
{

    [System.Serializable]
    public class HandJointData
    {
        [SerializeField] GameObject wrist;
        [SerializeField] GameObject[] thumb;
        [SerializeField] GameObject[] index;
        [SerializeField] GameObject[] middle;
        [SerializeField] GameObject[] ring;
        [SerializeField] GameObject[] pinky;
        [SerializeField] Material handMaterial;
        [SerializeField] Color color;

        public Vector3 Wrist { get; private set; }
        public Vector3[] Thumb { get; private set; }
        public Vector3[] Index { get; private set; }
        public Vector3[] Middle { get; private set; }
        public Vector3[] Ring { get; private set; }
        public Vector3[] Pinky { get; private set; }
        MLHandTracking.Hand hand;
        LineRenderer[] lines;

        public void Initialize(
            MLHandTracking.Hand _hand)
        {
            hand = _hand;
            Wrist = Vector3.zero;

            // 各関節 + 手首の分, 配列をとる.
            Thumb = new Vector3[thumb.Length + 1];
            Index = new Vector3[index.Length + 1];
            Middle = new Vector3[middle.Length + 1];
            Ring = new Vector3[ring.Length + 1];
            Pinky = new Vector3[pinky.Length + 1];

            lines = new LineRenderer[5];
            lines[0] = thumb[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[0].positionCount = 4;

            lines[1] = index[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[1].positionCount = 4;

            lines[2] = middle[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[2].positionCount = 4;

            lines[3] = ring[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[3].positionCount = 3;

            lines[4] = pinky[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[4].positionCount = 3;

            // LineRendererの初期セッティング.
            foreach (var line in lines)
            {
                line.material = handMaterial;
                line.startColor = color;
                line.endColor = color;
                line.startWidth = 0.01f;
                line.endWidth = 0.01f;
            }
        }


        public void UpdatePositions()
        {
            if (hand == null) return;

            Thumb[0] = hand.Wrist.KeyPoints[0].Position;
            Thumb[1] = hand.Thumb.KeyPoints[0].Position;
            Thumb[2] = hand.Thumb.KeyPoints[1].Position;
            Thumb[3] = hand.Thumb.KeyPoints[2].Position;
            for (var i = 1; i < Thumb.Length; ++i)
            {
                thumb[i - 1].transform.position = Thumb[i];
            }
            lines[0].SetPositions(Thumb);

            Index[0] = hand.Wrist.KeyPoints[0].Position;
            Index[1] = hand.Index.KeyPoints[0].Position;
            Index[2] = hand.Index.KeyPoints[1].Position;
            Index[3] = hand.Index.KeyPoints[2].Position;
            for (var i = 1; i < Index.Length; ++i)
            {
                index[i - 1].transform.position = Index[i];
            }
            lines[1].SetPositions(Index);

            Middle[0] = hand.Wrist.KeyPoints[0].Position;
            Middle[1] = hand.Middle.KeyPoints[0].Position;
            Middle[2] = hand.Middle.KeyPoints[1].Position;
            Middle[3] = hand.Middle.KeyPoints[2].Position;
            for (var i = 1; i < Middle.Length; ++i)
            {
                middle[i - 1].transform.position = Middle[i];
            }
            lines[2].SetPositions(Middle);

            Ring[0] = hand.Wrist.KeyPoints[0].Position;
            Ring[1] = hand.Ring.KeyPoints[0].Position;
            Ring[2] = hand.Ring.KeyPoints[1].Position;
            for (var i = 1; i < Ring.Length; ++i)
            {
                ring[i - 1].transform.position = Ring[i];
            }
            lines[3].SetPositions(Ring);

            Pinky[0] = hand.Wrist.KeyPoints[0].Position;
            Pinky[1] = hand.Pinky.KeyPoints[0].Position;
            Pinky[2] = hand.Pinky.KeyPoints[1].Position;
            for (var i = 1; i < Pinky.Length; ++i)
            {
                pinky[i - 1].transform.position = Pinky[i];
            }
            lines[4].SetPositions(Pinky);

        }
    }


    // ジェスチャ.
    public enum HandPoses
    {
        Ok,
        Finger,
        Thumb,
        OpenHand,
        Fist,
        NoPose,
        NoHand,
    }

    public enum HandId
    {
        RightHand,
        LeftHand
    }


    [SerializeField] HandPoses handPose = HandPoses.NoPose;
    [SerializeField] HandJointData handData;
    [SerializeField] HandId handId;
    MLHandTracking.HandKeyPose[] gestures;
    MLHandTracking.Hand hand;


    void Start()
    {
        // HandTrackingを開始する.
        MLHandTracking.Start();

        hand = handId == HandId.LeftHand ? MLHandTracking.Left : MLHandTracking.Right;
        handData.Initialize(hand);

        gestures = new MLHandTracking.HandKeyPose[6];

        // 各ジェスチャを登録.
        gestures[0] = MLHandTracking.HandKeyPose.Ok;
        gestures[1] = MLHandTracking.HandKeyPose.Finger;
        gestures[2] = MLHandTracking.HandKeyPose.OpenHand;
        gestures[3] = MLHandTracking.HandKeyPose.Fist;
        gestures[4] = MLHandTracking.HandKeyPose.Thumb;
        gestures[5] = MLHandTracking.HandKeyPose.NoHand;

        MLHandTracking.KeyPoseManager.EnableKeyPoses(gestures, true, false);

    }


    void OnDestroy()
    {
        MLHandTracking.Stop();
    }


    void Update()
    {
        handData.UpdatePositions();

        if (GetGesture(hand, MLHandTracking.HandKeyPose.Ok))
        {
            handPose = HandPoses.Ok;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Finger))
        {
            handPose = HandPoses.Finger;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.OpenHand))
        {
            handPose = HandPoses.OpenHand;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Fist))
        {
            handPose = HandPoses.Fist;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Thumb))
        {
            handPose = HandPoses.Thumb;
        }
        else
        {
            handPose = HandPoses.NoPose;
        }
    }


    /// <summary>
    /// ジェスチャの取得.
    /// </summary>
    /// <param name="hand"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    private bool GetGesture(
        MLHandTracking.Hand hand, 
        MLHandTracking.HandKeyPose type)
    {
        if (hand == null) return false;

        return 0.9f < hand.HandKeyPoseConfidence && hand.KeyPose == type;
    }

}


アタッチしたら以下の画像のように各関節のオブジェクトをセット、配列の添え字が若い方が根元に来るように設定
image.png

各パラメータの説明
HandPose : ジェスチャのポーズ名( 今回はInspectorに表示しているだけです )
HandData : 各関節のオブジェクトを保持するクラス、HandCenterは利用していません( うまくトラッキングできなかったので外しました )
HandMaterial : 各関節オブジェクトの球をつなぐ線の描画用マテリアルです、今回はMagicLeapパッケージ内のUIBeamを利用しました
Color : 各関節オブジェクトの球をつなぐ線の色です、今回は左は赤、右は緑で設定しています
HandId : 手の左右を決定する識別子

Unity の設定

この状態でTheLabでMagicLeapと接続してPlayModeに入るとハンドトラッキングされたオブジェクトの様子が確認できると思います
ただしmpkファイルとして出力する際は以下の設定を行わないと実機ではエラーが出てハンドトラッキング及びジェスチャの取得はできません
公式のチュートリアル通りにやれば設定の仕方まで説明されてたけど必要なメソッドとか確認したらすぐ実行したくなっちゃうのよね

image.png

Edit > ProjectSettigs > MagicLeap > ManifestSettings の項目を開き
GestureConfig, GestureSubscribeにチェックを入れる ( 公式チュートリアルだと明示的にLowLatencyLightwearにもチェックを入れるように説明されているが現バージョンでは自動で入ってる? )

これでmpkを出力して実機でテストするとハンドトラッキングが実装できているはずです

あとがき

これはTheLabからDLしてきたMagicLeapUnityPackageでのサンプルです、MagicLeapToolKitでのハンドトラッキングはまだ触ったことがないので後日記事にできればと思います

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MagicLeapのハンドトラッキング実装

MagicLeapのハンドトラッキング実装の備忘録

基本的にはここの通りに進めていけば問題ない

前提となるもの

  • MagicLeapのmpkをビルドできる段階までのセッティングが完了している
  • The LabでのZeroIterationが可能になっている

開発環境
Windows10
Unity 2019.3.7f1
MagicLeapUnityPackage 0.24.1

出来上がり

こんな感じに各関節
手首、親指 ~ 小指までの座標の取得
ジェスチャの取得( サンプルの中では利用していない )

handtracking.gif

実装方法

下準備

シーンに配置するGameObject
大体のVRデバイスとかで主流な構成の配置にしてます
image.png

CameraRig
ただのルートオブジェクトとして利用しています

Head
MagicLeapパッケージ内 Core > Assets > Prefabs > MainCamera
のプレハブをCameraRigの子オブジェクトにしてHeadと名称を変えたものです、この中にDirectionalLightを子オブジェクトとして配置していますが配置するか否かはお好みで
image.png

Controller
MagicLeapパッケージ内 Examples > Assets > Prefabs > Controller
のプレハブをCameraRigの子オブジェクトとして配置しています
こちらの記事で紹介したコントローラの入力でホームボタンでアプリを閉じるために利用してます
image.png

LHand, RHand
今回のメイン
CameraRigの子オブジェクトとしてGameObjectを作成( Emptyとして )
Thumb( 親指 ), Index( 人差し指 ), Middle( 中指 ), Ring( 薬指 ), Pinky( 小指 )をそれぞれEmptyのGameObjectで作成し、
各指の子オブジェクトThumb, Index, Middleは3個、Ring, Pinkyは2個、SphereObjectを生成、スケールは0.01くらいに設定
image.png

スクリプト

このスクリプトはMagicLeap公式サンプルのものに手を加えたものです
以下のスクリプトをLHand, RHandにアタッチ

using UnityEngine;
using UnityEngine.XR.MagicLeap;



/// <summary>
/// ハンドトラッキング.
/// </summary>
public class HandController : MonoBehaviour
{

    [System.Serializable]
    public class HandJointData
    {
        [SerializeField] GameObject wrist;
        [SerializeField] GameObject[] thumb;
        [SerializeField] GameObject[] index;
        [SerializeField] GameObject[] middle;
        [SerializeField] GameObject[] ring;
        [SerializeField] GameObject[] pinky;
        [SerializeField] Material handMaterial;
        [SerializeField] Color color;

        public Vector3 Wrist { get; private set; }
        public Vector3[] Thumb { get; private set; }
        public Vector3[] Index { get; private set; }
        public Vector3[] Middle { get; private set; }
        public Vector3[] Ring { get; private set; }
        public Vector3[] Pinky { get; private set; }
        MLHandTracking.Hand hand;
        LineRenderer[] lines;

        public void Initialize(
            MLHandTracking.Hand _hand)
        {
            hand = _hand;
            Wrist = Vector3.zero;

            // 各関節 + 手首の分, 配列をとる.
            Thumb = new Vector3[thumb.Length + 1];
            Index = new Vector3[index.Length + 1];
            Middle = new Vector3[middle.Length + 1];
            Ring = new Vector3[ring.Length + 1];
            Pinky = new Vector3[pinky.Length + 1];

            lines = new LineRenderer[5];
            lines[0] = thumb[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[0].positionCount = 4;

            lines[1] = index[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[1].positionCount = 4;

            lines[2] = middle[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[2].positionCount = 4;

            lines[3] = ring[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[3].positionCount = 3;

            lines[4] = pinky[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[4].positionCount = 3;

            // LineRendererの初期セッティング.
            foreach (var line in lines)
            {
                line.material = handMaterial;
                line.startColor = color;
                line.endColor = color;
                line.startWidth = 0.01f;
                line.endWidth = 0.01f;
            }
        }


        public void UpdatePositions()
        {
            if (hand == null) return;

            Thumb[0] = hand.Wrist.KeyPoints[0].Position;
            Thumb[1] = hand.Thumb.KeyPoints[0].Position;
            Thumb[2] = hand.Thumb.KeyPoints[1].Position;
            Thumb[3] = hand.Thumb.KeyPoints[2].Position;
            for (var i = 1; i < Thumb.Length; ++i)
            {
                thumb[i - 1].transform.position = Thumb[i];
            }
            lines[0].SetPositions(Thumb);

            Index[0] = hand.Wrist.KeyPoints[0].Position;
            Index[1] = hand.Index.KeyPoints[0].Position;
            Index[2] = hand.Index.KeyPoints[1].Position;
            Index[3] = hand.Index.KeyPoints[2].Position;
            for (var i = 1; i < Index.Length; ++i)
            {
                index[i - 1].transform.position = Index[i];
            }
            lines[1].SetPositions(Index);

            Middle[0] = hand.Wrist.KeyPoints[0].Position;
            Middle[1] = hand.Middle.KeyPoints[0].Position;
            Middle[2] = hand.Middle.KeyPoints[1].Position;
            Middle[3] = hand.Middle.KeyPoints[2].Position;
            for (var i = 1; i < Middle.Length; ++i)
            {
                middle[i - 1].transform.position = Middle[i];
            }
            lines[2].SetPositions(Middle);

            Ring[0] = hand.Wrist.KeyPoints[0].Position;
            Ring[1] = hand.Ring.KeyPoints[0].Position;
            Ring[2] = hand.Ring.KeyPoints[1].Position;
            for (var i = 1; i < Ring.Length; ++i)
            {
                ring[i - 1].transform.position = Ring[i];
            }
            lines[3].SetPositions(Ring);

            Pinky[0] = hand.Wrist.KeyPoints[0].Position;
            Pinky[1] = hand.Pinky.KeyPoints[0].Position;
            Pinky[2] = hand.Pinky.KeyPoints[1].Position;
            for (var i = 1; i < Pinky.Length; ++i)
            {
                pinky[i - 1].transform.position = Pinky[i];
            }
            lines[4].SetPositions(Pinky);

        }
    }


    // ジェスチャ.
    public enum HandPoses
    {
        Ok,
        Finger,
        Thumb,
        OpenHand,
        Fist,
        NoPose,
        NoHand,
    }

    public enum HandId
    {
        RightHand,
        LeftHand
    }


    [SerializeField] HandPoses handPose = HandPoses.NoPose;
    [SerializeField] HandJointData handData;
    [SerializeField] HandId handId;
    MLHandTracking.HandKeyPose[] gestures;
    MLHandTracking.Hand hand;


    void Start()
    {
        // HandTrackingを開始する.
        MLHandTracking.Start();

        hand = handId == HandId.LeftHand ? MLHandTracking.Left : MLHandTracking.Right;
        handData.Initialize(hand);

        gestures = new MLHandTracking.HandKeyPose[6];

        // 各ジェスチャを登録.
        gestures[0] = MLHandTracking.HandKeyPose.Ok;
        gestures[1] = MLHandTracking.HandKeyPose.Finger;
        gestures[2] = MLHandTracking.HandKeyPose.OpenHand;
        gestures[3] = MLHandTracking.HandKeyPose.Fist;
        gestures[4] = MLHandTracking.HandKeyPose.Thumb;
        gestures[5] = MLHandTracking.HandKeyPose.NoHand;

        MLHandTracking.KeyPoseManager.EnableKeyPoses(gestures, true, false);

    }


    void OnDestroy()
    {
        MLHandTracking.Stop();
    }


    void Update()
    {
        handData.UpdatePositions();

        if (GetGesture(hand, MLHandTracking.HandKeyPose.Ok))
        {
            handPose = HandPoses.Ok;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Finger))
        {
            handPose = HandPoses.Finger;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.OpenHand))
        {
            handPose = HandPoses.OpenHand;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Fist))
        {
            handPose = HandPoses.Fist;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Thumb))
        {
            handPose = HandPoses.Thumb;
        }
        else
        {
            handPose = HandPoses.NoPose;
        }
    }


    /// <summary>
    /// ジェスチャの取得.
    /// </summary>
    /// <param name="hand"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    private bool GetGesture(
        MLHandTracking.Hand hand, 
        MLHandTracking.HandKeyPose type)
    {
        if (hand == null) return false;

        return 0.9f < hand.HandKeyPoseConfidence && hand.KeyPose == type;
    }

}


アタッチしたら以下の画像のように各関節のオブジェクトをセット、配列の添え字が若い方が根元に来るように設定
image.png

各パラメータの説明
HandPose : ジェスチャのポーズ名( 今回はInspectorに表示しているだけです )
HandData : 各関節のオブジェクトを保持するクラス、HandCenterは利用していません( うまくトラッキングできなかったので外しました )
HandMaterial : 各関節オブジェクトの球をつなぐ線の描画用マテリアルです、今回はMagicLeapパッケージ内のUIBeamを利用しました
Color : 各関節オブジェクトの球をつなぐ線の色です、今回は左は赤、右は緑で設定しています
HandId : 手の左右を決定する識別子

Unity の設定

この状態でTheLabでMagicLeapと接続してPlayModeに入るとハンドトラッキングされたオブジェクトの様子が確認できると思います
ただしmpkファイルとして出力する際は以下の設定を行わないと実機ではエラーが出てハンドトラッキング及びジェスチャの取得はできません
公式のチュートリアル通りにやれば設定の仕方まで説明されてたけど必要なメソッドとか確認したらすぐ実行したくなっちゃうのよね

image.png

Edit > ProjectSettigs > MagicLeap > ManifestSettings の項目を開き
GestureConfig, GestureSubscribeにチェックを入れる ( 公式チュートリアルだと明示的にLowLatencyLightwearにもチェックを入れるように説明されているが現バージョンでは自動で入ってる? )

これでmpkを出力して実機でテストするとハンドトラッキングが実装できているはずです

あとがき

これはTheLabからDLしてきたMagicLeapUnityPackageでのサンプルです、MagicLeapToolKitでのハンドトラッキングはまだ触ったことがないので後日記事にできればと思います

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む