しっぽを追いかけて

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

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

Unity で作った UWP アプリから Surface Dial を使いたい

※ これは 2016/11/20 時点の情報です

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

前回Surface Dial を UWP で利用してみました

今回はさらに Unity で作った UWP アプリから使ってみたいと思います

※ 今回のサンプルコードは下記にアップしています

何を作るかというと、みなさんおなじみのブロック崩しゲームを Unity で作り、操作に Surface Dial を利用しようと思います

ブロック崩しゲーム本体の作りはサンプルコード等をご覧いただき、今回は Surface Dial の連携の部分を取り上げます

といっても、基本的には以前下記の記事に書いた方法でいけました!

f:id:matatabi_ux:20161121203049p:plain

こんな感じで Unity のプロジェクト以外に、Windows 10 UWP の API を利用するライブラリプロジェクトと、このライブラリと Unity の間を取り持つライブラリのインタフェースプロジェクトを用意しました

InputManager というのが Surface Dial の入力を管理するクラスで下記のようなコードです

using System;
#if NETFX_CORE
using Windows.Storage.Streams;
using Windows.UI.Input;
#endif

namespace BreakoutSample.UwpPlugin
{
    /// <summary>
    /// 入力管理クラス
    /// </summary>
    public class InputManager
    {
        /// <summary>
        /// ダイアルボタンクリックフラグ
        /// </summary>
        public bool IsButtonClicked { get; set; }

        /// <summary>
        /// 水平移動量
        /// </summary>
        public float AxisXDelta { get; set; }

        /// <summary>
        /// インスタンス
        /// </summary>
        private static InputManager instance;

        /// <summary>
        /// インスタンス
        /// </summary>
        public static InputManager Instance
        {
            get
            {
                return instance ?? (instance = new InputManager());
            }
        }

        /// <summary>
        /// 加速度係数
        /// </summary>
        private const float Accel = 0.5f;

#if NETFX_CORE
        // <summary>
        /// Surface Dial 管理クラス
        /// </summary>
        private RadialController control;
#endif
        /// <summary>
        /// コンストラクタ
        /// </summary>
        private InputManager()
        {
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        static InputManager()
        {
        }

        /// <summary>
        /// 初期化処理
        /// </summary>
        public void Initialize()
        {
#if NETFX_CORE
            this.control = RadialController.CreateForCurrentView();

            // オリジナルのメニューを追加
            var icon = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/CatHand.png"));
            var item = RadialControllerMenuItem.CreateFromIcon("Play Game", icon);
            this.control.Menu.Items.Add(item);

            // イベントの購読
            this.control.ButtonClicked += this.OnButtonClicked;
            this.control.RotationChanged += this.OnRotationChanged;
#endif
        }

#if NETFX_CORE
        /// <summary>
        /// ダイアル回転イベントハンドラ
        /// </summary>
        /// <param name="sender">イベント発行者</param>
        /// <param name="args">イベント引数</param>
        private void OnRotationChanged(RadialController sender, RadialControllerRotationChangedEventArgs args)
        {
            this.AxisXDelta = ((float)args.RotationDeltaInDegrees) * Accel;
        }

        /// <summary>
        /// ダイアルボタンクリックイベントハンドラ
        /// </summary>
        /// <param name="sender">イベント発行者</param>
        /// <param name="args">イベント引数</param>
        private void OnButtonClicked(RadialController sender, RadialControllerButtonClickedEventArgs args)
        {
            this.IsButtonClicked = true;
        }
#endif
    }
}

Windows 10 固有の API に触れる部分は #if NEXTFX_CORE のディレクティブで囲んで Unity Editor から見えないようにしています

また、Unity → UWP ライブラリは簡単に呼び出せますが、逆は Unity のメインスレッドを直接呼び出せずかんたんにはいきません

f:id:matatabi_ux:20161121210844p:plain

そのため、IsButtonClicked や AxisXDelta などの値型プロパティを下記のように Unity 側から参照することにしました

using BreakoutSample.UwpPlugin;
using UnityEngine;

/// <summary>
/// メインシーン
/// </summary>
public class MainScene : MonoBehaviour
{
    /// <summary>
    /// ラケット
    /// </summary>
    [SerializeField]
    private GameObject racket;

    /// <summary>
    /// ボール
    /// </summary>
    [SerializeField]
    private GameObject ball;

    /// <summary>
    /// プレイ中フラグ
    /// </summary>
    private bool isPlaying = false;

    /// <summary>
    /// 入力管理クラス
    /// </summary>
    public InputManager InputManager { get; private set; }

    /// <summary>
    /// 初期処理
    /// </summary>
    public void Awake()
    {
        this.InputManager = InputManager.Instance;
        UnityEngine.WSA.Application.InvokeOnUIThread(() =>
        {
            this.InputManager.Initialize();
        }, false);
        this.isPlaying = false;
    }

    /// <summary>
    /// 更新処理
    /// </summary>
    public void Update()
    {
        if (!this.isPlaying)
        {
            this.ball.transform.position = new Vector3(this.racket.transform.position.x, 0, this.racket.transform.position.z + 0.25f);

            // 開始前に Surfave Dial がクリックされたらボールに力を与えて動かし始める
            if (Input.GetButtonDown("Submit") || this.InputManager.IsButtonClicked)
            {
                this.ball.SendMessage("AddFource");
                this.isPlaying = true;

                // クリックフラグは降ろしておく
                this.InputManager.IsButtonClicked = false;
            }
        }
    }

    /// <summary>
    /// ミスイベントハンドラ
    /// </summary>
    public void Miss()
    {
        this.ball.GetComponent<Rigidbody>().velocity = Vector3.zero;
        this.InputManager.IsButtonClicked = false;
        this.isPlaying = false;
    }
}

あまりスマートなやり方ではないですが、取り急ぎやりたい場合ならこれで十分かもしれません

本格的にやるには Dispacher を用意して Unity のメインスレッドから呼び出す仕組みが必要になりそうですね

さてこれで動くかな?

f:id:matatabi_ux:20161121210857g:plain

ちゃんと動きましたね!