cocos2d-x v3.x win32でゲームパッド利用する

cocos2d-x v3.x win32でゲームパッドを利用するためのメモです。
軽く調べた感じcocos側に対応がなかったので、Android/iOSにあるcontroller系の機能をwin32対応してます。（glfwにjoystick系の処理あるんですが何か上手く動かなかったので、joyGetPosEx使ってます）
v3.3で検証しましたが、たぶんv3.x系ならいけると思います。

大まかな流れとしては、以下な感じ。
1. テスト用プロジェクトの準備
2. controller系イベントの修正
3. win32向けゲームパッド関連の処理を追加

##テスト用プロジェクトの準備
win32にはそもそもgame-controller-test自体が存在していないので、それを準備する。

* cpp-empty-tests/proj.win32フォルダをcpp-empty-tests/game-controller-testへリネームコピー。
* 同フォルダにあるcpp-empty-tests.\*をgame-controller-test.\*へリネーム。
* HelloWorldScene.cpp/hをプロジェクトから削除。
* GameControllerTest.cpp/hをプロジェクトに追加
* cpp-empty-tests.vcxprojをcocos2d-win32.vc2012.slnへ追加

これでテスト用プロジェクトの準備は終了。この段階ではgame-controller-testをビルドしてもリンクエラーがでるので注意。

## controller系イベントの修正
Android/iOS用にマスクされている箇所を修正、不足しているファイルをlibcocos2dプロジェクトへ追加する。また不足している処理を追加する。

* cocos2d/baseに存在する CCEventController.cpp/hをlibcocos2dプロジェクトへ追加。
* 同じくCCEventListenerController.cpp/hを追加。
* 同じくCCController.cpp/hを追加。
* CCController-android.cppをCCController-win32.cppへリネームコピーし、libcocos2dプロジェクトへ追加。
* Android/iOSのプラットフォーム依存箇所をwin32向けに修正。

```CCController.cpp
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || 
CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
```
```CCEventDispatcher.cpp
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || 
CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
```
```CCController-win32.cpp
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
//#include "jni/JniHelper.h"//いらない
```
```CCController-win32.cpp
void Controller::receiveExternalKeyEvent(int externalKeyCode,bool receive)
{
//実装削る
}
```
```CCController-win32.cpp
//adapter系の関数をwin32用にリネームして引数を修正
    void CC_DLL win32_cocos2dx_lib_GameControllerAdapter_nativeControllerConnected(const char* deviceName, int controllerID)
    {
        CCLOG("controller id: %d connected!", controllerID);
        cocos2d::ControllerImpl::onConnected(std::string(deviceName), controllerID);
    }

    void CC_DLL win32_cocos2dx_lib_GameControllerAdapter_nativeControllerDisconnected(const char* deviceName, int controllerID)
    {
        CCLOG("controller id: %d disconnected!", controllerID);
        cocos2d::ControllerImpl::onDisconnected(std::string(deviceName), controllerID);
    }

    void CC_DLL win32_cocos2dx_lib_GameControllerAdapter_nativeControllerButtonEvent(const char* deviceName, int controllerID, int button, bool isPressed, float value, bool isAnalog)
    {
        cocos2d::ControllerImpl::onButtonEvent(std::string(deviceName), controllerID, button, isPressed, value, isAnalog);
    }

    void CC_DLL win32_cocos2dx_lib_GameControllerAdapter_nativeControllerAxisEvent(const char* deviceName, int controllerID, int axis, float value, bool isAnalog)
    {
        cocos2d::ControllerImpl::onAxisEvent(std::string(deviceName), controllerID, axis, value, isAnalog);
    }
```

* そのままだとリンクエラーになるので、CC_DLLを追加

```CCController.h
class CC_DLL Controller
```
```CCEventListenerController.h
class CC_DLL EventListenerController : public EventListener
```

ここまでの作業でビルドが通り、game-controller-testが実行できるようになるはず。
ただし、実行はできるがまだコントローラーは反応しない。
Resourceのロードが失敗する場合は、デバッグ→作業ディレクトリに$(ProjectDir)..\Resourcesを追加する。

![GameControllerTest.png](https://qiita-image-store.s3.amazonaws.com/0/49333/0331210d-e0e0-f7f9-f672-e110fdbd3f14.png)

## win32向けゲームパッド関連の処理を追加
今のままだとゲームパッドの入力を受けてcocosのeventを発行する箇所がないので、それを追加する。
今回はGameControllerTest.cppに追加する。

```GameControllerTest.cpp
//updateに処理を書くので、scheduleUpdate有効にしておく
void GameControllerTest::registerControllerListener()

    scheduleUpdate();
```
```GameControllerTest.cpp
//イベントの発行
extern "C" {

	void win32_cocos2dx_lib_GameControllerAdapter_nativeControllerConnected(const char* deviceName, int controllerID);
	void win32_cocos2dx_lib_GameControllerAdapter_nativeControllerDisconnected(const char* deviceName, int controllerID);
	void win32_cocos2dx_lib_GameControllerAdapter_nativeControllerButtonEvent(const char* deviceName, int controllerID, int button, bool isPressed, float value, bool isAnalog);
	void win32_cocos2dx_lib_GameControllerAdapter_nativeControllerAxisEvent(const char* deviceName, int controllerID, int axis, float value, bool isAnalog);

} // extern "C" {
#include <array>
struct ControllerInfo{
	ControllerInfo()
	:_isConnected(false)
	, _button(0)
	, _joy(0)
	{
	}
	bool	_isConnected;
	int		_button;
	int		_joy;
};
std::array<ControllerInfo, 32>	_controllerInfos;

void GameControllerTest::update(float delta)
{
	JOYINFOEX JoyInfoEx;
	JoyInfoEx.dwSize = sizeof(JOYINFOEX);
	JoyInfoEx.dwFlags = JOY_RETURNALL;
	for (uint32_t i = 0; i < joyGetNumDevs(); i++){
		bool connected = false;
		if (JOYERR_NOERROR == joyGetPosEx(i, &JoyInfoEx)){
			connected = true;
		}
		char deviceName[64];
		sprintf(deviceName, "pad%d", i);
		if (_controllerInfos[i]._isConnected != connected){
			if (connected){
				win32_cocos2dx_lib_GameControllerAdapter_nativeControllerConnected(deviceName, i);
			}
			else{
				win32_cocos2dx_lib_GameControllerAdapter_nativeControllerDisconnected(deviceName, i);
			}
		}
		_controllerInfos[i]._isConnected = connected;

		if (connected){
			struct InputTable{
				int	_src;
				int _dst;
			};
			static InputTable	buttonTable[] = {
				{ 1 << 0, Controller::Key::BUTTON_A },
				{ 1 << 1, Controller::Key::BUTTON_B },
				{ 1 << 2, Controller::Key::BUTTON_X },
				{ 1 << 3, Controller::Key::BUTTON_Y },
			};
			auto buttonDiff = _controllerInfos[i]._button ^ JoyInfoEx.dwButtons;
			for (auto& j : buttonTable){
				if (buttonDiff&j._src){
					bool isPressed = (_controllerInfos[i]._button&j._src) ? (false) : (true);
					win32_cocos2dx_lib_GameControllerAdapter_nativeControllerButtonEvent(deviceName, i, j._dst, isPressed, 0, false);
				}
			}
			int joy = 0;
			switch (JoyInfoEx.dwPOV){
			case 0://up
				joy = 1 << 0;
				break;
			case 4500:
				joy = 1 << 0 | 1 << 1;
				break;
			case 9000://right
				joy = 1 << 1;
				break;
			case 13500:
				joy = 1 << 1 | 1 << 2;
				break;
			case 18000://down
				joy = 1 << 2;
				break;
			case 22500:
				joy = 1 << 2 | 1 << 3;
				break;
			case 27000://left
				joy = 1 << 3;
				break;
			case 31500:
				joy = 1 << 3 | 1 << 0;
				break;
			}
			static InputTable	joyTable[] = {
				{ 1 << 0, Controller::Key::BUTTON_DPAD_UP },
				{ 1 << 1, Controller::Key::BUTTON_DPAD_RIGHT },
				{ 1 << 2, Controller::Key::BUTTON_DPAD_DOWN },
				{ 1 << 3, Controller::Key::BUTTON_DPAD_LEFT },
			};
			auto joyDiff = _controllerInfos[i]._joy ^ joy;
			for (auto& j : joyTable){
				if (joyDiff&j._src){
					bool isPressed = (_controllerInfos[i]._joy&j._src) ? (false) : (true);
					win32_cocos2dx_lib_GameControllerAdapter_nativeControllerButtonEvent(deviceName, i, j._dst, isPressed, 0, false);
				}
			}
			float x = (float)JoyInfoEx.dwXpos / 32768.f - 1.f;
			win32_cocos2dx_lib_GameControllerAdapter_nativeControllerAxisEvent(deviceName, i, Controller::Key::JOYSTICK_LEFT_X, x, true);
			float y = (float)JoyInfoEx.dwYpos / 32768.f - 1.f;
			win32_cocos2dx_lib_GameControllerAdapter_nativeControllerAxisEvent(deviceName, i, Controller::Key::JOYSTICK_LEFT_Y, y, true);

			_controllerInfos[i]._button = JoyInfoEx.dwButtons;
			_controllerInfos[i]._joy = joy;
		}
	}
}
```


これでゲームパッドの接続を検出し、基本的なボタン等が利用できるはずです。
ゲームパッドはボタンの差が結構あるので、キーアサインなどは別途対応の必要があります。
また現状のままだと、都度updateにゲームパッド処理書くのが手間なので、何か適当なcustom event準備してlibcocos2d側に追加しておくと楽かも？