Unityでキーボード入力をイベント駆動ライクに扱ってみる

※2017/10/26 修正: @kajimura さんのコメントをもとにイベントリスナ側のコードを修正しました、ご指摘ありがとうございました！

# サンプルについて(2017/11/3追記)
この記事にて作ったScriptを、サンプル的なそれと一緒に[GitHubに上げてみました](https://github.com/AirBeans5956/UniKeyEventDispatcher)．

# 前置き
イベント駆動なプログラミングに浸かってると、Unityでもキー入力等をイベント駆動ぽく処理とかしてみたくなってくることもあります。[^1]

ですが、Unityでは各種のキー入力をイベント駆動的に処理する仕組みが、Unity側で用意されていない...ように見受けられます。[^2]

そこで、Unityの諸々を利用して、キー入力をイベント駆動ぽく処理する試みをしてみます。

以下、Unity 5.6f3で実装しました。

# 参考文献とか
以下のWebページを参考にしました。
[SendMessageに変わる新しいメッセージシステム、ExecuteEvents.Execute - テラシュールブログ](http://tsubakit1.hateblo.jp/entry/2015/04/13/010645)
[Unity - スクリプトリファレンス: EventSystems.ExecuteEvents.Execute](https://docs.unity3d.com/jp/540/ScriptReference/EventSystems.ExecuteEvents.Execute.html)

# 実装する
## インタフェース構築
通知用のInterfaceを組み立てます。  
キーの押下と押上のときに、`UnityEngine.EvemtSystems.ExecuteEvents.Execute<>()`でイベントを発行する想定で組んでみます。

```csharp:KeyInputEvent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using UnityEngine.EventSystems;

/// <summary>
/// キー押下時イベントのInterface
/// </summary>
public interface IKeyDownEvent : IEventSystemHandler {
	void OnKeyDown(List<KeyCode> downKeyList);
}
/// <summary>
/// キー押上時イベントのInterface
/// </summary>
public interface IKeyUpEvent : IEventSystemHandler {
	void OnKeyUp(List<KeyCode> upKeyList);
}
```
`UnityEngine.EvemtSystems.ExecuteEvents.Execute<>()`がイベントを発火できるように、`IEventSystemHandler`を継承するのがミソです。

## 発行役の構築
イベントの発行役を実装します。
ComponentとしてGameObjectにAttachする想定です。

仕組みは、

1. 毎フレーム、`Keycode`列挙型で登録されているキー全部に押下と押上の瞬間をチェック
2. 1つでもチェックに引っ掛かったら、イベントを`UnityEngine.EvemtSystems.ExecuteEvents.Execute<>()`経由でイベント発行

といった感じに。

```csharp:KeyEventDispatcher.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System;
using UnityEngine.EventSystems;

/// <summary>
/// 各種のキー入力イベントの発行役
/// </summary>
public class KeyEventDispatcher : MonoBehaviour {
	
	/// <summary>
	/// 処理負荷軽減のため、KeyCodeの全ての値を保持する配列
	/// </summary>
	static Array KeyCodeList;

	// Use this for initialization
	void Start () {
		//全てのKeyCode列挙型の値を控える
		KeyCodeList = Enum.GetValues(typeof(KeyCode));
	}
	
	// Update is called once per frame
	void Update () {
		var tmpUpList = new List<KeyCode>();
		foreach (KeyCode code in KeyCodeList) {//押上キーがあるかチェック
			if (Input.GetKeyUp(code)) {
				tmpUpList.Add(code);
			}
		}
		if (tmpUpList.Count != 0) {//1つでも押上キーがあれば
			ExecuteEvents.Execute<IKeyUpEvent>(this.gameObject,
				null,
				(handler, eventData) => handler.OnKeyUp(tmpUpList) //押上キー全てを込めてリスナを叩く
			);
		}
		var tmpDownList = new List<KeyCode>();
		foreach (KeyCode code in KeyCodeList) {//押下キーがあるかチェック
			if (Input.GetKeyDown(code)) {
				tmpDownList.Add(code);
			}
		}
		if (tmpDownList.Count != 0) {//1つでも押下キーがあれば
			ExecuteEvents.Execute<IKeyDownEvent>(this.gameObject,
				null,
				(handler, eventData) => handler.OnKeyDown(tmpDownList) //押下キー全てを込めてリスナを叩く
			);
		}
	}
}
```

この例では、発行役と同じGameObjectにAttachされているComponentに対して、イベントを発行しています。

## リスナ側実装
イベントを受け取るComponentを作ります。

先の作業で作ったInterfaceを実装し、通知役と同じGameObjectにAttachします。
すると、そのComponentはキー入力イベントを受け取ることができるようになります。

以下は、イベント発行の際に送られてきた`KeyCode`をそのままデバッグコンソールに出力するだけの実装です。

```csharp:KeyEventListener.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class KeyEventListener : MonoBehaviour, IKeyUpEvent, IKeyDownEvent {

	// Use this for initialization
	void Start () {
		
	}

	// Update is called once per frame
	void Update () {
		
	}

	public void OnKeyUp(List<KeyCode> upKeyList) {
		foreach (KeyCode code in upKeyList) {
			Debug.Log("Key up:" + code.ToString());
		}
	}

	public void OnKeyDown(List<KeyCode> downKeyList) {
		foreach (KeyCode code in downKeyList) {
			Debug.Log("Key down:" + code.ToString());
		}
	}
}
```

どのキーを受け付けるか、などは全てリスナでよしなに処理し、発行役はどんなキーでもイベントを発行するような実装です。

#あとがき
<!-- なんとかイベント駆動ぽくキー入力を処理できるように実装してみました。
(何かと無駄もありますが...)

あと必要なのは例外処理であったり、インタフェースを別な名前空間に放り込んだり[^3]とかでしょうか。 -->

PCで遊ぶ音楽ゲームとか、タイピングゲームとかに活用できそうかな...と考えています。

[^1]: あくまで私の場合
[^2]: 見落としているだけかもしれませんが...
[^3]: 開発元がグローバル名前空間に同名のインタフェース等を放り込む可能性は低そうですが