Qiitaの画像アップロード容量の上限になったんで 古い記事をまとめ直した。
半透明の赤い四角が 1フレームのずれもなく 手や足の先にかぶさっていることに着目してほしい。
Unityで2D格闘ゲームを作るときに、手の先だけ、足の先だけに当たり判定をつけるには どれが一番いい方法かは知らないが、次のような方法でも実現できる。
キャラクターのスプライト とは別に、 ヒットボックス(=上図で手や足に被っている半透明の赤い四角)のスプライト を用意する。
LateUpdate() もあるがカメラで使ってるし、とりあえずスプライトの移動は Update() に入れた。
じゃあ、スプライトのアニメに合わせて ヒットボックスの位置を
// ヒットボックスのスプライト
void Update()
{
// ~略~
hitboxTransform.Translate(
offsetX - hitboxTransform.position.x,
offsetY - hitboxTransform.position.y,
hitboxTransform.position.z
);
}
といった感じで スプライト・アニメに合わせればいい。
あと2つ、
- 今表示されているモーション
- 今表示されているフレーム
の2つを取得できれば あとはOKだろう。
今表示されているフレームの計算式を出す
まず、アニメーションを作成しよう。
弱パンチは この2フレームを使うとしよう。
3Dゲームではあまり考えられないタイムラインの使い方だが、
弱パンチを 2フレーム で繰り出されるようにしたいなら 単純に サンプル数も 2にして スプライトを 2枚並べよう。
3フレームにしたければ [Samples] を 3 にすればいい。
タイムラインは 画像を全部埋めなくても、モーションの先頭フレームだけ 置いておけばいい。
設定したパンチ・モーションは、その場で再生して確認できる。
今表示されているフレームの計算式
何フレーム目が表示されているかは、次の計算式で出せる。
// キャラクターのスプライト
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週目の半分なら 2.5、3週目の3分の2なら 3.6666... とかになる。ピッタリではなく思っているより端数は出る。
% 1.0f
は1で割った余りなので、0~0.999…に変換できる。数学的には 0.999... も 1 も、同じと見ていい。
frameRate は 1秒間に何フレーム表示されるかなんだが、[Samples]と同じと見ていい。2 とか 3。
ノーマライズドタイムが 3.8 とかなら リピート3回目で、2フレーム目、ぐらいのことが分かる。
これで 今何フレーム目が表示されているかは出せた。
だいたいでいいのなら、スピード調整もできる
Animator でステートをクリックすると、Inspectorビューで [Speed] を設定できる。
このスピードという数字は何なのか 結局分からないんだが、だいたい次のように使うことにした。
入れるSpeedを概算する式: Speed = 60 / Samples / 表示したいフレーム数
別に これでうまくいくわけでもないんだが、他にいい方法も見つからないので これで良しとすることにした。ドープシートのタイムラインには画像を隙間なくきっちり詰めている(つまりSamplesと画像の枚数が同じ)ものとして説明する。
(例1)弱パンチが画像2枚として、5フレームで表示したい場合。
60 / 2 / 5 = 6 (スピード 6)
(例2)中パンチが画像3枚として、7フレームで表示したい場合。
60 / 3 / 7 = 約 2.8571 (スピード 約2.8571)
(例3)強パンチが画像5枚として、9フレームで表示したい場合。
60 / 5 / 9 = 約 1.3333 (スピード 約1.3333)
(例4)ギブアップは画像4枚として、120フレームで表示したい場合。
60 / 4 / 120 = 約 0.125 (スピード 約0.125)
ステートを抜けるときは、Exit Time を 1、Duration を 0 にしておくと モーションをピタッと抜ける。
今表示されているモーションの名前の取得方法
例えば上図は Animatorビューでキャラクターのモーション遷移図を開いたところだが、黄色い丸で囲んだステートは Base Layer.SAtkHK だ。スタンド・ハード・キック、つまり立ち強キック。
ただし、Unity は「Base Layer.SAtkHK」という文字列を持っていない。文字列を ハッシュ という不可逆な数字に変換して持っている。
AnimatorStateInfo animeStateInfo = animator.GetCurrentAnimatorStateInfo(0);
// これがハッシュ
int hash = animeStateInfo.fullPathHash;
実行速度や メモリ容量の省エネのために ハッシュ はよく使われる手法だ。
そこでプログラムでは事前に
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");
をするなりして、検索テーブルを作っておくこと。
これで アニメーターのフル・パス・ハッシュを調べれば、
現在のステートが分かるので、そこに紐づくモーションと、
そのモーションの何フレーム目は どの位置にヒットボックスがあるか、といった情報を追いかければ、
Unity でアクションゲームを作るときに使えるだろう。