今回のスクリプトです。
スターつけてくれると喜びます。
https://github.com/konbraphat51/Unity-GoodInputManager
この記事は
素敵なInputManager
を書きました。
モチベーションとしては、
- キー操作の対応を一か所にまとめたい
- エディタ上でキー操作を登録したい
- n度押しに簡単に対応したい
- 関数を呼ぶことでキーを押したことにしたい
というのがありました。
キー操作の対応を一か所にまとめたい
各々のオブジェクトが好き勝手にInput.GetKey()
をしていたら、どいつがどのキーを読んでいるのかややこしくなるので、一元管理したいですよね。
できました。
エディタ上でキー操作を登録したい
Unityの良いところは非プログラマーも 仕事を押し付けられる 編集に参加できるところです。プログラマーだってノーコードで済む作業はノーコードで済ませたいでしょう。
できました。
n度押しに対応したい
一度押しで歩行、二度押しでダッシュ、のような仕様が欲しいですよね?しかしオブジェクトごとにその処理を書くのはだるい、、、
なんと、こちらのInputManager
はn度押しを検知したら登録したメソッドを呼んでくれます!
こちらは「一度押しで歩行、二度押しでダッシュ」を実装した例です。
ここでミソなのが、 ダッシュする直前に一瞬だけ歩いている ところです。二度押しされるまで待って遅延させるより、一度押しがあった時点で歩行を開始し、二度押しを検知した時点で走行に切り替えています。天才じゃないですか?(もちろん、入力待ちができるようにもしています)
関数を呼ぶことでキーを押したことにしたい
例えばクリック検知とかでEventTrigger
で呼ばれたものをキー入力扱いにしておきたいことってありますよね。
できました。(これは球へのクリックを「w」入力に変換している例です)
実装!
コードが全部で700行ほどあって、丸ごとをここに載せても記事がかさばるだけですので、全コードはこちらのgithubを参照していただくようお願いします。
下記のコード例は、オリジナルのクラスやメソッドをゴリゴリに使っているので使いまわしは難しいです。メソッドやクラスの中身についてはgithubを参照してください。
あくまでも、大筋の流れが参考になるかと思います。
シングルトンにする
シングルトンにすることで、staticなメソッドの呼び出し(オブジェクトの参照などせずにInputManager.Push()
を書けるように)しています。
そのシングルトンの処理については、こちらのTeachさんの記事からお借りしました。
関数を登録しておいて、キーが押されたらそれを発動する
UnityEvent
とUnityAction
型を上手に活用することで実現できます。
引数にUnityAction
型を指定することで、メソッドをそのまんま渡すことができます。
UnityEvent
を作っておいて、UnityEvent.AddListener()
で登録。
UnityEvent.Invoke()
で発動させることができる、って寸法です。
エディタ上で編集可能にする
クラスの定義に[System.Serializable]
をつけることで、クラスをエディタ上で編集できるようにできます。
publicメソッドを登録できるのは、変数の型にUnityEvent
を指定したからです。
enum
型を使うことで、プルダウンメニューを作れます。
[System.Serializable] public class KeyAction
{
public enum InputType
{
GetKey,
GetKeyDown,
GetKeyUp
}
[Tooltip("Methods called when key pushed")]
[SerializeField] private UnityEvent _actions;
[Tooltip("Key name. Google 'Unity Input key name'")]
[SerializeField] private string _keyName;
[Tooltip("0:GetKey, 1:GetKeyDown, 2:GetKeyUp")]
[SerializeField] private InputType _inputType = 0;
}
高度なn度押し対応
n度押し自体の対応は、シンプルにキーが押されたら、そのキーに対応するタイマーを作って、タイマーが指定時間になったらアクションを開始するという感じです。
「走る直前に歩く」のような二度押しされるまで一度押しのアクションを実行するn度押し対応の仕組みはこのような感じです。
dontWait
がTrue
と登録されたk度押しのアクションを、k度押しされた瞬間に発動するようにしています。
発動するにはリストrunningActions
に入れることで発動します。
そして、k+1度目が押された瞬間に、runningActions
から削除すれば解決、ってことですね。
/// <summary>
/// n度押しのnの部分が増えたか検知する
/// </summary>
private void CheckSeveralPushesIncreases()
{
//対象のキー全てについて
foreach (string keyName in severalPushesKeys)
{
if (GetKeyDown(keyName))
{
//対象のキーが押された!
//カウンターを増やす
//対象のタイマーを取ってくる(このタイマーは、n度押し検知の時間計測のために使われる)
KeyTimer keyTimer = GetTimer(keyName);
keyTimer.pushedNum++;
//タイマーをリセット
keyTimer.timer = 0f;
////もし即発動の対象なら
//対応するアクションを走査
foreach (KeyActionSeveralPushes keyAction in severalPushesActions)
{
if ((keyAction.keyName == keyName)
&& (keyAction.pushesNum == keyTimer.pushedNum)
&& (keyAction.dontWait == true))
{
//見つかった
//このリストに追加すれば、アクションが実行される
runningActions.Add(keyAction);
}
}
}
}
}
/// <summary>
/// タイマーの時間が指定時間を超過したキーについて、対応する回数のアクションを実行する
/// </summary>
private void CheckTimers()
{
List<KeyTimer> _keyTimers
= new List<KeyTimer>(keyTimers);
foreach(KeyTimer keyTimer in _keyTimers)
{
//タイマーを進める
keyTimer.timer += Time.deltaTime;
//超過した
if (keyTimer.timer >= pushesInterval)
{
//同じキーのアクションが既に登録されていたら、削除
List<KeyActionSeveralPushes> _runningActions
= new List<KeyActionSeveralPushes>(runningActions);
foreach (KeyActionSeveralPushes action in _runningActions)
{
if (action.keyName == keyTimer.keyName)
{
//発見されたので、削除
runningActions.Remove(action);
}
}
////実行させる
//対応するアクションを走査
foreach (KeyActionSeveralPushes action in severalPushesActions)
{
if ((action.keyName == keyTimer.keyName)
&& (action.pushesNum == keyTimer.pushedNum))
{
//見つかった
//登録
runningActions.Add(action);
}
}
//タイマーを削除
keyTimers.Remove(keyTimer);
}
}
}
/// <summary>
/// 実際に実行する
/// </summary>
private void RunSeveralPushesActions()
{
List<KeyActionSeveralPushes> _runningActions
= new List<KeyActionSeveralPushes>(runningActions);
foreach(KeyActionSeveralPushes action in _runningActions)
{
switch (action.inputType)
{
case KeyAction.InputType.GetKey:
//押下している間
if (GetKey(action.keyName))
{
action.Invoke();
}
else
{
//not push => delete
runningActions.Remove(action);
}
break;
case KeyAction.InputType.GetKeyDown:
//押下した瞬間
action.Invoke();
runningActions.Remove(action);
break;
case KeyAction.InputType.GetKeyUp:
//押下解除の瞬間
//not using GetKeyUp because of possibility of
//key up during waiting for keyTimer
if (GetKeyUp(action.keyName))
{
action.Invoke();
runningActions.Remove(action);
}
break;
}
}
}
最後に
いいね大好きです。ぜひこの記事にいいねしてください。