Unity C# 格闘ゲームのコマンド入力判定

## DEMO

http://bl.ocks.org/feo52/raw/2f8318a084915fd0108738253f3ec036/

https://gist.github.com/feo52/8e922e9ff2768b8f0d6c9320a9fe2127

※1
Build.hoge.unitywebのdataなどが重いので、
Wi-Fi接続またはLAN接続のPCで確認ください。

※2
判定部分とは実質無関係ですが、
OnGUI()を画面表示に使用しており、かつ、
OnGUI()の関連処理で文字列処理を多用しています。
そのため上記処理が起因の[GCのスパイク](https://www.google.com/search?q=%E3%82%AC%E3%83%99%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3+%E3%82%B9%E3%83%91%E3%82%A4%E3%82%AF)が発生します。

※3
[時間精度の引き下げ](https://developer.mozilla.org/ja/docs/Web/API/Performance/now#reduced_time_precision)が行なわれる事があるようなので、
処理時間がミリ秒の精度に丸められることがあります。
同処理が施されたブラウザでは処理時間が正確に計測出来ません。

Firefoxであれば、
[Firefoxの設定エディター](https://support.mozilla.org/ja/kb/about-config-editor-firefox)にて、自己責任の上、
privacy.reduceTimerPrecisionをfalseにすれば、
処理時間の丸めなしにはできないようですが、
処理時間の丸めの精度は変更できるようです。

## はじめに

Unityの存在を先日知り、
Unity及びC#のお勉強も兼ねて、
波動拳の入力受付をしてみよう、
という思いに何故か至りました。

Unity及びC#を今回初めて触る、
オブジェクト指向恐怖症のIT業界外の人間で、
html,css,JavaScriptやPythonを、
休日等に少々触る程度の素人ですので、
本内容に間違いや勘違いが見受けられるかもしれません。

<!--
Rxを使用した方法がQiitaに投稿されていますが、
Rxってなにそれおいしいの？
といったレベルの人間なので、
入力キーの履歴から判定しているようだ、
ということくらいしか分かりませんでした...
ググると他にも入力キーの履歴から、
判定をしているような方法が見受けられます。
自分も同様の方法による対応を検討していたので、
アイデア的には本投稿には目新しいものは無いと思います。
-->

カプコン社のストリートファイター等の技名を使用していますが、
あのコマンドね、と認識が伝わりやすいだろう、という意図です。

## 解説前書

https://gist.github.com/feo52/5d45238d0c02136c6343e7e64f39ff07

冒頭のDEMOから以下処理を削除した波動拳(強)のみの入力判定で、
冒頭のDEMOから以下処理を削除した入力判定の中核部分のコードになります。

・波動拳(強)以外の技の削除
・溜め時間取得用処理の削除
・同時押し判定緩和用処理の削除
・本解説に不要なinput処理の削除
・本解説に不要な処理時間低減施策削除
・Inspectorへのデバッグ表示用処理削除
・GameViewへのデバッグ表示関連処理削除

※Consoleに入力判定の成否を出力

## 一口解説

詳細はコードを見て貰うものとして、
全体のざっくりした流れは以下です。

・各種enum設定
・技のコマンドのクラスとそのList　…①
・十字キーとパンチキックの状態取得用のクラス
・キー履歴保持のクラスとそのList
・void Start()
　・技のコマンドのListの反転　…②
　・キー履歴保持のためのインスタンス生成
　・フレームレートの設定
・void Update()
　・今フレームのキーの状態をキー履歴保持のListに追記
　（ある程度の量になったら古い履歴情報から順次削除）
　　・foreach
　　　・foreach
　　　　・技のコマンドのひとつひとつを順次判定処理　…③
　　　　（波動拳は下⇒右下⇒右⇒Pなので4回判定）
　　　　（foreach1回目はP 　の判定）
　　　　（foreach2回目は右　の判定）
　　　　（foreach3回目は右下の判定）
　　　　（foreach4回目は下　の判定）

#### ①技のコマンドのList

```csharp
    private List<List<MoveCommand>> MoveCommandList = new List<List<MoveCommand>>()
    {
        new List<MoveCommand>()
        {
            // 波動拳                    状態は押している         キーは左   次の入力は 6フレーム以内 次の入力同時押し不可NextFrame判定
            new MoveCommand { DoFinger = Finger.Hold,    InputKey = Key.J2, NextTime =  6,            SlipTime = 1 },
            // 波動拳                    状態は押している         キーは左下 次の入力は 6フレーム以内 次の入力同時押し不可NextFrame判定
            new MoveCommand { DoFinger = Finger.Hold,    InputKey = Key.J3, NextTime =  6,            SlipTime = 1 },
            // 波動拳                    状態は押している         キーは右   次の入力は 7フレーム以内 次の入力同時押し可能ThisFrame判定
            new MoveCommand { DoFinger = Finger.Hold,    InputKey = Key.J6, NextTime =  7,            SlipTime = 0 },
            // 波動拳                    状態は押した             キーはP    最後なので 0に必ずする   最後なので 0に必ずする
            new MoveCommand { DoFinger = Finger.Pressed, InputKey = Key.HP, NextTime =  0,            SlipTime = 0 },
        },
    };
```

以下の条件をListに設定。

1つ目の入力：
キーの下　を押している、
次の入力は6フレーム以内、
次の入力の判定とは同一フレーム不可。

2つ目の入力：
キーの右下を押している、
次の入力は6フレーム以内、
次の入力の判定とは同一フレーム不可。

3つ目の入力：
キーの右　を押している、
次の入力は7フレーム以内、
次の入力の判定とは同一フレーム可能（右とＰの同時押しOK）

4つ目の入力：
キーのＰ　を押した。

#### ②技のコマンドのListの反転

```csharp
        foreach (List<MoveCommand> MoveCommandData in MoveCommandList) MoveCommandData.Reverse();
```

波動拳は下⇒右下⇒右⇒Ｐだが、
処理順はＰ⇒右⇒右下⇒下の順序で判定を行なうため反転。

#### ③技のコマンドのひとつひとつを順次判定処理

```csharp
        foreach (List<MoveCommand> MoveCommandData in MoveCommandList)
        {
            // 中略
            foreach (MoveCommand record in MoveCommandData)
            {
                // 中略
                InputGetKeyTinyHistoryDataRow = record.DoFinger switch
                {
                    Finger.Hold    => InputGetKeyTinyHistoryList.FindLast(row => ((StaticFrameCount - StaticNextTime) <= row.FrameCount) && (row.FrameCount <= (StaticFrameCount - StaticSlipTime)) && ((row.Hold    & StaticInputKey) == StaticInputKey)),
                    Finger.Pressed => InputGetKeyTinyHistoryList.FindLast(row => ((StaticFrameCount - StaticNextTime) <= row.FrameCount) && (row.FrameCount <= (StaticFrameCount - StaticSlipTime)) && ((row.Pressed & StaticInputKey) == StaticInputKey)),
                    _ => null
                };
                // 中略
            }
            // 中略
        }
```

:::note info
参考
[C# 8.0 switch 式](https://ufcpp.net/blog/2018/12/cs8switchexpr/)
:::

キー履歴保持のList内に条件に合うキーが存在するかを判定。
以下の全ての条件判定が成立していれば波動拳入力受付承認。

波動拳のみなので、
外側のforeachは1回のみ。

内側のforeachの1回目：
現在のフレームに、
Ｐを"押した"、
という条件に合うキーが存在するか判定。

内側のforeachの2回目：
(Ｐ　の成立フレーム-7)から
(Ｐ　の成立フレーム-0)の間で、
右　を"押している"、
という条件に合うキーが存在するか判定。

内側のforeachの3回目：
(右　の成立フレーム-6)から
(右　の成立フレーム-1)の間で、
右下を"押している"、
という条件に合うキーが存在するか判定。

内側のforeachの4回目：
(右下の成立フレーム-6)から
(右下の成立フレーム-1)の間で、
下　を"押している"、
という条件に合うキーが存在するか判定。

## 備考情報

Debug.Logに関する備忘録。
Debug.Logは結構重いようで、
環境によって違うかもしれませんが、
1回で0.1msとか持っていかれるので、
処理時間計測時は使用しない方がよいです。

WebGLのBuildに関する備忘録。
WebGLのBuildすると日本語が表示されません。
等幅フォントを使用したいので、
[NotoSansMonoCJKjp-Bold.otf](https://github.com/googlefonts/noto-cjk/tree/main/Sans/Mono)を、
[Assets/Resources](https://qiita.com/takish/items/8608ba9070755da3ae6d)に配置して表示しています。

<!--
WebGLのBuild後の生成ファイルの内、
index.html内の下記部分のJavaScriptを変更して、
CSSの新機能であるaspect-ratioを設定。

```javascript
        // 変更前
        canvas.style.width = "960px";
        canvas.style.height = "600px";
        // 変更後
        container.style.maxWidth = "960px";
        container.style.width = "100%";
        canvas.style.maxWidth = "960px";
        canvas.style.width = "100%";
        canvas.style.height = "auto";
        canvas.style.aspectRatio = "8 / 5";
```
-->

## 動作方法

Unityを触られている人なら不要とは思いますが、念の為。

###### Unity Hub での工程

Unity Hub でプロジェクトを新規作成。2D(または3D)で作成。
※1 Unityのバージョンは［Unity 2020.3.33f1］や［Unity 2021.3.0f1］以降であれば大丈夫かと...
※2 プロジェクト名と保存先はお任せします。
　↓
{ Unity 起動 }
　↓

###### Unityでの工程

GameObject⇒Create Empty
InspectorウインドウでAdd Component⇒New script⇒Nameをscriptにして⇒Create and Add
ProjectウインドウのAssetsに追加されたscriptをダブルクリック。
　↓
{ Visual Studio Community 起動 }
　↓

###### Visual Studio Community での工程

冒頭のリンク先のコード、または、
中段のリンク先のコード、どちらかをコピペ。
[エンコードをUnicode(UTF-8シグネチャ付き)の形式に変更](https://teratail.com/questions/265233)してscript.csを上書き保存。
Visual Studio Community を終了。
　↓
{ Unity をアクティブウインドウにする }
　↓

###### Unityでの工程

File⇒Save
File⇒Exit
　↓
{ Unity Hub をアクティブウインドウにする }
　↓

###### Unity Hub での工程

上記で作成したプロジェクトをクリックしてUnityを再度起動。
　↓
{ Unity 起動 }
　↓

###### Unityでの工程

( HierarchyウインドウのGameObjectをクリック )
( Consoleウインドウをクリックした後 )
Edit⇒Play

###### 再度起動理由

Unityを再起動しないと、
当方の環境での話なのかもしれませんが、
Input Manager で同時押しのキー取得できない、とか、
Input Manager の動作が何故かもっさりしたり、とか、
Input Manager が正常に動作をしてくれません。

## References

#### Unity 初心者入門講座

https://www.youtube.com/playlist?list=PLED8667EEZ9aB72WVMHfRHBd6oj9vplRy

https://www.youtube.com/watch?v=SwI3duEdcCA&list=PLED8667EEZ9aB72WVMHfRHBd6oj9vplRy

#### Unity

http://tsubakit1.hateblo.jp/

https://baba-s.hatenablog.com/

https://light11.hatenadiary.com/

https://kan-kikuchi.hatenablog.com/

#### Unity Profiler

https://light11.hatenadiary.com/entry/2019/09/19/000006

https://light11.hatenadiary.com/entry/2019/12/23/222233

https://light11.hatenadiary.com/archive/category/PerformanceTuning

https://kan-kikuchi.hatenablog.com/entry/Unite2017_BeforeOptimizing_1

https://kan-kikuchi.hatenablog.com/entry/Unite2017_BeforeOptimizing_2

#### Unity Font

https://stackoverflow.com/questions/54804550/unity-system-fonts-in-android

https://develop.hateblo.jp/entry/unity-font-script-change

http://tsubakit1.hateblo.jp/entry/2018/05/30/234314

https://qiita.com/tsubaki_t1/items/93e4b91b830729cd93a4

#### Noto CJK fonts

https://qiita.com/hamaguchitm/items/546d40b7777bd9242e78

https://note.com/25728121/n/nbf0c20b94850#qbeK6

https://github.com/googlefonts/noto-cjk

https://github.com/googlefonts/noto-cjk/tree/main/Sans

https://github.com/googlefonts/noto-cjk/tree/main/Sans/Mono

#### **C#**

https://itsakura.com/csharp

https://ufcpp.net/study/csharp/

https://note.dokeep.jp/post/csharp-enum-flags/

https://qiita.com/hadashiA/items/0c65a999422e7713ed3c

https://qiita.com/higty/items/306eebe988af39ba775d

https://zenn.dev/higty/articles/73b6b4a402b94e

#### Local Web Server

https://qiita.com/massie_g/items/2913066e596dae197539

https://qiita.com/shiena/items/d5bc13a97c4d06b53072