プレイヤーが移動する時に歩くアニメーションを実装しよう!
どうも!
プレイヤーが移動する時に歩いたり走ったりするアニメーションってつけたいですよね!
でもどうすればいいのかって結構分からなくないですか?
今回はそこについてまとめてみようと思います!
以下のように移動する方向によってアニメーションが変わる処理を実装していきます!
ではいきましょー!
STEP1:アニメーション用のスプライトを作成する
そもそもスプライトがないと話が始まりませんので
こちらを用意します。
私は信じられないほど絵を描くのがヘタクソなのですが
今回は自分で絵を描いてみました。
それが以下です。
ウマいでしょ?…あほか!
ということでこちらの絵については触れないでいただきたい!
股がちょっと白いのなんやねんとか言わないでいただきたい!笑
なにで絵を描いたかというとAsepriteというソフトで描きました。
こちらはSteamで2000円ぐらいで買えるのでよければ買ってみてください。
自分で描くのはなぁって方はアセットとしては以下が人気があってよさげでした
STEP2:UnityでスプライトをImportし1枚毎に切り抜く
上記の画像をUnity上に取り込みましょう。
手順は簡単、画像をドラッグ&ドロップするだけです。
これで画像をUnity上に取り込めました!
続いて取り込んだ画像が4枚で1枚になっているのでこれを1枚づつに切り分けてあげます。
とりあえずどれか一枚画像を左クリック→InspectorウィンドウのSprite ModeをMultipleに変更→Sprite Editorをクリックします。
するとSprite Editorウィンドウが表示されます。
今回は一つ一つが64px × 64pxになっているので
Grid by Cell Size(セルサイズ毎に切り取る)ってします。
Sliceをクリック→Typeを Grid by Cell Sizeに変更→Sliceをクリック
これで切り分けられました!
最後にApplyを押して保存します。
画像をAnimationにする
続いて今切り分けた画像をAnimationにします。
これも結構簡単です。
今切り分けた画像を全部選択してHierarchyウィンドウにドラッグ&ドロップします。これだけ
するとファイルの保存先を聞かれるのでいい場所を選んでください!
これでanimation自体は出来ました!
animationの再生速度を調整したい場合
何も設定を変えていないと以下の速度で再生されます。
この再生速度を変更したい場合はSample Rateを変更します。
まずはAnimationウィンドウを表示しましょう。
Sample Rateはデフォルトでは表示されていないので表示してあげます。
Animationウィンドウの右上の…をクリック→Show Sample Rateをクリックで表示されます。
するとSample Rateを変更できる項目が表示されました。
初期値では12という値が入っていますね。
この数字の意味は一秒に12回画像を表示しまっせ!ってことです。
つまりこの数値が高いほど1秒間に表示する画像の枚数が多くなり低いほど少なくなります。
例えばsample rateの値を4にして再生したのが以下です。
Sample Rateが4なので1秒間に4枚の画像が表示されます。
最初よりもゆっくり表示されるようになりましたね!
こんな感じの残りの画像も同様にアニメーション化していきましょう!
STEP3:スクリプトを作成する
続いてスクリプトを作成して移動時にアニメーションを変化させるようにしましょう。
こちらについては以下でダウンロードできるサンプルプロジェクトを参考にしています。
作成するスクリプトは以下2つです。
・プレイヤーの移動スクリプト
・プレイヤーの移動状況によってアニメーションを切り替えるスクリプト
とりあえずプレイヤーの移動についてはRigidbodyでの移動を用います。
なぜRigidbodyでの移動をするのかの理由については以下記事を参考にしていただければと思います。
移動スクリプト
まずは移動のスクリプトを以下に記載します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMoving : MonoBehaviour
{
public float movementSpeed = 5f;
public bool isMove;
Rigidbody2D rbody;
CharacterRenderer cr;
// Start is called before the first frame update
void Start()
{
rbody = GetComponent<Rigidbody2D>();
cr = GetComponentInChildren<CharacterRenderer>();
isMove = true;
}
// Update is called once per frame
void Update()
{
}
void FixedUpdate()
{
if (isMove)
{
Vector2 currentPos = rbody.position;
float horizontalInput = Input.GetAxis("Horizontal");
float verticalInput = Input.GetAxis("Vertical");
Vector2 inputVector = new Vector2(horizontalInput, verticalInput);
inputVector = Vector2.ClampMagnitude(inputVector, 1);
Vector2 movement = inputVector * movementSpeed;
Vector2 newPos = currentPos + movement * Time.fixedDeltaTime;
cr.SetDirection(movement);
rbody.MovePosition(newPos);
}
}
}
ここで分からないのは以下ですね。
inputVector = Vector2.ClampMagnitude(inputVector, 1);
Vector2.ClampMagnitudeは
ClampMagnitude (Vector2 vector, float maxLength);
の形式で記載します。
大きさをmaxLengthに制限したVectorを返す関数とのことです。
今回であればmaxLength = 1になっていますので
最大の大きさが1を超えないようなVectorを返却します。
つまりC#のコードで書くと
Math.Sqrt(Math.Pow(x ,2) + Math.Pow(y ,2)) <= 1
となるようなx,yの組み合わせをもつベクトルを返却します。
具体的には
inputVector = (1 , 0)
inputVector = (0.7071 , 0.7071)
inputVector = (0 , 1)
みたいなベクトルを返却するということですね。
で、この値をCharacterRendererというスクリプトにSetDirection関数に渡しています。
わざわざこんなことしているのはSetDirection関数で処理をしているからです。
こちらについては以下で記載します。
アニメーション切り替えスクリプト
こちらが本題です。
まずはスクリプト全体を以下に示します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterRenderer : MonoBehaviour
{
public static readonly string[] staticDirections = { "staticN",
"staticW", "staticS", "staticE"};
public static readonly string[] runDirections = { "back", "left", "front", "right" };
Animator animator;
int lastDirection;
private void Awake()
{
//cache the animator component
animator = GetComponent<Animator>();
}
public void SetDirection(Vector2 direction){
string[] directionArray = null;
if (direction.magnitude < .01f)
{
directionArray = staticDirections;
}
else
{
directionArray = runDirections;
lastDirection = DirectionToIndex(direction, 4);
}
animator.Play(directionArray[lastDirection]);
}
public static int DirectionToIndex(Vector2 dir, int sliceCount){
Vector2 normDir = dir.normalized;
float step = 360f / sliceCount;
float halfstep = step / 2;
float angle = Vector2.SignedAngle(Vector2.up, normDir);
angle += halfstep;
if (angle < 0){
angle += 360;
}
float stepCount = angle / step;
return Mathf.FloorToInt(stepCount);
}
}
順を追って説明しましょう!
最初にストリングの配列が二つ定義されていますね。
public static readonly string[] staticDirections = { “staticN”,
“staticW”, “staticS”, “staticE”};
public static readonly string[] runDirections = { “back”, “left”, “front”, “right” };
これは再生するアニメーションの名前が記載された配列になっています。
staticDirections:静止時に再生されるアニメーション
runDirections:移動時に再生されるアニメーション
ちょっとわかりにくくなっていますが
SetDirectionの最後でanimator.Play(directionArray[lastDirection]);
となっておりここで上記のいずれかのアニメーションを再生しています。
で、まず移動スクリプトからはSetDirection関数を呼び出していますね。
SetDirection関数ではまず引数のdirectionの大きさによって処理を分岐しています。
このときdirection.magnitude < .01fで分岐しています。
direction.magnitudeはベクトルのスカラー値(大きさ)です。
あんまり専門的な話をしたくないので「スカラー」って言葉は気にしないでいただきたいです。
「スカラー」って言葉を出すことによって
俺数学知ってるんだぜ?かっこいいだろ?
ってかっこつけたいアホなおじさんだということで認識していただければと思います…。
direction.magnitudeは移動方向の大きさを示しており、その値が設定した値より小さい場合は
静止のアニメーションを表示するようにするということです。
今回であれば移動方向の大きさが0.01f以下であれば静止のアニメーションを表示します。
そうでなければ移動のアニメーションを表示するようにします。
で、移動時の分岐処理として
lastDirection = DirectionToIndex(direction, 4);
という部分がありますね。
ここは何をしているかというと
DirectionToIndex(direction, 4)
という関数によってどのアニメーションを再生するかを判定させているんです。
ここがアニメーション処理の肝の部分で、かつ一番わかりにくいところです。
とりあえずDirectionToIndexの全体を以下に記載します。
public static int DirectionToIndex(Vector2 dir, int sliceCount){
Vector2 normDir = dir.normalized;
float step = 360f / sliceCount;
float halfstep = step / 2;
float angle = Vector2.SignedAngle(Vector2.up, normDir);
angle += halfstep;
if (angle < 0){
angle += 360;
}
float stepCount = angle / step;
return Mathf.FloorToInt(stepCount);
}
そもそも一行目から分かりずらいですよね。
Vector2 normDir = dir.normalized;
normalizedってなんだよって話ですがnormalizedは正規化です。
正規化とはどんな大きさになっていようとも大きさを1にすることです。
なぜそうするかというと理由は簡単で、そうすると後々の計算が分かりやすいからです。
例えば12 × 15っていう計算結果パッと思いつけなくないですか?
それに比べると0.8 × 1( = 12/15 × 15/15)っていう計算は一瞬で出来ますよね。
こんな感じで分かりやすい計算をするために正規化したりします。
意味わかんねーよ!説明下手くそだな!って思っている方は正にごもっともなんですが
とりあえずそういうもんなんだなって思ってもらえばいいかと思います。
続きまして以下です。
float step = 360f / sliceCount;
stepという変数の値を計算しています。
これはアニメーションを変える角度を計算しているんです。
今回上・右・下・左の四方向のアニメーションを作成しました。
例えば上に歩くアニメーションはどの方向からどの方向までの移動時に表示するか?
その角度を定義しているんです。
例えば今回は4方向に分けるので
step = 360 / 4 = 90になります。
ということで90度毎にアニメーションを変えます。
次にその下の処理なのですが以下で一つの処理としてみてください。
float halfstep = step / 2;
float angle = Vector2.SignedAngle(Vector2.up, normDir);
angle += halfstep;
まずVector2.SignedAngleはfromからtoへの角度を返却します。
そこにhalfstepを加算しています。
なぜhalfstepを加算しないといけないかということです。
Vector2.SignedAngle(Vector2.up,normDir)
でfromのベクトルがVector2.upなので(0 ,1)、つまりy軸上からnormDirまでの角度はどうなっているか計算しています。
本来であれば以下の時に表示するアニメーションを変化させたいです。
316度から45度の間であれば「上」のアニメーションを表示
46度から135度の間であれば「右」のアニメーションを表示
136度から225度の間であれば「下」のアニメーションを表示
226度から315度の間であれば「左」のアニメーションを表示
しかし今回はVector2.SignedAngleですので0度から始まります。
ということで何も考えずhalfstepを加算しないと0度から90度の場合に「上」のアニメーションを表示するようになります。
つまり本来表示したいアニメーションの角度範囲とhalfstep分だけズレていることになります。
今回であればhalfstep = 90 / 2 = 45ですので本来表示してほしい角度と45度の差があるわけです。
このズレを補正するためにhalfstepを加算しているという訳です。
なんとなくわかったでしょうか。…わかんないですよね、すいません 笑
自分でも説明がヘタなのは重々分かっています、勉強あるのみですね!
最後にアニメーションする配列のどれを表示するかを決めるために
Mathf.FloorToInt(angle / step);
の処理を行っています。
例えばangle = 80の場合
80 ÷ 90 = 0.88…になります。
これを整数で切り捨て(Mathf.FloorToInt)にするので
Mathf.FloorToInt(0.88) = 0
になります。
ということで最終的にrunDirections[0]のアニメーションが再生されるというわけです。
上記により逆に配列に格納するアニメーションの順番は決まってきます。
0番目:上に進むアニメーション名
1番目:右に進むアニメーション名
2番目:下に進むアニメーション名
3番目:左に進むアニメーション名
プレイヤーとアニメーションの設定
スクリプトも作成できたので最後にプレイヤーの設定を行いましょう。
まず親子でゲームオブジェクトを作成します。
そして以下をアタッチします。
・親オブジェクト(上記画像のPlayer)
PlayerMoving
Rigidbody2D
・子オブジェクト(上記画像のPlayerAnimation)
Animator
CharacterRenderer
最後にAnimatorの設定です。
Animatorウィンドウに最初に作成した
Animationをドラッグアンドドロップしています。
完成!実行してみよう
やっと一通り作業が終わりました!長かったですねー!
最後に実行してみましょう。
ちゃんとそれぞれの方向に移動するときに
アニメーションが再生されているのが分かるかと思います。