バックグラウンドのUnityアプリで入力検知する方法2020 for Windows10

# 結論
- キーボードのみでOK & 厳密な同時押しは必要ない → UnityRawInputが使いやすい
- ゲームパッド対応や正確な同時押し判定が必要 → SharpDX.DirectInputを使う
  - ※SharpDXは既に開発終了となっていますが、Windows10で動作するのでこの記事で紹介しています

# 開発環境
- Windows 10 Pro
- Unity 2019.4.12f1 (2020.1.8f1でも動作確認済み)

# 事前設定
まずはUnityアプリがBackgroundで動くように設定します。
`Project Settings > Player > Resolution and Presentation`にある`Run In Background`にチェックを入れます。
(プロジェクト作成時にデフォルトでチェックされています)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105321/b3fb2499-7a75-e255-ae54-29645d7dc411.png)

# UnityRawInput
## 使い方
以下のREADMEに記載されている通り、.unitypackageをダウンロードしてプロジェクトにimportして使います。
[GitHub - Elringus/UnityRawInput: Windows Raw Input wrapper for Unity game engine](https://github.com/Elringus/UnityRawInput)

日本語の説明記事はこちらを参照。
[【Unity】バックグラウンド動作時のキー入力取得 - Qiita](https://qiita.com/neku/items/c6ffb5c5b83d0104efd8)

特に難しいことは無いので詳細な説明は割愛しますが、バックグラウンドで動かすためには`RawKeyInput.Start`にtrueを渡す必要があります。

```csharp
private void Start() {
  var workInBackground = true;
  RawKeyInput.Start(workInBackground);
}
```

## 注意事項.1 忘れずにRawKeyInput.Stop()を書く
上記URLにも記載してありますが、OnDisableなどのタイミングで`RawKeyInput.Stop()`を実行する必要があります。**これを忘れると、2回目のアプリ実行時にUnityEditorが落ちるので注意してください。**(私は1時間くらい気づきませんでした...)

```csharp
  private void OnDisable()
  {
    RawKeyInput.Stop();
  }
```

## 注意事項.2 ゲームパッド入力は非対応
以下のIssueがある通り、ゲームパッド入力には対応していないようです。

[Any leads on how to add Joystick Inputs? · Issue #7 · Elringus/UnityRawInput](https://github.com/Elringus/UnityRawInput/issues/7)

## 注意事項.3 複数キーの同時押しがズレる
複数(大体3つ以上のキー)を同時に押した場合に、任意のキーの入力開始タイミングがズレます。
詳しくは後述の[実装サンプル](#実装サンプル)を参照してください。

# SharpDX.DirectInput
ゲームパッド入力ではお馴染みのDirectInputを使う方式です。
Unity(C#)で使う場合はSharpDXというラッパーが用意されているのでこちらを使います。

[Home | SharpDX](http://sharpdx.org/)

上記のページにある通り既に開発が終了してしまっていますが、良い意味で枯れた技術であり、Windows10で問題なく動作します。

## 使い方
以下にアクセスし、右側のリンクから`.nupkg`形式のファイルをダウンロードします。
この記事を書いた時点での最新バージョンは4.2(2018/08/24更新)です。

https://www.nuget.org/packages/SharpDX/
https://www.nuget.org/packages/SharpDX.DirectInput/

ダウンロードした.nupkgファイルをzipとして解凍し、`lib\net45`フォルダ内の.dllファイルをAssetsフォルダに入れます。

```
// 以下2つのDLLを使います
sharpdx.4.2.0.nupkg
  lib\net45\SharpDX.dll
sharpdx.directinput.4.2.0.nupkg
  lib\net45\SharpDX.DirectInput.dll
```

次に、UnityEditorの`Project Settings > Player > Other Settings`にある`Api Compatibility Level`で.NET 4.xを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105321/579edcc5-8bc1-c087-5f96-d80839e93dbd.png)

ここまでで`using SharpDX.DirectInput;`ができるようになるため、後はSharpDXの使い方に従って実装...なのですが、公式ドキュメントが少なかったので参考サイトを載せておきます。

### キーボード入力の場合
[SharpDX(DirectX) キーボード入力 - C# de げぇむプログラマー](http://csdegame.net/sharpdx/dx_input_key.html)

任意のキーが入力されていることを検出するには以下のようにします。

```csharp
Keyboard keyboard = new Keyboard(...); // Keyboardの初期化方法は上記URL参照
...
Key key = Key.A; // 検出したいキー
KeyboardState state = keyboard.GetCurrentState();
return state.IsPressed(inputKey);
```

### ゲームパッド入力の場合
[SharpDX(DirectX) ゲームパッド入力 - C# de げぇむプログラマー](http://csdegame.net/sharpdx/dx_input_pad.html)

任意のボタンが入力されていることを検出するには以下のようにします。

```csharp
/** 実装イメージ **/
Joystick joystick = new Joystick(...); // Joystickの初期化方法は上記URL参照
...
int buttonNo = 0; // 検出したいボタン番号
JoystickState state = joystick.GetCurrentState();
return state.Buttons[buttonNo];
```

# 実装サンプル
UnityRawInputとSharpDXの両方を同時に動かすためのサンプルアプリを作ってみました。
キーボードでA,S,D,Fのそれぞれのキーの入力開始時刻を画面に表示します。

https://github.com/MasaoBlue/unity-windows-bg-input-sample

READMEにも記載している通り、UnityRawInputはウィンドウにフォーカスが当たっていない状態でしか反応しないため注意してください。(この制限はSharpDXと同時に使用した場合のみ発生します)

## UnityRawInputを使った場合の同時押しのズレについて
UnityRawInputを使用した方式だと、1フレーム中に複数のキーを同時押しした場合に、任意のキーの入力開始のタイミングが微妙にズレて検出されてしまいます。

下記がその一例で、SharpDX側では全て同一フレーム内で入力を検出できていますが、UnityRawInputの場合はAとSの入力開始時間がズレているのが分かります。**※あくまで入力開始判定がズレるだけで、複数キーが押しっぱなしになっていることは正常に検出できます**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105321/28c1ad22-b733-cda5-49d4-4842d2e3bc36.png)


## さいごに
私はbeatmania IIDXが好きで、ゲーム中にボタンを押した所を可視化するツールを作っています。

[IIDX Input Capturer](https://masaoblue.github.io/iidx_input_capturer.html)

このツールを構想した2017年当時はSharpDXを使う方法が分からず挫折しましたが、今年になって再度調べてみた所DLLをimportするだけで使えることが分かり、なんとか実装までたどり着くことができました。

Unityのバックグラウンド入力について検索してもUnityRawInputを使った方法しかヒットしなかったため、SharpDXを使う方法もあるということが誰かに伝われば幸いです。

それでは、来年も良いUnityライフを!