【Unity】【C#】CapsLock のオン・オフ状態を取得する (Windows) 
2020/03/18 Wed [edit]
Unity には Input クラスという入力に関するクラスが元からあるが、残念ながら「Input.GetKey(KeyCode.CapsLock)」のようにして、CapsLock の状態を取得するのは上手く行かなかった(押下した瞬間の判定なので、常に true になる)。
また、OnGUI() での Event.capsLock というものもあるが(Update() などでは使えない=エラーが出る)、こちらも同じように押下した瞬間に true になるだけで、「オン/オフの継続している状態」が取得できない。
なのでいつものようにググってたら、Windows API から取得する方法があったので、試してみた。しかし同時に利用するにあたって、いくつか注意する点もあったので、備忘録として残しておこう。
(※) Unity 2019.2.21f1 / Windows10(x64) で確認
まずは Windows API から状態を取得するメソッドを static で定義しよう。以下のコードは参考資料の内容を、使い回せるように少しだけ手を加えたものだ。.NET 4.x 以降のコードになっているので注意して欲しい(.NET 3.x にしたいなら「=>」などを通常のメソッドに書き直せば使える)。
(参考)
・Capslock detection ?
●Windows API から CapsLock の状態を取得する
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
using System.Runtime.InteropServices;
public static class WindowsUtil //※名前は任意
{
//https://forum.unity.com/threads/capslock-detection.538792/
[DllImport("user32.dll")]
public static extern short GetKeyState(int keyCode);
//CapsLock の状態取得
public static bool IsCapsLockOn
=> (((ushort)GetKeyState(0x14)) & 0xffff) != 0; //※.NET 4.x
}
#endif
あとは参考資料と同じように、起動時にプロパティにでも入れておいて、キー入力のたびに反転するのが良いだろう。毎フレーム API 使うのは負荷が高そうだしね(常に型変換の必要があるので)。ここまではほとんど参考資料と同じだ。
メインは例えば、以下のようなコードで確認できるだろう(何かキーを押したときに CapsLock のオン・オフ状態が取得できる)。
●メインでの使用例
using UnityEngine;
bool IsCapsLockOn { get; set; }
private void Awake()
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
IsCapsLockOn = WindowsUtil.IsCapsLockOn; //Windows API を定義したクラス
#endif
}
private void Update()
{
if (Input.anyKeyDown)
{
if (Input.GetKeyDown(KeyCode.CapsLock))
{
IsCapsLockOn = !IsCapsLockOn;
}
Debug.Log($"IsCapsLockOn = {IsCapsLockOn}"); //※.NET 4.x
}
}
ただこれには1つ注意点があって、「アプリから離れる → 他のアプリ上などで CapsLock を切り替える → アプリに戻る」などの操作をすると、IsCapsLock のプロパティが狂ってしまう。なので、再度アプリにフォーカスが当たったときや、アプリのポーズ復帰(Resume)のようなタイミングで再度 Windows API から取得した方が良いようだ。
●フォーカスやレジュームのタイミングで再取得例
・・・(中略 ※前述のコード)・・・
private void OnApplicationFocus(bool focus)
{
if (focus)
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
IsCapsLockOn = WindowsUtil.IsCapsLockOn;
#endif
}
}
private void OnApplicationPause(bool pause)
{
if (!pause) //Resume
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
IsCapsLockOn = WindowsUtil.IsCapsLockOn;
#endif
}
}
しかし、実はまだこれでも上手く行かないときがある。何度か試してみたら、どうやら OnApplicationFocus 等と同フレームで取得すると、不正確になるようだ。なので1フレーム遅らせて取得するなどの工夫も必要になる(エディタ上では上手く行くかも知れないが、ランタイム時にはタイミングによって上手く行かないことがある=Unity ではよくある事なので覚えておくと良い)。
1フレーム遅らせて処理する方法は色々あるので、好きにやって欲しい。コルーチンでも良いし、.NET 4.x なら async/await でも良いだろう。UniRx プラグイン使っているなら Observable.NextFrame() を使うのも良いね。以下に例を載せておくので、既存のコードとの兼ね合いで実装すると良いだろう。
●コルーチン例
・・・(中略)・・・
private IEnumerator OnApplicationFocus(bool focus)
{
if (focus)
{
yield return null;
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
IsCapsLockOn = WindowsUtil.IsCapsLockOn;
#endif
}
}
//OnApplicationPause も同様
●UniRx, UniTask / async, await 例
・【Unity】【UniRx】時間系のオペレータまとめ
・【Unity】【C#】UniRx で「1フレームごと待機して処理」してみる
(関連記事)
【Unity】【C#】UniRx で「1フレームごと待機して処理」してみる
【Unity】【C#】KeyCode をリアルタイムで調べる / Windows 日本語キーボードでの記号の KeyCode 一覧
【Unity】5ボタンマウスの KeyCode 図解
【Unity】【C#】ゲームパッドの右スティックを使えるようにする
| h o m e |