Last active
July 8, 2021 17:29
-
-
Save nekomimi-daimao/1dece355acf66b84aaaaa185cff6e271 to your computer and use it in GitHub Desktop.
Unity. 最初の1文字を入力してから一定時間内に特定のキーを入力したかを判定する.途中で違うキーを押すかタイムアウトすると今まで入力した値はクリアされる.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
using Cysharp.Threading.Tasks; | |
using UnityEngine; | |
using UnityEngine.Assertions; | |
namespace Nekomimi.Daimao | |
{ | |
/// <summary> | |
/// 最初の1文字を入力してから一定時間内に特定のキーを入力したかを判定する. | |
/// 途中で違うキーを押すかタイムアウトすると今まで入力した値はクリアされる. | |
/// </summary> | |
public class TypeChecker | |
{ | |
/// <summary> | |
/// A-Z | |
/// </summary> | |
private static readonly KeyCode[] KeyCodeAlphabet = | |
{ | |
KeyCode.A, | |
KeyCode.B, | |
KeyCode.C, | |
KeyCode.D, | |
KeyCode.E, | |
KeyCode.F, | |
KeyCode.G, | |
KeyCode.H, | |
KeyCode.I, | |
KeyCode.J, | |
KeyCode.K, | |
KeyCode.L, | |
KeyCode.M, | |
KeyCode.N, | |
KeyCode.O, | |
KeyCode.P, | |
KeyCode.Q, | |
KeyCode.R, | |
KeyCode.S, | |
KeyCode.T, | |
KeyCode.U, | |
KeyCode.V, | |
KeyCode.W, | |
KeyCode.X, | |
KeyCode.Y, | |
KeyCode.Z, | |
}; | |
/// <summary> | |
/// インスタンスが要求するキーの順番. | |
/// </summary> | |
public readonly KeyCode[] Command; | |
/// <summary> | |
/// インスタンスに設定されたタイムアウト(秒). | |
/// 0以下の場合はタイムアウトしない. | |
/// </summary> | |
public readonly float TimeoutSecond; | |
/// <summary> | |
/// キーが要求通りに入力されたときのコールバック. | |
/// </summary> | |
public Action OnComplete = default; | |
/// <summary> | |
/// コンストラクタ. | |
/// </summary> | |
/// <param name="command"><see cref="Command"/></param> | |
/// <param name="timeoutSecond"><see cref="TimeoutSecond"/></param> | |
/// <param name="progress"><see cref="IProgress{T}"/> (分子/分母)</param> | |
/// <param name="token"><see cref="CancellationToken"/></param> | |
public TypeChecker(KeyCode[] command, float timeoutSecond, | |
IProgress<(int numerator, int denominator)> progress, CancellationToken token) | |
{ | |
Assert.IsNotNull(command); | |
Assert.IsTrue(command.Length > 0); | |
Command = command; | |
TimeoutSecond = timeoutSecond; | |
CheckTypeLoop(progress, token).Forget(); | |
} | |
private async UniTaskVoid CheckTypeLoop(IProgress<(int numerator, int denominator)> progress, CancellationToken baseToken) | |
{ | |
var first = Command[0]; | |
while (true) | |
{ | |
await UniTask.Yield(); | |
if (baseToken.IsCancellationRequested) | |
{ | |
return; | |
} | |
if (CurrentPressed() != first) | |
{ | |
// 最初の1文字が入力されるまではタイムアウトも以後の判定も行わない | |
progress?.Report((0, Command.Length)); | |
continue; | |
} | |
// 最初の1文字が入力された | |
progress?.Report((1, Command.Length)); | |
var cancelSource = CancellationTokenSource.CreateLinkedTokenSource(baseToken); | |
var token = cancelSource.Token; | |
bool result; | |
// 1文字目以降のキーが順番に入力されたら完了する | |
var type = TypeCommandAsync(progress, token); | |
if (TimeoutSecond > 0f) | |
{ | |
// タイムアウトが設定されている場合はカウントする | |
var timeout = UniTask.Delay(TimeSpan.FromSeconds(TimeoutSecond), cancellationToken: token); | |
var (hasResultLeft, ret) = await UniTask.WhenAny(type, timeout); | |
// キー入力のtaskが先に完了してかつそれがtrue | |
result = hasResultLeft && ret; | |
} | |
else | |
{ | |
// タイムアウトを待たない場合はキー入力の結果だけを待つ | |
result = await type; | |
} | |
if (result) | |
{ | |
OnComplete?.Invoke(); | |
} | |
if (!cancelSource.IsCancellationRequested) | |
{ | |
cancelSource.Cancel(); | |
} | |
} | |
} | |
/// <summary> | |
/// キー入力を監視して正しく入力されたかを判定する. | |
/// 最初の1文字は判定しない. | |
/// </summary> | |
/// <param name="progress"><see cref="IProgress{T}"/> (分子/分母)</param> | |
/// <param name="token"><see cref="CancellationToken"/></param> | |
/// <returns>true/false = 成功/失敗</returns> | |
private async UniTask<bool> TypeCommandAsync( | |
IProgress<(int numerator, int denominator)> progress, CancellationToken token) | |
{ | |
for (var count = 1; count < Command.Length; count++) | |
{ | |
while (true) | |
{ | |
await UniTask.Yield(); | |
if (token.IsCancellationRequested) | |
{ | |
return false; | |
} | |
var current = CurrentPressed(); | |
if (current == KeyCode.None) | |
{ | |
// 何も入力していない場合は判定しない | |
continue; | |
} | |
if (Command[count] != current) | |
{ | |
// 違うキーが入力されたので中断する | |
return false; | |
} | |
// 求めているキーが入力されたので進捗を通知して次のキーに進む | |
progress?.Report((count + 1, Command.Length)); | |
break; | |
} | |
} | |
return !token.IsCancellationRequested; | |
} | |
/// <summary> | |
/// <see cref="KeyCodeAlphabet"/>に含まれている現在押されているキーを返す. | |
/// </summary> | |
/// <returns><see cref="KeyCode"/></returns> | |
private static KeyCode CurrentPressed() | |
{ | |
if (!Input.anyKeyDown) | |
{ | |
return KeyCode.None; | |
} | |
// every Update, no LINQ | |
foreach (var keyCode in KeyCodeAlphabet) | |
{ | |
if (Input.GetKeyDown(keyCode)) | |
{ | |
return keyCode; | |
} | |
} | |
return KeyCode.None; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Cysharp.Threading.Tasks; | |
using UnityEngine; | |
using UnityEngine.UI; | |
namespace Nekomimi.Daimao | |
{ | |
public class TypeCheckerExample : MonoBehaviour, IProgress<(int numerator, int denominator)> | |
{ | |
private static readonly KeyCode[] Command = | |
{ | |
KeyCode.N, | |
KeyCode.E, | |
KeyCode.K, | |
KeyCode.O, | |
KeyCode.M, | |
KeyCode.I, | |
KeyCode.M, | |
KeyCode.I, | |
}; | |
[SerializeField] | |
private Slider _slider = default; | |
[SerializeField] | |
private Text _textSlider = default; | |
[SerializeField] | |
private Text _textComplete = default; | |
private void Start() | |
{ | |
var typeChecker = new TypeChecker(Command, 5f, this, this.GetCancellationTokenOnDestroy()); | |
typeChecker.OnComplete += () => | |
{ | |
_textComplete.text = "COMPLETE!"; | |
Debug.Log("COMPLETE!"); | |
}; | |
} | |
public void Report((int numerator, int denominator) value) | |
{ | |
var (numerator, denominator) = value; | |
_slider.maxValue = denominator; | |
_slider.value = numerator; | |
_textSlider.text = $"{numerator} / {denominator}"; | |
Debug.Log($"progress {numerator} / {denominator}"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment