WindowsPhone8/8.1 Game Pad/Controller

WindowsPhone8/8.1(以降WP8/8.1)にはGame Pad/Controllerが有りませんでした。
これはモバイル向けGame Pad/Controllerとの接続で良く使用されるBluetoothですが、WP8/8.1にはBluetoothは搭載されているものの、対応profileとして「HID」が無いため外部入力機器が使用できませんでした。

ところが、最近検索していて見つけたのですが、Bensussen Deutsch & Associates, Inc(以下 BDA)社の「MOGA Game On Anywhere(以下 MOGA)」シリーズの一部が対応しているとの記事を見つけました。

MOGAにはiPhone用とAndroid用のゲームコントローラーがあるのですが、Android用でWP8/8.1に使用できる機能が付き、WP8用SDK(WinPRT)が公開されています。

MOGA Pro SDK – WindowsPhone8

自分は下記のコントローラーを購入して、SDKにあったサンプルでコントローラーが動作することを確認しました。


此方はUSB充電伝可能なリチウム電池内蔵型です。


価格は安いものの、単三電池二本が必要になる、リチウム電池やUSBコネクタはありません。

というわけで、WP8(WinPRT)用とはなっているものの、WP8.1WinRTベースで使用できるようにできないかと、色々試してみました。

“WindowsPhone8/8.1 Game Pad/Controller” の続きを読む

WinRTで画面遷移の履歴スタックを削除する

WindowsRuntime(WinRT)アプリで画面遷移を行っている際に、一気にTOPページに戻したいけど、戻した後に戻ってこられては困るという時があります。

そこで下記のコードを「OnNavigatedTo/NavigationHelper_LoadState」に書いておきます。

Frame.BackStack.Clear();

このAPIで履歴スタックを一括で消せます。

また、あるページに到達した時点で、直前のページには戻したくなく、入力などを行ってるページまで戻したい場合などに、戻す件数を把握してBackKeyが押された際の制御を行うのもアプリの構成としてはありだと思います。

下記のように「Frame.BackStack.RemoveAt」を利用して指定したスタックを削除することもできるので、使い分けるといいでしょう。

            // 画面遷移の履歴スタック数を取得する
            int stackCount = Frame.BackStackDepth;
            for (int i = 0; i < stackCount; i++)
            {
                // 指定した位置のBackコレクションを削除
                Frame.BackStack.RemoveAt(0);
            }

WindowsRuntime8.1/WindowsPhoneSilverLight8.1での日本語フォントが中華フォントになることの回避方法

さて、以前からWindowsPhoneアプリの開発で日本語フォントが中華フォントに置き換えられることがあり、これを回避する方法のコードがテンプレートにも取り込まれていたのですが、WindowsPhone8.1から消えていました。

現在私は「universal Apps」を優先的に行っているので、環境としては「WindowsRuntime 8.1」での開発となっています。

そこで、これまで通り下記コードを書いていたのですが、これが返す値がアプリに設定された言語を返すようになっています。
「en」のみで作成している場合、以前は実行環境の「ja」が返ってきたのですが、「en」が返ってくるようになっています。

                rootFrame = new Frame();

                rootFrame.Language = System.Globalization.CultureInfo.CurrentUICulture.Name;

Twitter上で話題が出た際にBluewaterSoftの山本氏が、その原因となった変更と新しく使用するとよいAPI「GlobalizationPreferences」を見つけていただけました。
そのAPIを利用するのが下記のコードです。
“WindowsRuntime8.1/WindowsPhoneSilverLight8.1での日本語フォントが中華フォントになることの回避方法” の続きを読む

dynabook TabをWindowsストアアプリのリモートデバッガで使用する

2014/05/29-30に行われた日本マイクロソフト社主催のイベント「de:code」で学習用機材として配布された「TOSHIBA dinabook Tab VT484/22K」を活用する一歩として、Windowsストアアプリのリモートデバッグを出来るようにしてみました。

使用した機材
・母艦PC:デスクトップPC
・タブレット:TOSHIBA dinabook Tab VT484/22K
・LAN:BUFFALO 10/100M USB2.0用 LANアダプタ LUA3-U2-ATX
・USB変換:ELECOM タブレットPC用USB変換アダプタ A(メス)-microB(オス) TB-MAEMCB010BK

リモートデバッグを行う場合は同一ネットワーク内にある必要があります。
その為デスクトップPCと接続するため、有線LANにするためUSB-LANアダプタを使用しました。

設定
・ルーターのDHCPでMACアドレスを利用してIPを固定しています。
固定していなくても実行可能とは思いますが、IPがコロコロ変わるのは面倒なので、固定しました。

必要なソフト
・母艦PC側はVisualStudio2013(Update3適応済み)を使用しています。
・dynabook Tabには「Remote Tools for Visual Studio 2013 Update1 x86」をインストールします

「Remote Tools for Visual Studio 2013」は「x86」「x64」「ARM」があります。
dynabook Tabでは「x86」版を使用しています。
dynabook Tab以外で行う場合は其々環境にあったバージョンをご使用ください。

インストール後、「Remote Debugger」を起動します。
起動すると各種設定に関する承諾が出ますがOKを押してください。
「Visual Studio リモート デバッグ モニター」が表示され、「サーバが開始され、接続を待っています」と表示されれば、「dynabook Tab」側は準備完了です。

Remote
母艦PC側のアプリソリューションを開き、アプリ動作先を「リモートコンピュータ」に変更して実行します。
実行の際、dynabook Tabがロックされていないように注意してください。
ロックされていると転送エラーになり、作業できません。

実行すると「接続先の選択」が表示されますので、きちんと接続準備ができていれば、リモートの「dynabook Tab」が選択可能です。(使用している開発言語で違いがあります)

接続が完了すると「開発者ライセンス認証」が促されます。
認証を行うとアプリが転送され、リモートデバッグが可能になります。

マルチタッチやカメラ、比較的非力なタブレット型でのデバッグが可能になり、開発のアイデアも浮かびやすいのではないかと思います。

Windowsデベロッパーセンターに「リモート マシンでの Visual Studio からの Windows ストア アプリの実行」という項目があり、ここに詳細がかかれています。

WindowsPhone8.1でカメラ操作(MediaCapture)

WindowsPhone8.1でカメラ操作(MediaCapture)と銘打ちましたが、普通にMediaCaptureを使用したサンプルなどは良くあります。
にもかかわらず、なぜに?となりますが、よくサンプルが出ているのは「WindowsRuntime」ベースのMediaCaptureです。
そこで「WindowsPhone SilverLight 8.1(以下WPSL8.1)」ベースでの解説など行うことにしました。

なぜWPSLで「MediaCapture」なのかですが、「PhotoCaptureDevice」API群がWindowsPhone8.1では非推奨(VS上では使用不可と出る)なためです。

さて、大きな違いですが、WPSL8.1ではUIElementの「CaptureElement」が存在しません。

WPSL8.1では今までの方法同様「VideoBrush」にアタッチする形をとります。
その為のAPIとして「Windows.Phone.Media.Capture.MediaCapturePreviewSink」があります。

			<Rectangle x:Name="PreviewRectangle" Width="450" Height="450" VerticalAlignment="Top" Margin="0,40,0,12" Tap="PreviewRectangle_Tap" >
				<Rectangle.Fill>
					<VideoBrush x:Name="PreviewBrush">
						<VideoBrush.RelativeTransform>
							<CompositeTransform x:Name="previewTransform" CenterX=".5" CenterY=".5" Rotation="90" />
						</VideoBrush.RelativeTransform>
					</VideoBrush>
				</Rectangle.Fill>
			</Rectangle>

定番の方法で「Rectangle」に「VideoBrush」を設定します。
「Rotation=”90″」は入力が90度回転した状態で送られてくるためです。

次にコードの部分です。
基本構成としては「初期化」「プレビューの開始」「プレビューの停止」「解放」となっています。
また、基本以外のところでは「フォーカス」「カメラIDの取得」を行っています。

    public class CameraDevice : IDisposable
    {
        // キャプチャー本体
        private MediaCapture _captureManager;
        // プレビュー用
        private MediaCapturePreviewSink _previewSink;
        // プレビュー判定
        private bool _bPreview = false;

        /// <summary>
        /// Camera Device Initialize
        /// </summary>
        /// <returns></returns>
        public async Task CameraDeviceInitialize()
        {
            try
            {
                // Get DeviceID rear camera
                var devId = await GetCameraId(Panel.Back);

                // 初期化
                _captureManager = new MediaCapture();

                await _captureManager.InitializeAsync(new MediaCaptureInitializationSettings
                {
                    StreamingCaptureMode = StreamingCaptureMode.Video,
                    PhotoCaptureSource = PhotoCaptureSource.Photo,
                    VideoDeviceId = devId.Id
                });

            }
            catch (Exception ex)
            {
                throw new Exception("CameraDeviceInitialize : " + ex.Message);
            }
        }

        /// <summary>
        /// Start PreView
        /// </summary>
        /// <param name="capturePreview">Preview area VideoBrush</param>
        /// <returns></returns>
        public async Task StartPreView(VideoBrush capturePreview)
        {
            try
            {
                if (_captureManager != null)
                {
                    // Preview Sink Initialize
                    _previewSink = new MediaCapturePreviewSink();

                    // Photo Primary
                    _captureManager.VideoDeviceController.PrimaryUse = CaptureUse.Photo;

                    // List of supported video preview formats to be used by the default preview format selector.
                    var supportedVideoFormats = new List<string> { "nv12", "rgb32" };

                    // Find the supported preview format
                    var availableMediaStreamProperties =
                        _captureManager.VideoDeviceController.GetAvailableMediaStreamProperties(
                            Windows.Media.Capture.MediaStreamType.VideoPreview)
                            .OfType<Windows.Media.MediaProperties.VideoEncodingProperties>()
                            .Where(p => p != null
                                        && !String.IsNullOrEmpty(p.Subtype)
                                        && supportedVideoFormats.Contains(p.Subtype.ToLower()))
                            .ToList();

                    var previewFormat = availableMediaStreamProperties.FirstOrDefault();

                    // Start Preview stream
                    await
                        _captureManager.VideoDeviceController.SetMediaStreamPropertiesAsync(
                            Windows.Media.Capture.MediaStreamType.VideoPreview, previewFormat);

                    await
                        _captureManager.StartPreviewToCustomSinkAsync(
                            new Windows.Media.MediaProperties.MediaEncodingProfile { Video = previewFormat }, _previewSink);

                    // Set the source of the VideoBrush used for your preview
                    Microsoft.Devices.CameraVideoBrushExtensions.SetSource(capturePreview, _previewSink);

                    _bPreview = true;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("StartPreView : " + ex.Message);
            }
        }

        /// <summary>
        /// Stop Preview
        /// </summary>
        /// <returns></returns>
        public async Task StopPreView()
        {
            try
            {
                if (_captureManager != null && _bPreview)
                {
                    await _captureManager.StopPreviewAsync();

                    _bPreview = false;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("StopPreView" + ex.Message);
            }
        }

        /// <summary>
        /// I explicitly free
        /// </summary>
        public async void Dispose()
        {
            if (_captureManager != null)
            {
                await StopPreView();
                _captureManager.Dispose();
            }
        }

        /// <summary>
        /// I will confirm the presence or absence rear camera, front camera
        /// </summary>
        /// <param name="desired"></param>
        /// <returns></returns>
        private static async Task<DeviceInformation> GetCameraId(Panel desired)
        {
            //var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);

            DeviceInformation deviceId = (await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture))
                .FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == desired);

            if (deviceId != null)
            {
                return deviceId;
            }
            else
            {
                throw new Exception(string.Format("Camera of type {0} doesn't exist.", desired));
            }
        }

        /// <summary>
        /// Execution of focus
        /// </summary>
        /// <returns></returns>
        public async Task SetFocusTask()
        {
            try
            {
                if (_captureManager == null)
                {
                    return;
                }

                foreach (var variable in _captureManager.VideoDeviceController.FocusControl.SupportedFocusModes)
                {
                    Debug.WriteLine(variable.ToString());
                }

                if (_captureManager.VideoDeviceController.FocusControl.Supported)
                {
                    _captureManager.VideoDeviceController.FocusControl.Configure(new FocusSettings { Mode = FocusMode.Auto, DisableDriverFallback = true });

                    await _captureManager.VideoDeviceController.FocusControl.FocusAsync();

                    Debug.WriteLine("focus supported");
                }
                else
                {
                    Debug.WriteLine("focus Not supported");
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("SetFocusTask" + ex.Message);
            }
        }
    }

使用の際は「CameraDeviceInitialize」行って「StartPreView(PreviewBrush)」すると開始されます。
「PreviewBrush」が表示されていない場合は例外が発生しますので注意してください。

「OnNavigatingFrom」でプレビューの停止と解放を行いましょう。
「OnNavigatedTo」に初期化を書いておくことで、復帰の際にも対処可能です。

フォーカスやその他の機能に関しては必ずサポートの有無を確認したうえで、操作を開始しないと未サポートの場合、例外が起きて停止します。

駆け足ではありますが、これでWPSL8.1環境下でもカメラが使用可能です。

ただ使用して思うのは、このAPIでは機能が制限される端末が存在しうることです。
「Lumia 1320」ではフォーカスが可能なのですが「Samsung ATIV S」では未サポートとなります。(旧APIでは使用可能)
これは、使用しているWindowsPhone8.1がDeveloperPreviewであることが原因の可能性もあります。

それでも、明らかに以前より機能が落ちるAPIを使用しなければいけないのは残念です。(「入力画素数」が指定できない等)
もっとも、自分がまだAPIを把握し切れていないために、使いきれていないということもあり得ます。

今後WPSL8.1はOSのバージョンアップなどに伴って、使用可能なAPIが制限されるのではないかと思いますので、アプリを作成する際は、ストアへの登録のしやすさなども考え見ると「universal Apps」を前提として「WindowsRuntime」ベースへ移行していったほうがよいと思います。

もっとも、WinRTでは使用できないAPIが多くあるのも事実ですので、そういったAPIを生かしたアプリを作るのであれば「WindowsPhone SilverLight 8.1」ベースで作りこむのがよいと思います。

WindowsPhoneでダイスと籤

アプリを作っていると、いろいろな判定やランダムによるチェックを行うことがあると思います。

その中でもよくある方法としては、ダイス(サイコロ)を振ってその数値による判定というのと、一定数の当たりから籤を引くという判定があります。

まずはダイス判定関数です。
基本はランダムを利用したもので、ランダム用のシードを作成する関数。
シード利用してランダム関数をコールして、結果の数値に+1して返すダイス関数。(Random.Next(Count)は0~指定数値-1なので)
複数ダイスを判定する関数で構成しています。

        /// <summary>
        /// 複数ダイスのロール
        /// 2D6 なら DiceRoll(2, 6)
        /// 1Dなら「.First<int>()」をつけて呼び出せばintで見れる
        /// </summary>
        /// <param name="numCount">サイコロの数</param>
        /// <param name="surfaceCount">サイコロの面数</param>
        /// <returns>各個の返り値</returns>
        public static int[] DiceRoll(int numCount, int surfaceCount)
        {
            var ret = new int[numCount];

            for (int i = 0; i < numCount; i++)
            {
                ret[i] = Dice(surfaceCount);
            }

            return ret;
        }

        /// <summary>
        /// サイコロ
        /// 数値は0~maxCount - 1なので、出てきた数値に + 1
        /// </summary>
        /// <param name="maxCount">最大数を渡す、D6なら6を</param>
        /// <returns>1~maxCount</returns>
        private static int Dice(int maxCount)
        {
            // シードを生成
            var rnd = new Random((int)GenerateRndNumber());

            var ret = rnd.Next(maxCount) + 1;

            return ret;
        }

        /// <summary>
        /// ランダム用のシード生成
        /// </summary>
        /// <returns></returns>
        private static UInt32 GenerateRndNumber()
        {
            // Generate a random number.
            var rnd = CryptographicBuffer.GenerateRandomNumber();

            return rnd;
        }

次に、籤による判定関数です。
この関数は、箱の中に当たりくじを入れて、その配列を返す関数にしています。
普通にダイス関数での判定でもいいではないかと思う方もいらっしゃると思いますが、これは当たり籤を判断させるための判定です。

どこかに入っている当たりくじを引くというのはダイスでは比率になり、ランダム性にかけます。

そこで、当たりくじの箱を作って、ダイスを振り、そこが当たりくじかどうかを判定することで、比率だけではない判定を行うために作製しました。

        /// <summary>
        /// 100の中に当たりくじをランダムで仕込む
        /// </summary>
        /// <param name="trueDataCount">あたりの数</param>
        /// <returns></returns>
        public static bool[] CreateHundredLottery(int trueDataCount)
        {
            if (trueDataCount > 100) return null;

            // falseで初期化
            bool[] bLotteryArray = Enumerable.Repeat<bool>(false, 100).ToArray();
            bool[] bAddArray = Enumerable.Repeat<bool>(true, trueDataCount).ToArray();

            bAddArray.CopyTo(bLotteryArray, 0);

            // 配列のランダム化
            var retArray = bLotteryArray.OrderBy(i => Guid.NewGuid()).ToArray();

            // 配列の中身を確認する
            // string stData = string.Join(Environment.NewLine, retArray);
            // Debug.WriteLine(stCsvData);

            return retArray;
        }

これらのコードはWindowsPhoneSilverlight8.1ベースですが、WinRTでもほぼそのまま使えるのではないかと思います。
書き換えるとしたらシード生成のところを書き換える形で、Windowsストアアプリでも使用は可能なはずです。

Autorun.infで32/64bitを分けて起動する

Autorun.infの[AutoRun]セクションの拡張で[AutoRun.Amd64]というのがあるようで、これで起動ファイルを分岐できるようです。

[AutoRun.Amd64]
open=setup.exe
icon=setup.exe,0

[AutoRun]
open=sperr32.exe
icon=sperr32.exe,0

記事などでWindows9は64bitオンリーになるんではないかというのが流れてきて、デスクトップの起動はどうしたものかというのがTwitterで流れていたのですが、「いきなり32bitアプリが全面禁止は考えにくい」というのが自分の考えですが、64bitアプリの対応は行うべきかなという感じです。

ただ、最近デスクトップアプリは全く触ってなかったので、この方法にたどり着くのに検索しまくったのはダメな感じだなー(;´Д`)

WindowsPhone8.1開発メモ

WindowsPhoneのデバッグ作業の際、実機とエミュレータで試しますが、機能によってはエミュレータではカメラなどはできません。
(WebCamと連携してカメラが使えるとかになってほしい気はしてます)

そこで実機で動作中なのか、エミュレータで動作中なのかでコード分岐をしたいと思う時があります。

その為の判断は「if(Environment.DeviceType == DeviceType.Emulator)」でエミュレータであることを確認できます。

Environmentは「Environment.NewLine」等、改行コードを追加などで使われることが多いと思いますが、現在のところWindowsPhoneSilverlightでは「DeviceType」のみの実装となります。

WindowsPhoneWindowsRuntimeでは、「Environment.NewLine」が使用可能ではありますが「Environment.DeviceType」がありません。

これは、WindowsPhoneSilverlightでは「Microsoft.Devices.Environment」でWindowsPhoneWindowsRuntimeでは「System.Environment」で判定されるためです。

WindowsPhoneSilverlight開発では明示的に「System.Environment」とすれば、「System.Environment.NewLine」なども使用可能ですが、WindowsPhoneWindowsRuntime開発の場合はWindowsPhoneSilverlightを呼び出せないため、「Microsoft.Devices.Environment.DeviceType」は使用できません。

こんな所にも違いがあるので注意が必要です。

WindowsPhone8.1でのカメラAPIが変更されています

WindowsPhone8では「Windows.Phone.Media.Capture」を使用していましたが、このAPIがWindowsPhone8.1では「APIs is provided for backwards compatibility only.」と「後方互換」のためだけに提供されるAPIとなっています。

「Windows.Phone.Media.Capture」に代わってWindowsPhone8.1では「Windows.Media.Capture」というAPI群が提供されるようになりました。

「Windows.Media.Capture」は「Windows.Phone.Media.Capture」の代わりだけあって、細かな設定ができます。
細かな設定はいらないという場合は「CameraCaptureUI」というAPIが提供されています。

「CameraCaptureUI」は手軽なビデオ撮影機能を実現したい場合には便利ではないかと思います。
※CameraCaptureUI APIはWindowsストアアプリのみのAPIでした

WindowsRuntime(WindowsPhone SilverLight 8.1でも動きます)ですが、Back/Frontのチェック方法を発見したので掲載しておきます。

private static async Task<DeviceInformation> GetCameraID(Windows.Devices.Enumeration.Panel desired)
{
    DeviceInformation deviceID = (await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture))
        .FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == desired);

    if (deviceID != null) return deviceID;
    else throw new Exception(string.Format("Camera of type {0} doesn't exist.", desired));
}

片手用ControlPadなUserControlを作ってみた

環境はWindowsPhoneSilverLight8.1ですが、基本的にはC#+XAMLなのであまり変更せずにWinRTにも移植可能ではないかと思います。

まずは外観から
名称未設定 1

XAMLで「ChangePropertyAction」を利用して押した時の反応などを定義してあります

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:es="clr-namespace:Microsoft.Expression.Shapes;assembly=Microsoft.Expression.Drawing" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:ec="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions" xmlns:eim="clr-namespace:Microsoft.Expression.Interactivity.Media;assembly=Microsoft.Expression.Interactions" x:Class="CryEarthWPLib.Controller.View.SingleControllerPad"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="150" d:DesignWidth="150" Opacity="0.7">
    
	<Grid x:Name="LayoutRoot" Width="150" Height="150">
		<VisualStateManager.VisualStateGroups>
			<VisualStateGroup x:Name="VisualStateGroup"/>
		</VisualStateManager.VisualStateGroups>
		<Path x:Name="TopPath" UseLayoutRounding="False" VerticalAlignment="Top" Data="M84.8528,0 L98.9949,14.1422 L97.7933,15.3155 C85.2319,27.2917 68.2234,34.6447 49.4975,34.6447 C30.1675,34.6447 12.6675,26.8097 0,14.1421 L14.1421,0 C23.1904,9.04823 35.6904,14.6447 49.4975,14.6447 C63.3046,14.6447 75.8046,9.04823 84.8528,4E-06 z" Height="34.645" Margin="25.503,35.355,25.503,0" RenderTransformOrigin="0.5,0.061905" Stretch="Fill" Stroke="#667088B2" MouseEnter="RingPath_MouseEnter" MouseLeftButtonDown="RingPath_MouseLeftButtonDown" MouseLeftButtonUp="RingPath_MouseLeftButtonUp" MouseLeave="RingPath_MouseLeave">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="MouseLeftButtonDown">
					<ec:ChangePropertyAction TargetName="TopPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=TopPath_State}"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseEnter">
					<ec:ChangePropertyAction TargetName="TopPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=TopPath_State}"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeftButtonUp">
					<ec:ChangePropertyAction TargetName="TopPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=TopPath_State}">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeave">
					<ec:ChangePropertyAction TargetName="TopPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=TopPath_State}">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
			</i:Interaction.Triggers>
			<Path.Fill>
				<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
					<GradientStop Color="#FF71A0E4" Offset="1"/>
					<GradientStop Color="#FF17335B" Offset="0.246"/>
				</LinearGradientBrush>
			</Path.Fill>
			<Path.RenderTransform>
				<CompositeTransform ScaleY="-1"/>
			</Path.RenderTransform>
		</Path>
		<Path x:Name="BottomPath" UseLayoutRounding="False" VerticalAlignment="Bottom" Data="M14.1421,0 C23.1904,9.04823 35.6904,14.6447 49.4975,14.6447 C63.3046,14.6447 75.8046,9.04823 84.8528,0 L98.995,14.1422 C86.3275,26.8097 68.8275,34.6447 49.4975,34.6447 C30.1675,34.6447 12.6675,26.8097 0,14.1422 z" Height="34.645" Margin="25.503,0,25.503,5" RenderTransformOrigin="0.5,0.061905" Stretch="Fill" Stroke="#667088B2" MouseLeftButtonDown="RingPath_MouseLeftButtonDown" MouseLeftButtonUp="RingPath_MouseLeftButtonUp" MouseLeave="RingPath_MouseLeave" MouseEnter="RingPath_MouseEnter">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="MouseLeftButtonDown">
					<ec:ChangePropertyAction TargetName="BottomPath_State" PropertyName="Visibility"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseEnter">
					<ec:ChangePropertyAction TargetName="BottomPath_State" PropertyName="Visibility"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeftButtonUp">
					<ec:ChangePropertyAction TargetName="BottomPath_State" PropertyName="Visibility">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeave">
					<ec:ChangePropertyAction TargetName="BottomPath_State" PropertyName="Visibility">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
			</i:Interaction.Triggers>
			<Path.Fill>
				<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
					<GradientStop Color="#FF71A0E4" Offset="1"/>
					<GradientStop Color="#FF17335B" Offset="0.246"/>
				</LinearGradientBrush>
			</Path.Fill>
			<Path.RenderTransform>
				<CompositeTransform/>
			</Path.RenderTransform>
		</Path>
		<Path x:Name="RightPath" Data="M14.1422,0 C26.8097,12.6675 34.6447,30.1675 34.6447,49.4975 C34.6447,68.2234 27.2917,85.2319 15.3155,97.7933 L14.1422,98.995 L0,84.8528 C9.04823,75.8046 14.6447,63.3046 14.6447,49.4975 C14.6447,36.5533 9.72592,24.758 1.65557,15.8786 L0,14.1422 z" Margin="110.355,25.503,5,25.503" Stretch="Fill" UseLayoutRounding="False" Stroke="#667088B2" MouseLeftButtonDown="RingPath_MouseLeftButtonDown" MouseLeftButtonUp="RingPath_MouseLeftButtonUp" MouseLeave="RingPath_MouseLeave" MouseEnter="RingPath_MouseEnter">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="MouseLeftButtonDown">
					<ec:ChangePropertyAction TargetName="RightPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=RightPath_State}"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseEnter">
					<ec:ChangePropertyAction TargetName="RightPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=RightPath_State}"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeftButtonUp">
					<ec:ChangePropertyAction TargetName="RightPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=RightPath_State}">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeave">
					<ec:ChangePropertyAction TargetName="RightPath_State" PropertyName="Visibility" TargetObject="{Binding ElementName=RightPath_State}">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
			</i:Interaction.Triggers>
			<Path.Fill>
				<LinearGradientBrush EndPoint="0.5,1" MappingMode="RelativeToBoundingBox" StartPoint="0.5,0">
					<LinearGradientBrush.RelativeTransform>
						<CompositeTransform CenterY="0.5" CenterX="0.5" Rotation="-90"/>
					</LinearGradientBrush.RelativeTransform>
					<GradientStop Color="#FF71A0E4" Offset="1"/>
					<GradientStop Color="#FF17335B" Offset="0.246"/>
				</LinearGradientBrush>
			</Path.Fill>
		</Path>
		<Path x:Name="LeftPath" UseLayoutRounding="False" HorizontalAlignment="Left" VerticalAlignment="Center" Data="M84.8528,0 L98.9949,14.1422 L97.7933,15.3155 C85.2319,27.2917 68.2234,34.6447 49.4975,34.6447 C30.1675,34.6447 12.6675,26.8097 0,14.1421 L14.1421,0 C23.1904,9.04823 35.6904,14.6447 49.4975,14.6447 C63.3046,14.6447 75.8046,9.04823 84.8528,4E-06 z" Height="34.645" Margin="-11.997,72.855,0,42.5" RenderTransformOrigin="0.5,0.061905" Stretch="Fill" Width="98.994" Stroke="#667088B2" MouseLeftButtonDown="RingPath_MouseLeftButtonDown" MouseLeftButtonUp="RingPath_MouseLeftButtonUp" MouseLeave="RingPath_MouseLeave" MouseEnter="RingPath_MouseEnter">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="MouseLeftButtonDown">
					<ec:ChangePropertyAction TargetName="LeftPath_State" PropertyName="Visibility"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseEnter">
					<ec:ChangePropertyAction TargetName="LeftPath_State" PropertyName="Visibility"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeftButtonUp">
					<ec:ChangePropertyAction TargetName="LeftPath_State" PropertyName="Visibility">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeave">
					<ec:ChangePropertyAction TargetName="LeftPath_State" PropertyName="Visibility">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
			</i:Interaction.Triggers>
			<Path.Fill>
				<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
					<GradientStop Color="#FF71A0E4" Offset="1"/>
					<GradientStop Color="#FF17335B" Offset="0.246"/>
				</LinearGradientBrush>
			</Path.Fill>
			<Path.RenderTransform>
				<CompositeTransform Rotation="90"/>
			</Path.RenderTransform>
		</Path>
		<es:Arc x:Name="TapArc" ArcThickness="1" ArcThicknessUnit="Percent" EndAngle="360" Height="40" Stretch="None" UseLayoutRounding="False" Width="40" HorizontalAlignment="Center" VerticalAlignment="Center" Tap="TapArc_Tap" Stroke="#33999DCB" d:IsLocked="True">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="MouseLeftButtonDown">
					<ec:ChangePropertyAction TargetName="TapArc_State" PropertyName="Visibility"/>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeftButtonUp">
					<ec:ChangePropertyAction TargetName="TapArc_State" PropertyName="Visibility">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
				<i:EventTrigger EventName="MouseLeave">
					<ec:ChangePropertyAction TargetName="TapArc_State" PropertyName="Visibility">
						<ec:ChangePropertyAction.Value>
							<Visibility>Collapsed</Visibility>
						</ec:ChangePropertyAction.Value>
					</ec:ChangePropertyAction>
				</i:EventTrigger>
			</i:Interaction.Triggers>
			<es:Arc.Fill>
				<RadialGradientBrush>
					<GradientStop Color="#FF2E4A93" Offset="0.884"/>
					<GradientStop Color="Black"/>
					<GradientStop Color="#FF4D77E8" Offset="0.53"/>
					<GradientStop Offset="1" Color="Transparent"/>
					<GradientStop Color="#FF162D66" Offset="0.944"/>
					<GradientStop Color="#FF2E4A93" Offset="0.19"/>
				</RadialGradientBrush>
			</es:Arc.Fill>
		</es:Arc>
		<es:RegularPolygon x:Name="TopTriangle" HorizontalAlignment="Center" Height="15" InnerRadius="1" PointCount="3" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Top" Width="15" Fill="#66FFFFFF" Margin="67.5,7,67.5,0" IsHitTestVisible="False" d:IsLocked="True"/>
		<es:RegularPolygon x:Name="BottomTriangle" HorizontalAlignment="Center" Height="15" InnerRadius="1" PointCount="3" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Bottom" Width="15" Fill="#66FFFFFF" Margin="67.5,0,67.5,7" RenderTransformOrigin="0.5,0.5" IsHitTestVisible="False" d:IsLocked="True">
			<es:RegularPolygon.RenderTransform>
				<CompositeTransform ScaleY="-1"/>
			</es:RegularPolygon.RenderTransform>
		</es:RegularPolygon>
		<es:RegularPolygon x:Name="LeftTriangle" HorizontalAlignment="Left" Height="15" InnerRadius="1" PointCount="3" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="15" Fill="#66FFFFFF" RenderTransformOrigin="0.5,0.5" Margin="7,0,0,0" IsHitTestVisible="False" d:IsLocked="True">
			<es:RegularPolygon.RenderTransform>
				<CompositeTransform ScaleY="-1" Rotation="90"/>
			</es:RegularPolygon.RenderTransform>
		</es:RegularPolygon>
		<es:RegularPolygon x:Name="RightTriangle" HorizontalAlignment="Right" Height="15" InnerRadius="1" PointCount="3" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="15" Fill="#66FFFFFF" RenderTransformOrigin="0.5,0.5" Margin="0,0,7,0" IsHitTestVisible="False" d:IsLocked="True">
			<es:RegularPolygon.RenderTransform>
				<CompositeTransform Rotation="-90" ScaleY="-1" ScaleX="-1"/>
			</es:RegularPolygon.RenderTransform>
		</es:RegularPolygon>
        <Path x:Name="TopPath_State" UseLayoutRounding="False" VerticalAlignment="Top" Data="M84.8528,0 L98.9949,14.1422 L97.7933,15.3155 C85.2319,27.2917 68.2234,34.6447 49.4975,34.6447 C30.1675,34.6447 12.6675,26.8097 0,14.1421 L14.1421,0 C23.1904,9.04823 35.6904,14.6447 49.4975,14.6447 C63.3046,14.6447 75.8046,9.04823 84.8528,4E-06 z" Height="34.645" Margin="25.503,35.355,25.503,0" RenderTransformOrigin="0.5,0.061905" Stretch="Fill" Stroke="#667088B2" Fill="#99FFFFFF" Visibility="Collapsed" IsHitTestVisible="False" d:IsLocked="True">
			<Path.RenderTransform>
				<CompositeTransform ScaleY="-1"/>
			</Path.RenderTransform>
		</Path>
		<Path x:Name="RightPath_State" Data="M14.1422,0 C26.8097,12.6675 34.6447,30.1675 34.6447,49.4975 C34.6447,68.2234 27.2917,85.2319 15.3155,97.7933 L14.1422,98.995 L0,84.8528 C9.04823,75.8046 14.6447,63.3046 14.6447,49.4975 C14.6447,36.5533 9.72592,24.758 1.65557,15.8786 L0,14.1422 z" Margin="110.355,25.503,5,25.503" Stretch="Fill" UseLayoutRounding="False" Stroke="#667088B2"  Fill="#99FFFFFF" Visibility="Collapsed" IsHitTestVisible="False" d:IsLocked="True"/>
		<Path x:Name="BottomPath_State" UseLayoutRounding="False" VerticalAlignment="Bottom" Data="M14.1421,0 C23.1904,9.04823 35.6904,14.6447 49.4975,14.6447 C63.3046,14.6447 75.8046,9.04823 84.8528,0 L98.995,14.1422 C86.3275,26.8097 68.8275,34.6447 49.4975,34.6447 C30.1675,34.6447 12.6675,26.8097 0,14.1422 z" Height="34.645" Margin="25.503,0,25.503,5" RenderTransformOrigin="0.5,0.061905" Stretch="Fill" Stroke="#667088B2" Fill="#99FFFFFF" Visibility="Collapsed" IsHitTestVisible="False" d:IsLocked="True">
			<Path.RenderTransform>
				<CompositeTransform/>
			</Path.RenderTransform>
		</Path>
		<Path x:Name="LeftPath_State" UseLayoutRounding="False" HorizontalAlignment="Left" VerticalAlignment="Center" Data="M84.8528,0 L98.9949,14.1422 L97.7933,15.3155 C85.2319,27.2917 68.2234,34.6447 49.4975,34.6447 C30.1675,34.6447 12.6675,26.8097 0,14.1421 L14.1421,0 C23.1904,9.04823 35.6904,14.6447 49.4975,14.6447 C63.3046,14.6447 75.8046,9.04823 84.8528,4E-06 z" Height="34.645" Margin="-11.997,72.855,0,42.5" RenderTransformOrigin="0.5,0.061905" Stretch="Fill" Width="98.994" Stroke="#667088B2" Fill="#99FFFFFF" Visibility="Collapsed" IsHitTestVisible="False" d:IsLocked="True">
			<Path.RenderTransform>
				<CompositeTransform Rotation="90"/>
			</Path.RenderTransform>
		</Path>
		<es:Arc x:Name="TapArc_State" ArcThickness="1" ArcThicknessUnit="Percent" EndAngle="360" Height="40" Stretch="None" UseLayoutRounding="False" Width="40" HorizontalAlignment="Center" VerticalAlignment="Center" Tap="TapArc_Tap" Stroke="#33999DCB" Margin="55" Fill="#99FFFFFF" Visibility="Collapsed" IsHitTestVisible="False" d:IsLocked="True"/>

	</Grid>
</UserControl>

続いてコード部分

    public partial class SingleControllerPad : UserControl
    {
        /// <summary>
        /// Key判断イベントHANDLE
        /// </summary>
        /// <param name="sender">this</param>
        /// <param name="keyId">0=Enter,1=TOP,2=Bottom,3=Left,4=Right,5=non</param>
        public delegate void TapKeyControlEventHandler(object sender, int keyId);

        public event TapKeyControlEventHandler TapKeyControl;

        /// <summary>
        /// Keyイベント
        /// </summary>
        /// <param name="keyId">0=Enter,1=TOP,2=Bottom,3=Left,4=Right,5=non</param>
        protected virtual void OnTopKeyControl(int keyId)
        {
            if (TapKeyControl != null)
            {
                TapKeyControl(this, keyId);
            }
        }

        public SingleControllerPad()
        {
            InitializeComponent();
        }

		/// <summary>
		/// 確定Button
		/// 所謂Aボタンを押したとか
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
        private void TapArc_Tap(object sender, System.Windows.Input.GestureEventArgs e)
		{
            // 確定なので0で固定
            // イベントをチェックしているところでイベント発生後、イベントをリセットする
		    OnTopKeyControl(0);
		}

        /// <summary>
        /// 領域に入ってきたことを検知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RingPath_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            var ul = e.OriginalSource as UIElement;
            if (ul != null)
            {
                var name = ((System.Windows.Shapes.Path)ul).Name;
                CallRingPathEvent(name);
            }
        }

        /// <summary>
        /// 領域から出たことを検知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RingPath_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            // 何も押していないことを通知
            CallRingPathEvent("");
        }

        /// <summary>
        /// Buttonを選択
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RingPath_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            var ul = e.OriginalSource as UIElement;
            if (ul != null)
            {
                var name = ((System.Windows.Shapes.Path)ul).Name;
                CallRingPathEvent(name);
            }
        }

        /// <summary>
        /// Buttonを離す
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RingPath_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            var ul = e.OriginalSource as UIElement;
            if (ul != null)
            {
                var name = ((System.Windows.Shapes.Path)ul).Name;
                CallRingPathEvent(name);
            }
        }

        /// <summary>
        /// 外部にどのボタンを押しているかを知らせる
        /// </summary>
        /// <param name="pathName"></param>
        void CallRingPathEvent(string pathName)
        {
            switch (pathName)
            {
                case "TopPath":
                    OnTopKeyControl(1);
                    break;

                case "BottomPath":
                    OnTopKeyControl(2);
                    break;

                case "LeftPath":
                    OnTopKeyControl(3);
                    break;

                case "RightPath":
                    OnTopKeyControl(4);
                    break;

                default:
                    OnTopKeyControl(5);
                    break;
            }
        }

    }

このコントロール自体はあくまでもキー情報を通知するだけの機能にしています。
使うときは、イベントでKeyIDを確保して、ゲームループでそのIDを判断して各操作を行うという形にしています。

{
            var timer = ThreadPoolTimer.CreatePeriodicTimer(new TimerElapsedHandler(GameLoopTimerCallback), TimeSpan.FromMilliseconds(35));
            // メインUIのDispatcherを取得
            dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
}

        private async void GameLoopTimerCallback(ThreadPoolTimer timer)
        {
            if (dispatcher != null)
                await dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                    () =>
                    {
                            // 各種処理関数を呼び出す
                    });
        }

大分手抜き&片手コントロール用なのでいろいろ不足していますし、キャンセルボタンの扱いという問題もあります。
まあ、入り口ということで、こんなのもありかなと思います。