Unityで２Ｄ格闘ゲームを作るときに欲しいと思われる、アニメのフレームと当たり判定の合わせ方

Qiitaの画像アップロード容量の上限になったんで　古い記事をまとめ直した。


![201701190426gif22.gif](https://qiita-image-store.s3.amazonaws.com/0/159417/74ce2eb0-6b8d-6d79-95a6-3aeed280ea8b.gif)
半透明の赤い四角が　１フレームのずれもなく　手や足の先にかぶさっていることに着目してほしい。

Unityで2D格闘ゲームを作るときに、手の先だけ、足の先だけに当たり判定をつけるには　どれが一番いい方法かは知らないが、次のような方法でも実現できる。

キャラクターのスプライト　とは別に、　ヒットボックス（＝上図で手や足に被っている半透明の赤い四角）のスプライト　を用意する。

![201701182222a29.png](https://qiita-image-store.s3.amazonaws.com/0/159417/a7376b1c-642a-b3b6-5ecd-94358424c546.png)

LateUpdate() もあるがカメラで使ってるし、とりあえずスプライトの移動は Update() に入れた。

じゃあ、スプライトのアニメに合わせて　ヒットボックスの位置を



```C#
// ヒットボックスのスプライト

void Update()
{
    // ～略～
    hitboxTransform.Translate(
        offsetX - hitboxTransform.position.x,
        offsetY - hitboxTransform.position.y, 
                  hitboxTransform.position.z
    );
}
```

といった感じで　スプライト・アニメに合わせればいい。

あと２つ、

- 今表示されているモーション
- 今表示されているフレーム


の２つを取得できれば　あとはＯＫだろう。


# 今表示されているフレームの計算式を出す

まず、アニメーションを作成しよう。

![201701160449a2.png](https://qiita-image-store.s3.amazonaws.com/0/159417/e3610541-7300-99d9-8c76-dda27042e4d5.png)

弱パンチは　この２フレームを使うとしよう。

![201701160449a4.png](https://qiita-image-store.s3.amazonaws.com/0/159417/294ab175-9347-19d9-fb07-971310787314.png)

３Ｄゲームではあまり考えられないタイムラインの使い方だが、

弱パンチを　２フレーム　で繰り出されるようにしたいなら　単純に　サンプル数も　２にして　スプライトを　２枚並べよう。

３フレームにしたければ [Samples] を 3 にすればいい。
タイムラインは　画像を全部埋めなくても、モーションの先頭フレームだけ　置いておけばいい。

![201701160058gif8.gif](https://qiita-image-store.s3.amazonaws.com/0/159417/3ad76e43-6cd8-4278-760d-d774db34e257.gif)

設定したパンチ・モーションは、その場で再生して確認できる。

## 今表示されているフレームの計算式

何フレーム目が表示されているかは、次の計算式で出せる。

```C#
// キャラクターのスプライト

using UnityEngine;
// 中略

    void Update()
    {
        // 中略

        // animator は GetComponent<Animator>(); で取得しておく

        AnimationClip clip = animator.GetCurrentAnimatorClipInfo(0)[0].clip;
        AnimatorStateInfo animeStateInfo = animator.GetCurrentAnimatorStateInfo(0);
        float normalizedTime = animeStateInfo.normalizedTime;

        // これが、今表示されているフレームの計算式
        int currentFrame = Mathf.FloorToInt((normalizedTime % 1.0f) * clip.frameRate);
    }
```

ノーマライズド・タイムというのが　今表示しているフレームなんだが、単位はノーマライズド、つまりアニメーションのリピート1回を 0～1 に直したものだ。
リピート２週目の半分なら 2.5、３週目の３分の２なら 3.6666... とかになる。ピッタリではなく思っているより端数は出る。

`% 1.0f` は１で割った余りなので、0～0.999…に変換できる。数学的には 0.999... も 1 も、同じと見ていい。

frameRate は 1秒間に何フレーム表示されるかなんだが、[Samples]と同じと見ていい。2 とか 3。

ノーマライズドタイムが 3.8 とかなら リピート３回目で、２フレーム目、ぐらいのことが分かる。
これで　今何フレーム目が表示されているかは出せた。

# だいたいでいいのなら、スピード調整もできる

![201701160449a8.png](https://qiita-image-store.s3.amazonaws.com/0/159417/62c691a7-0f8d-c1bb-1b3b-da5a469b60c3.png)

Animator でステートをクリックすると、Inspectorビューで [Speed] を設定できる。
このスピードという数字は何なのか　結局分からないんだが、だいたい次のように使うことにした。

**入れるSpeedを概算する式：　Speed = 60 / Samples / 表示したいフレーム数**

別に　これでうまくいくわけでもないんだが、他にいい方法も見つからないので　これで良しとすることにした。ドープシートのタイムラインには画像を隙間なくきっちり詰めている（つまりSamplesと画像の枚数が同じ）ものとして説明する。

（例１）弱パンチが画像２枚として、5フレームで表示したい場合。

60 / 2 / 5 = 6 (スピード 6)

（例２）中パンチが画像３枚として、7フレームで表示したい場合。

60 / 3 / 7 = 約 2.8571 (スピード 約2.8571)

（例３）強パンチが画像５枚として、9フレームで表示したい場合。

60 / 5 / 9 = 約 1.3333 (スピード 約1.3333)

（例４）ギブアップは画像４枚として、１２０フレームで表示したい場合。

60 / 4 / 120 = 約 0.125 (スピード 約0.125)

![201701160449a10.png](https://qiita-image-store.s3.amazonaws.com/0/159417/27ba83a7-0fce-f07e-ac31-062f21950613.png)

ステートを抜けるときは、Exit Time を 1、Duration を 0 にしておくと　モーションをピタッと抜ける。


# 今表示されているモーションの名前の取得方法

![201701182222a34.png](https://qiita-image-store.s3.amazonaws.com/0/159417/c272e672-1419-11d5-b7df-19c9d5204c7e.png)

例えば上図は Animatorビューでキャラクターのモーション遷移図を開いたところだが、黄色い丸で囲んだステートは Base Layer.SAtkHK だ。スタンド・ハード・キック、つまり立ち強キック。

ただし、Unity は「Base Layer.SAtkHK」という文字列を持っていない。文字列を **ハッシュ** という不可逆な数字に変換して持っている。

```C#
    AnimatorStateInfo animeStateInfo = animator.GetCurrentAnimatorStateInfo(0);

    // これがハッシュ
    int hash = animeStateInfo.fullPathHash;
```

実行速度や　メモリ容量の省エネのために　ハッシュ　はよく使われる手法だ。

そこでプログラムでは事前に

```C#
using System.Collections.Generic;
using UnityEngine;
// ～中略

    Dictionary<int, string> hashToName = new Dictionary<int, string>();

    // 自分でハッシュを作る方法
    int hash = Animator.StringToHash( "Base Layer.SAtkHK");
    hashToName.Add( hash, "Base Layer.SAtkHK");
```

をするなりして、検索テーブルを作っておくこと。

これで　アニメーターのフル・パス・ハッシュを調べれば、
現在のステートが分かるので、そこに紐づくモーションと、
そのモーションの何フレーム目は　どの位置にヒットボックスがあるか、といった情報を追いかければ、

ヒットボックスの位置を　アニメに合わせることができる。
![201701190426gif22.gif](https://qiita-image-store.s3.amazonaws.com/0/159417/74ce2eb0-6b8d-6d79-95a6-3aeed280ea8b.gif)

Unity でアクションゲームを作るときに使えるだろう。