しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Unity + MRTK の HoloLens アプリで立てた指の座標を取得する

※ これは 2018/01/08 Unity 2017.3.0f3、Mixed Reality Toolkit 2017.2.1.3 Hot Fix 時点の情報です

最新版では動作が異なる可能性がありますのでご注意ください

指を立てたらねこモデルが顔をこちらにむけることはできました

f:id:matatabi_ux:20180401213011g:plain

HoloLens 実機ではモーションコントローラーがないとできないかもしれませんが、今回はさらに立てた指の座標を取得して、指を追いかけるかのようにねこモデルを動かしてみたいと思います

やることはねこモデルにアタッチしているスクリプトコードの修正のみ

using HoloToolkit.Unity.InputModule;
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(Animator))]
public class CatWalker : MonoBehaviour, ISourceStateHandler
{
    /// <summary>
    /// 顔の部分のボーン
    /// </summary>
    [SerializeField]
    private Transform headBone = null;

    /// <summary>
    /// キャッシュ用カメラ Transform
    /// </summary>
    private Transform camra;

    private Animator animator;
    private CharacterController controller;
    private uint sourceId = 0;
    private IInputSource inputSource = null;

    /// <summary>
    /// 状態の列挙型
    /// </summary>
    internal enum State
    {
        /// <summary>
        /// 待機状態
        /// </summary>
        Idle,

        /// <summary>
        /// 歩行中
        /// </summary>
        Walk,

        /// <summary>
        /// こちらを向く
        /// </summary>
        Look,
    }

    /// <summary>
    /// 現在の状態
    /// </summary>
    private State state;

    /// <summary>
    /// 歩行状態
    /// </summary>
    public bool IsWalking
    {
        get { return this.animator.GetBool("IsWalking"); }
        set
        {
            // 変更があった場合のみ反映
            if (value != this.IsWalking)
            {
                this.animator.SetBool("IsWalking", value);
                this.state = value ? State.Walk : State.Idle;
            }
        }
    }

    /// <summary>
    /// 顔向き状態
    /// </summary>
    public bool IsLooking
    {
        get { return this.animator.GetBool("IsLooking"); }
        set
        {
            var newState = value && !this.IsWalking;

            // 変更があった場合のみ反映
            if (newState != this.IsLooking)
            {
                this.animator.SetBool("IsLooking", newState);
                this.state = newState ? State.Look : this.IsWalking ? State.Walk : State.Idle;
            }
        }
    }

    /// <summary>
    /// 初期処理
    /// </summary>
    public void Start()
    {
        this.camra = Camera.main.transform;
        this.animator = this.GetComponent<Animator>();
        this.controller = this.GetComponent<CharacterController>();
        this.state = State.Idle;

        InputManager.Instance.AddGlobalListener(this.gameObject);
    }

    /// <summary>
    /// 破棄時の処理
    /// </summary>
    public void OnDestroy()
    {
        if (this != null && InputManager.Instance != null)
        {
            InputManager.Instance.RemoveGlobalListener(this.gameObject);
        }
    }

    /// <summary>
    /// 可変毎フレームごとの処理
    /// </summary>
    public void Update()
    {
        // 距離の判定に高低差を考慮しない
        var cameraPosition = this.camra.position;
        cameraPosition.y = this.transform.position.y;

        var distance = Vector3.Distance(cameraPosition, this.transform.position);
        switch (this.state)
        {
            case State.Idle:
            case State.Look:
                this.IsWalking = distance > 2f;
                break;

            case State.Walk:
                this.IsWalking = distance > 1f;
                break;
        }
    }

    /// <summary>
    /// 固定毎フレームの処理
    /// </summary>
    public void FixedUpdate()
    {
        switch (this.state)
        {
            case State.Walk:
                var cameraPosition = this.camra.position;

                // 身体全体をカメラに向ける(Y軸の回転)
                cameraPosition.y = this.transform.position.y;
                this.transform.LookAt(cameraPosition);

                var distance = Vector3.Distance(cameraPosition, this.transform.position);
                // カメラに向かって移動
                var delta = Mathf.Clamp(distance - 0.9f, 0f, 0.01f);
                this.controller.Move(Vector3.MoveTowards(this.transform.position, cameraPosition, delta) - this.transform.position);

                break;

            case State.Look:
                cameraPosition = this.GetInputSourcePosition();

                // 身体全体をカメラに向ける(Y軸の回転)
                cameraPosition.y = this.transform.position.y;
                this.transform.LookAt(cameraPosition);
                break;
        }

        // 重力をかける
        this.controller.Move(Physics.gravity);
    }

    /// <summary>
    /// 入力ソースの座標を取得
    /// </summary>
    /// <returns>入力ソースの座標</returns>
    private Vector3 GetInputSourcePosition()
    {
        Vector3 position = this.camra.position;
        if (this.inputSource != null)
        {
            this.inputSource.TryGetPointerPosition(this.sourceId, out position);
        }

        return position;
    }

    /// <summary>
    /// 遅延フレームの処理
    /// </summary>
    public void LateUpdate()
    {
        switch (this.state)
        {
            case State.Look:
                // 顔だけカメラに向かせる(X軸の回転)
                this.headBone.LookAt(this.GetInputSourcePosition());
                var angle = this.headBone.rotation.eulerAngles.x;
                angle = angle > 180f ? angle - 360f : angle;
                this.headBone.localRotation = Quaternion.Euler(0f + Mathf.Clamp(angle, -30f, 30f), 0f, 0f);

                break;
        }
    }

    /// <summary>
    /// 入力ソース認識時のイベントハンドラ
    /// </summary>
    /// <param name="eventData">イベント情報</param>
    public void OnSourceDetected(SourceStateEventData eventData)
    {
        InteractionSourceInfo info;
        if (eventData.InputSource.TryGetSourceKind(eventData.SourceId, out info))
        {
            switch (info)
            {
                case InteractionSourceInfo.Hand:
                    this.sourceId = eventData.SourceId;
                    this.inputSource = eventData.InputSource;
                    this.IsLooking = true;
                    break;
            }
        }
    }

    /// <summary>
    /// 入力ソースの認識が解除時のイベントハンドラ
    /// </summary>
    /// <param name="eventData">イベント情報</param>
    public void OnSourceLost(SourceStateEventData eventData)
    {
        if (eventData.SourceId == this.sourceId)
        {
            this.IsLooking = false;
            this.inputSource = null;
        }
    }
}

OnSourceDetected イベントハンドラ内で InputSource を保存しておき、下記 GetInputSourcePosition() メソッドで立てた指の座標を取得するようにしました

    /// <summary>
    /// 入力ソースの座標を取得
    /// </summary>
    /// <returns>入力ソースの座標</returns>
    private Vector3 GetInputSourcePosition()
    {
        Vector3 position = this.camra.position;
        if (this.inputSource != null)
        {
            this.inputSource.TryGetPointerPosition(this.sourceId, out position);
        }

        return position;
    }

この座標はこれまでのカメラ座標(HoloLens をかけている人の位置)の代わりに、 FixedUpdate() と LateUpdate() のねこモデルの向きを変える処理の中で利用するようにしています

さてさっそく動かしてみます

f:id:matatabi_ux:20180402205136g:plain

ぐるっと回ってにゃんこの目!