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ベースで使用できるようにできないかと、色々試してみました。

1:プロジェクトのローカルにSDKの「Library」フォルダをコピーしておく
2:「Library」フォルダ内の「x86」フォルダの名前を「Win32」に変更しておく

自分はC#を使うのを基本にしているので、VS2013で新規プロジェクトの作成で、C#->ストアアプリ->universalAppテンプレートを選択しました。

3:プロジェクトにC++->ストアアプリ->WindowsPhoneアプリ->WindowsRuntimeコンポーネント(Windows Phone)でMOGA用ライブラリプロジェクトを作成します。
4:画像のように「Moga.Windows.Phone.winmd」を参照で追加します。
参照画像
5:保存してVisualStudioをいったん閉じます。
6:「MOGA用ライブラリプロジェクト.vcxproj」をテキストで開きます。

  <ItemGroup>
    <Reference Include="Moga.Windows.Phone">
      <HintPath>..\Library\ARM\Moga.Windows.Phone.winmd</HintPath>
      <IsWinMDFile>true</IsWinMDFile>
    </Reference>
  </ItemGroup>

上記を下記のように書き換えます。

  <ItemGroup>
    <Reference Include="Moga.Windows.Phone">
      <HintPath>..\Library\$(Platform)\Moga.Windows.Phone.winmd</HintPath>
      <IsWinMDFile>true</IsWinMDFile>
    </Reference>
  </ItemGroup>

7:ソリューションを開くことで準備が完了します。

#pragma once

#include <pch.h>

namespace CryearthLib_MogaController
{
	/*
		現在下記が発生するためWP8.1WinRTではコールできない
	    Cn::XH
		Platform::COMException ^ HRESULT:0x80070103
	*/

	public enum class MogaStateEnum
	{
		Connected,
		Connecting,
		Disconnected,
		PowerLowTrue,
		PowerLowFalse,
	};

	public delegate void MogaControllerEventHandler();
	public delegate void MogaStateEventHandler(MogaStateEnum);

	public ref class MogaController sealed
	{
	public:

		event MogaControllerEventHandler^ OnMogaConnected;
		event MogaControllerEventHandler^ OnMogaDisConnected;

		event MogaStateEventHandler^ OnMogaState;

		// コンストラクタ
		MogaController();

		void MogaControllerSuspending();
		void MogaControllerResuming();

		//イベントを登録
		void MogaControllerEventInitialize();

		/*
		static Moga::Windows::Phone::ControllerManager^ getController() {
		return controller;
		};*/

		// キーCodeProperty
		property bool KeycodeStart{bool get(){ return _keycodestart; }; private : void set(bool value){ if (_keycodestart != value){ _keycodestart = value; } };	}
		property bool KeycodeSelect{bool get(){ return _keycodeselect; }; private: void set(bool value){ if (_keycodeselect != value){ _keycodeselect = value; } }; }

		property bool KeyCodeX{bool get(){ return _keycodex; }; private: void set(bool value){ if (_keycodex != value){ _keycodex = value; } };	}
		property bool KeyCodeY{bool get(){ return _keycodey; }; private: void set(bool value){ if (_keycodey != value){ _keycodey = value; } }; }
		property bool KeyCodeA{bool get(){ return _keycodea; }; private: void set(bool value){ if (_keycodea != value){ _keycodea = value; } }; }
		property bool KeyCodeB{bool get(){ return _keycodeb; }; private: void set(bool value){ if (_keycodeb != value){ _keycodeb = value; } }; }
		property bool KeyCodeL1{bool get(){ return _keycodel1; }; private: void set(bool value){ if (_keycodel1 != value){ _keycodel1 = value; } }; }
		property bool KeyCodeL2{bool get(){ return _keycodel2; }; private: void set(bool value){ if (_keycodel2 != value){ _keycodel2 = value; } }; }
		property bool KeyCodeR1{bool get(){ return _keycoder1; }; private: void set(bool value){ if (_keycoder1 != value){ _keycoder1 = value; } }; }
		property bool KeyCodeR2{bool get(){ return _keycoder2; }; private: void set(bool value){ if (_keycoder2 != value){ _keycoder2 = value; } }; }
		property bool KeyCodeUp{bool get(){ return _keycodeup; }; private: void set(bool value){ if (_keycodeup != value){ _keycodeup = value; } }; }
		property bool KeyCodeDown{bool get(){ return _keycodedown; }; private: void set(bool value){ if (_keycodedown != value){ _keycodedown = value; } }; }
		property bool KeyCodeLeft{bool get(){ return _keycodeleft; }; private: void set(bool value){ if (_keycodeleft != value){ _keycodeleft = value; } }; }
		property bool KeyCodeRight{bool get(){ return _keycoderight; }; private: void set(bool value){ if (_keycoderight != value){ _keycoderight = value; } }; }

		property float Norm_rot_X{float get(){ return _norm_rot_x; }; private: void set(float value){ if (_norm_rot_x != value){ _norm_rot_x = value; } };	}
		property float Norm_rot_Y{float get(){ return _norm_rot_y; }; private: void set(float value){ if (_norm_rot_y != value){ _norm_rot_y = value; } };	}
		property float Norm_rot_X2{float get(){ return _norm_rot_x2; }; private: void set(float value){ if (_norm_rot_x2 != value){ _norm_rot_x2 = value; } };	}
		property float Norm_rot_Y2{float get(){ return _norm_rot_y2; }; private: void set(float value){ if (_norm_rot_y2 != value){ _norm_rot_y2 = value; } };	}
		property float Red_ratio{float get(){ return _red_ratio; }; private: void set(float value){ if (_red_ratio != value){ _red_ratio = value; } };	}
		property float Blue_ratio{float get(){ return _blue_ratio; }; private: void set(float value){ if (_blue_ratio != value){ _blue_ratio = value; } };	}

	private:

		static Moga::Windows::Phone::ControllerManager^ controller;

		// 変数の初期化
		void MogaControllerVariableInitialize();

		void OnConnected();
		void OnDisconnected();
		void OnKeyChanged(Moga::Windows::Phone::KeyEvent ^__param0);
		void OnAxisChanged(Moga::Windows::Phone::MotionEvent ^__param0);
		void OnStateChanged(Moga::Windows::Phone::StateEvent ^__param0);

		bool _keycodestart;
		bool _keycodeselect;

		bool _keycodex;
		bool _keycodey;
		bool _keycodea;
		bool _keycodeb;
		bool _keycodel1;
		bool _keycodel2;
		bool _keycoder1;
		bool _keycoder2;
		bool _keycodeup;
		bool _keycodedown;
		bool _keycodeleft;
		bool _keycoderight;

		float _norm_rot_x;
		float _norm_rot_y;
		float _norm_rot_x2;
		float _norm_rot_y2;
		float _red_ratio;
		float _blue_ratio;
	};
}

#include "pch.h"
#include "MogaController.h"

using namespace Moga::Windows::Phone;

namespace CryearthLib_MogaController
{
	Moga::Windows::Phone::ControllerManager^ MogaController::controller;

	//コンストラクタ
	MogaController::MogaController()
	{
		try
		{
			// 変数を初期化
			MogaControllerVariableInitialize();
			OutputDebugString(L"System: MogaControllerVariableInitialize\r\n");

			// Controllerを初期化
			controller = ref new ControllerManager();
			OutputDebugString(L"System: ref new ControllerManager();\r\n");

			// コネクションを開始
			controller->Connect();
			OutputDebugString(L"System: controller->Connect();\r\n");
		}
		catch (Platform::Exception^ anyEx)
		{
			OutputDebugString(L"ERROR: Controller failed\r\n");
			throw ref new Platform::Exception(anyEx->HResult, anyEx->Message);
		}

	}

	// 変数初期化
	void MogaController::MogaControllerVariableInitialize()
	{
		_keycodex = false;
		_keycodey = false;
		_keycodea = false;
		_keycodeb = false;
		_keycodel1 = false;
		_keycodel2 = false;
		_keycoder1 = false;
		_keycoder2 = false;
		_keycodeup = false;
		_keycodedown = false;
		_keycodeleft = false;
		_keycoderight = false;

		_norm_rot_x = 0.0f;
		_norm_rot_y = 0.0f;
		_norm_rot_x2 = 0.0f;
		_norm_rot_y2 = 0.0f;
		_red_ratio = 0.0f;
		_blue_ratio = 0.0f;
	}

	// イベント宣言初期化
	void MogaController::MogaControllerEventInitialize()
	{
		controller->Connected += ref new Moga::Windows::Phone::ConnectHandler(this, &CryearthLib_MogaController::MogaController::OnConnected);
		controller->Disconnected += ref new Moga::Windows::Phone::DisconnectHandler(this, &CryearthLib_MogaController::MogaController::OnDisconnected);

		controller->KeyChanged += ref new Moga::Windows::Phone::KeyEventHandler(this, &CryearthLib_MogaController::MogaController::OnKeyChanged);
		controller->AxisChanged += ref new Moga::Windows::Phone::MotionEventHandler(this, &CryearthLib_MogaController::MogaController::OnAxisChanged);
		controller->StateChanged += ref new Moga::Windows::Phone::StateEventHandler(this, &CryearthLib_MogaController::MogaController::OnStateChanged);
	}

	// Suspend時にコールすること
	void MogaController::MogaControllerSuspending()
	{
		try
		{
			if (controller != nullptr)
			{
				controller->Suspending();
			}
		}
		catch (Platform::Exception^ anyEx)
		{
			OutputDebugString(L"ERROR: Controller failed to suspending\r\n");
			throw ref new Platform::Exception(anyEx->HResult, anyEx->Message);
		}
	}

	// レジューム時にコール
	void MogaController::MogaControllerResuming()
	{
		try
		{
			if (controller != nullptr)
			{
				controller->Resuming();
			}
		}
		catch (Platform::Exception^ anyEx)
		{
			OutputDebugString(L"ERROR: Controller failed to resuming\r\n");
			throw ref new Platform::Exception(anyEx->HResult, anyEx->Message);
		}
	}

	// 接続完了
	void MogaController::OnConnected()
	{
		OutputDebugString(L"System: Controller OnConnected\r\n");
		OnMogaConnected();
		//throw ref new Platform::NotImplementedException();
	}

	// 接続が解除
	void MogaController::OnDisconnected()
	{
		OutputDebugString(L"System: Controller OnDisconnected\r\n");
		OnMogaDisConnected();
		//throw ref new Platform::NotImplementedException();
	}

	// キー変更イベント
	void MogaController::OnKeyChanged(Moga::Windows::Phone::KeyEvent ^__param0)
	{
		KeyCode kc = __param0->KeyCode;
		ControllerAction  ac = __param0->Action;

		bool acb = (ac == ControllerAction::Pressed);

		switch (kc)
		{
		case KeyCode::Start:			KeycodeStart = acb; break;
		case KeyCode::Select:			KeycodeSelect = acb; break;

		case KeyCode::X:               KeyCodeX = acb; break;
		case KeyCode::B:               KeyCodeB = acb; break;
		case KeyCode::A:               KeyCodeA = acb; break;
		case KeyCode::Y:               KeyCodeY = acb; break;
		case KeyCode::L1:              KeyCodeL1 = acb; break;
		case KeyCode::R1:              KeyCodeR1 = acb; break;
		case KeyCode::L2:              KeyCodeL2 = acb; break;
		case KeyCode::R2:              KeyCodeR2 = acb; break;

		case KeyCode::DirectionUp:     KeyCodeUp = acb; break;
		case KeyCode::DirectionDown:   KeyCodeDown = acb; break;
		case KeyCode::DirectionLeft:   KeyCodeLeft = acb; break;
		case KeyCode::DirectionRight:  KeyCodeRight = acb; break;
		}
	}

	// アナログControllerイベント
	void MogaController::OnAxisChanged(Moga::Windows::Phone::MotionEvent ^__param0)
	{
		Axis ax = __param0->Axis;
		float val = __param0->AxisValue;

		switch (ax)
		{
		case Axis::X:             Norm_rot_X = val; break;
		case Axis::Y:             Norm_rot_Y = val; break;
		case Axis::Z:             Norm_rot_X2 = val; break;
		case Axis::RZ:            Norm_rot_Y2 = val; break;

		case Axis::LeftTrigger:   Red_ratio = val; break;
		case Axis::RightTrigger:  Blue_ratio = val; break;
		}
	}

	// Status変更イベント
	void MogaController::OnStateChanged(Moga::Windows::Phone::StateEvent ^__param0)
	{
		OutputDebugString(L"System: Controller OnStateChanged\r\n");

		auto state = __param0->StateKey;
		auto value = __param0->StateValue;

		switch (state)
		{
		case ControllerState::Connection:
			if (value == ControllerResult::Connected)
			{
				OutputDebugString(L"System : ControllerState::Connection to ControllerResult::Connected\r\n");
				OnMogaState(MogaStateEnum::Connected);
			}
			else if (value == ControllerResult::Connecting)
			{
				OutputDebugString(L"System : ControllerState::Connection to ControllerResult::Connecting\r\n");
				OnMogaState(MogaStateEnum::Connecting);
			}
			else if (value == ControllerResult::Disconnected)
			{
				OutputDebugString(L"System : ControllerState::Connection to ControllerResult::Disconnected\r\n");
				OnMogaState(MogaStateEnum::Disconnected);
			}
			else
			{
				OutputDebugString(L"System : ControllerState::Connection to ControllerResult::?????\r\n");
			}
			break;

		case ControllerState::PowerLow:
			if (value == ControllerResult::True)
			{
				OnMogaState(MogaStateEnum::PowerLowTrue);
			}
			else
			{
				OnMogaState(MogaStateEnum::PowerLowFalse);
			}
			break;
		case ControllerState::SelectedVersion:		break;
		case ControllerState::SupportedVersion:		break;
		}
		//throw ref new Platform::NotImplementedException();
	}
}

7:C#側で参照して使用する「だけ」なのですが、現在正常には使用できません。
いろいろ追いかけたのですが、SDK内部で例外を発生させているため、やはりWP8.1WinRTにはSDKが対応していないことによる現象ではないかと思います。
今しばらくはいろいろ試したいとは思っていますが、さすがに厳しいかなとは思っています。

というわけで、WP8/8.1で使用できるGame Pad/Controllerは存在して、WP8向けの対応ゲームも存在しています。
さらに、最近採用事例が増えているUnityですが、UnityのAssetとしてMOGAのAndroid&WindowsPhone8Assetがありますので、差別化を図るにはいいのではないでしょうか?

これまで、AndroidやiPhoneに普通に存在したGame Pad/Controllerと違い、WindowsPhoneには存在しなかったGame Pad/Controllerが1社とは言え、差を詰める要因にはなります。

残念ながらWP8.1WinRTにはまだSDKが対応していませんが、今後更新がされることを要望としてメールはしました。
もしくは、Windows PhoneのBluetooth ProfileにHIDが追加され、WP8.1SDKがDirectInput/XInputへ対応すれば、Windowsストアアプリとももっと共通のつくりで行えるので、マイクロソフトにはぜひ対応してほしいところです。

さて、このゲームコントローラーがなぜBluetooth接続にもかかわらず、HIDのないWP8/8.1で動くのかですが、ここからは自分の想像と予想です。
WP8/8.1のBluetoothProfileには「AVRC(Audio Visual Remote Control)」というProfileがあります。
所謂、AV機器のリモコンですね。
リモコンですから、ボタンも回転するボリュームなどもあるわけですから、これを使用しているんではないか?
だからこそ専用SDKがないと変換できないという形なのでは無いかと、自分は想像と予想をしています。

今回はひとまずここまで言う形ですが、ぜひ続きが書けるようになってほしいなー

 

※自分は試していませんが上位機種があり、これも使用可能ではないかと思われます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です