[UNITY][Ruby’s Adventure]#4 アニメーションの作成と適用

前回までの記事

この記事はUNITYの公式チュートリアル『Ruby's Adventure』を日本語で解説している記事です。

↓↓記事一覧↓↓

#1 UNITY導入~キャラクター移動

#2 タイルマップ~物理演算

#3 HP設定~回復とダメージ~敵の配置

#4 アニメーションの設定と適用 ←いまここ

#5 攻撃とカメラ追従

#6 パーティクルとUI(HPゲージ)

#7 会話ダイアログ~音声~ビルド

(番外編)うまく動かない場合の対処法

敵にアニメーションを適用しよう

実際のチュートリアルページはこちら

アニメーションを適用させる準備をしよう

さて、いよいよキャラクターにアニメーションを付けていきます。これで一気にゲームっぽくなりますね。

まずは敵のオブジェクトをHierarchyウインドウからProjectウインドウ内のPrefabフォルダにドラッグ&ドロップしてPrefab化してください。

次にそのPrefabを選択した状態でInspectorウインドウからコンポーネント「Animator」を追加してください(AnimationではなくAnimatorです)。

これですね。

次に、Projectウインドウ内のAnimationフォルダを開きそのなかで右クリック→Create→Animator Controllerを作成し、「Robot」と名付けてください。これがアニメーションのファイルになります。

作成したら、敵のPrefabを選択した状態で、いま作ったAnimator ControllerファイルをInspectorウインドウのControllerに貼り付けてください。

ここですね。これでアニメーションが適用されました。

あ、Prefabではなくオブジェクトに貼り付けた場合はOverrides→Apply Allを押さないとPrefabに保存されないので忘れないでくださいね。

アニメーションを作ろう

次に、いま適用させたアニメーションの内容を作っていきます。

UNITY画面上部のWindow→Animation→Animationと選択し、アニメーションを作るウインドウを表示させます。Animatorと間違えやすいので注意です。

ウインドウを表示させた状態で敵のPrefabを選択すると、↓のようにCreateボタンが現れるのでクリックします。

ボタンを押すとアニメーションファイルの保存先をきかれるので、「RobotLeft」という名付けてAnimationフォルダに保存してください。

すると、

こんな画面になります。左が作成したアニメーションファイルのリスト、右側がアニメーションを作成する枠です。

さて、本来はここでパラパラ漫画のように複数の画像を準備してアニメーションを作るのですが、このチュートリアルでは既にアニメーションデータが用意されています。

Projectウインドウ内のSpritesフォルダ→Charactersフォルダのなかの「MrClockworkSheet.png」というファイルを探してください。

このファイルの▶ボタンを押すと、すでに画像ファイルが分割されていることがわかります。

タイルマップを作成したときと同じですね。

自分で2Dゲームを開発をする際、アニメーションを作るときは画像ファイルを複数作成してもOKです。
では、なぜこのチュートリアルでは「一つの画像をUNITY内で分割」しているのかというと、このほうが処理が軽いのです。
複数の画像を用意するとその画像を全て読み込む必要がありますが、一つの画像を分割すれば、読み込む画像も一つで済むので処理が軽くなるわけですね。

さて、この分割画像のなかから敵が左向きに歩いている画像4つを探し出し、Shiftキーを押しながら4つを選択し、Animationウインドウの右枠にドラッグ&ドロップしてください。

↑この4つですね。1つめの画像を選択して、Shiftキーを押しながら4つめの画像をクリックすれば複数選択できます。

この状態でAnimationウインドウの▶ボタンを押してみると、

あら、動いてくれたのはいいですが、少し動きが速すぎますね…。

これはアニメーションの1秒間に描画する枚数(Sample Rate)の初期値が「60」に設定されているためです。1秒間に60枚なので、1コマ0.016秒しか表示されていないことになります。

そして、Sample Rateはデフォルトで非表示になっています。

編集するには、↓の画像のようにAnimationウインドウ右上の「…ボタン」→「Show Sample Rate」をクリックしましょう。すると、Animationウインドウの左側にSample Rateの現在値が表示されます。

この「60」を「4」に変更してEnterキーを押し、1コマを1/4秒間描画されるように調整してみてください。

Enterキーを押さないと適用されないので注意です。

すると、

…まあ、なんか遅すぎる感じもしますが、こんなもんでしょう。

一つのオブジェクト内で複数のアニメーションを作成しよう

さて、敵のオブジェクトに左向きに歩くアニメーションを適用できたので、右や上下に歩くアニメーションも作りましょう。

Animationウインドウ左上のCreate New Clipsを選択し、Animationフォルダを選択したうえで「RobotRight」と名付けたアニメーションファイルを作成しましょう。

↑こんな感じです。

Sample Rateを「4」に変更するのを忘れずに。

 

さて、次はさきほどと同じように、事前に用意された右に歩く画像をアニメーションに適用させて…

…と思ったら、右向きに歩くアニメーションは用意されていません。

各方向に歩くアニメーションを用意しても良いのですが、この敵はデザインが左右対称です。

なので、左向きに歩くアニメーションを左右反転させることで右向きに歩くアニメーションを作ってみましょう。そのほうが処理も軽くなりそうですね。

Animationウインドウの左上が「RobotRight」になっていることを確認し、「Add Property」をクリック→「Sprite Renderer」内の「Flip X」の+ボタンをクリックしてください。

↑こんな感じです。

できたら、そのウインドウを一度確認してください。↓のように、「Sprite Renderer.Flip X」の行が追加され、右枠にひし形が二つ追加されていることがわかると思います。

これは「Flip X」が適用される視点と終点を表しています。位置をみてみるとアニメーションの最初と最後にひし形があるので、このアニメーション全体に適用されるようになっています。

この状態で、「始点のひし形をクリック」→「Flip Xの右側のチェックボックスにチェックを入れる」ということをすると、始点のX軸が反転します。

同じく「終点のひし形をクリック」→「Flip Xの右側のチェックボックスにチェックを入れる」ということをすると、終点のX軸が反転します。

以上により、アニメーション全体のX軸が反転されました。少々解りにくいので、画像を見ながら試してみてください。

 

終わったら、上方向のアニメーションと下方向のアニメーションを、それぞれ「RobotUp」「RobotDown」として追加しましょう。

アニメーションに方向を割り当てよう

さて、今度は敵の上下左右の動きにそれぞれのアニメーションを割り当てましょう。

まずはWindow→Animation→Animatorと選択し、アニメーション全体を管理するウインドウを表示させます。

↑こんな画面になったかと思います。

で、ここからスクリプトをいじったりとか色々すれば移動方向とアニメーションを紐付けることができるのですが、チュートリアルでは「Blend Tree」という機能を使って紐付けているようです。せっかくなので挑戦してみましょう。

まず、いま見ているBase Layer画面に「RobotLeft」などの作成したアニメーションの名前が並んでいる(これをノードといいます)ので、このノードを選択した状態でDeleteキーを押して、4つとも削除してください(Exit、Any State、Entryの3つは消さないでください)。

↑こんな感じです。

次に、同じウインドウの右側の枠内の空いてるところを右クリック→Create State→「From New Blend Tree」をクリックして、Blend Treeを一つ作成してください。

で、出てきた「Blend Tree」というノードをダブルクリックすると、↓こんな画面になります。

で、このノードをさらにクリックすると、Inspectorウインドウが↓こんなふうになります。

次に、このInspectorウインドウのBlend Typeを「2D Simple Directional」に変更します。

続いて、Animationウインドウの左側の枠の上部で「Parameters」タブに切り替え、+ボタンを押して、「Float」をクリックします。

↑ここです。

すると、↓こんな感じでパラメーターが追加されるので、

↑「Move X」と名付けます。

同じ手順でもう一つパラメーターを追加し、「Move Y」と名付けます。

次に、Inspectorウインドウの項目Parametersの「Blend」の横の▼マークを押してください。

押すといま追加した「Move X」と「Move Y」が選択肢に追加されていますので、それぞれ「Move X」と「Move Y」に変更してください。

次に、同じくInspectorウインドウのMotionという枠の右下にある+ボタンを押して、「Add Motion Field」をクリックします。

すると、アニメーションを入れるための方向軸(Motion)が生成されます。

同じ事をあと3回繰り返して、Motionを全部で4つにしてください。

↑こんな感じです。

で、追加されたMotionの↓この部分

↑この◎ボタンを押すと作成したアニメーションクリップの一覧が現れるので、それぞれ上から順番に「RobotLeft」「RobotRight」「RobotUp」「RobotDown」を割り当て、「Pos X」と「PosY」は上から順番に「-0.5, 0」「0.5, 0」「0, 0.5」「0, -0.5」に変更してください。

↑こんな風になったと思います(色はちょっと違うと思いますが)。

さて、お解りのとおり、この「Pos」は「Position」という意味です。

つまり、「Blend Tree」は移動方向によってアニメーションを自動判別して切り替えてくれる機能なのですね。

 

というわけで、「どの方向が入力される」と「どのアニメーションと判定される」のか、一度確認してみましょう。

Inspectorウインドウの下のほうにある「Blend Tree」という箇所をクリックしてください。

↑ここです。

するとアニメーションが表示されるので、赤い点をドラッグしてみてください。

↑赤い点は入力方向です。どの方向に動くとどのアニメーションが判定されるのかはこうやって確認できますね。

これで、アニメーションに「方向」を割り当てることに成功したので、次は「移動」に割り当てましょう。

アニメーションに移動を適用しよう

さて、移動にアニメーションを割り当てるには、スクリプトを編集する必要があります。

EnemyControllerスクリプトを開き、Start()の上あたりから次のように書き加えてください。

Animator animator;                               //←ここ

void Start()
{
    rigidbody2D = GetComponent<Rigidbody2D>();
    timer = changeTime;
    animator = GetComponent<Animator>();         //←ここ
}

//が追加箇所です。

Animator animator;」で、コンポーネントAnimatorを取得するための変数を定義しています。

で、「animator = GetComponent<Animator>();」でコンポーネントを取得しています。これでコンポーネント取得は完了です。

次に、FixedUpdate()内のif文を次のように書き加えてください。

    if (vertical)
    {
        position.y = position.y + Time.deltaTime * speed * direction;
        animator.SetFloat("Move X", 0);            //←ここ
        animator.SetFloat("Move Y", direction);    //←ここ
    }
    else
    {
        position.x = position.x + Time.deltaTime * speed * direction;
        animator.SetFloat("Move X", direction);    //←ここ
        animator.SetFloat("Move Y", 0);            //←ここ
    }

まず、垂直方向に動く処理を記述している「if(vertical)」内に「animator.SetFloat("Move X", 0);」とあります。

animator.SetFloat」は「コンポーネントAnimatorのなかに値を入れる(SetFloat)」という意味です。AnimatorのParametersは「float」で作りましたよね。その時はこの「SetFloat」というコードを使って値を入れることができます。

("Move X", 0)」は「Move Xには0を入れるよ」という意味です。SetFloatの括弧()内は「(パラメーター名,値)」のように書きます。ここの処理は垂直方向に動くコードを記述してるので、"Move X"=x軸の方向は0ですね。

で、次の行には「("Move Y", direction)」とあるので、「Move Yには変数directionを入れるよ」という記述になってます。変数directionは移動方向を制御するための変数で、移動時間によって1または-1が入っていましたね。

つまり、「上方向に動いているときは変数directionが1になる」=「Move Yに1が入る」=「上方向のアニメーションが再生される」というわけです。

で、水平方向に動く処理を記述している「else」内に内に逆のコードを追加しているので、全てのアニメーションが再生されるようになりました。

 

以上で敵の設定は終わりです! 長かった! お疲れ様でした

主人公にアニメーションを適用しよう

RubyにAnimatorを適用させよう

いやー今回は長いですね。

次はRubyのアニメーションです。あ、Rubyは「Blend Tree」を使わない方法で設定していきます。

まずはRubyのPrefabを選択し、InspectorウインドウでコンポーネントAnimatorを追加、項目ControllerにProjectウインドウ内のAnimationフォルダにあるAnimator Controllerファイル「Ruby」をアタッチしてください。

で、Animatorウインドウを見るとこんな風になってます。

ウインドウの左側を見ると、「Speed」「Look X」「Look Y」「Hit」「Launch」という5種類のパラメーターが用意されているようです。また、「Hit」と「Launch」は数値ではなくボタンが付いているのでTigger型で作られています。

Trriger型」なんて言っていますが、C#スクリプトにおいて「Trigger」という型は存在しません。「Trigger」はあくまでもUNITYのアニメーションパラメーターの種類の名前です。

アニメーションパラメーターの選択肢には他に「Bool型」があり、これはC#スクリプトにおける「bool型」で、「trueかfalseか」で判定するフラグです。スクリプト上で「フラグ」を作るには基本的に「bool型」を使って、その変数を「true」にするか「false」にするかでフラグをON/OFFします。

ではUNITYのアニメーションパラメーターにおける「Bool型」と「Trigger型」の違いはなんでしょうか?

Bool型」は、スクリプトから呼び出すときに「true」「false」といういずれかの命令を送る必要があり、「trueになっている間はずっとtrue」です。

対して「Trigger型」は、「そのパラメーター名をUNITYに送れば発動する」という特徴があり、「true」などと書かなくても発動します。また、最大の特徴は「一度発動すると自動的にリセットされる」という動き。アニメーションは「一回だけ再生する」という処理が多いので、このようなパラメーターをUNITY側で用意しているわけですね。

次に、ウインドウの右側を見ると、「Idle(待機時)」「Moving(移動時)」「Hit(被ダメージ時)」「Launch(攻撃時)」という4種類のアニメーションノードが用意されているようです。

白い矢印は「Transition偏移)」で、「矢印の方向に偏移する条件を指定する」という意味です。

被ダメージ中は移動しても被ダメージアニメを一定時間再生し続けてほしいので、HitからMovingに向かう矢印がない=Hitアニメーション再生中にMovingに偏移する条件はない、という感じですね。

試しに「Moving」から「Idle」に繋がる下向きの矢印をクリックして、Inspectorウインドウで詳細を見てみましょう。

この画面の詳細についてはこちらの記事が詳しいのですが、ほとんどが3Dゲーム開発時に必要となる項目です。

要点のみ説明すると、まず確認しなければいけないのは「Has Exit Timeのチェックが外れている、という部分です。

Has Exit Time」は、簡単にいうと「無効にすると、すぐに次のアニメーションへの偏移を始める」という項目です。OFFにすることが多い項目なので覚えておきましょう。

続いて、少し下にある「Conditions」の項目を見てください。「Speed」「Less」「0.1」とあります。「Speed」はこのAnimator Controllerファイルに事前に準備されたコンディション名です。

これは右下の+ボタンで追加したり-ボタンで削除したりできるのですが、今の状態はRubyの移動速度(Speed)が0.1未満(Less)になると「Idle(待機時アニメーション)」になる、という設定になっています。ということは、この「Speed」に移動速度を割り当てる必要があるのですが、それは後でスクリプトでやります。

 

では、次に「Moving」から「Hit」へのTransition(矢印)を見てみましょう。

Condisionsに「Hit」というパラメーターのみが設定された項目があります。先ほど説明したとおり、パラメーター「Hit」はTrigger型で用意されていたので、このように設定するだけで「Hitが命令された時に再生」という条件になります。「Speed」の時と同じく、この「Hit」にHP減少トリガーを割り当てる必要があるのですが、それは後でスクリプトでやります。

 

Rubyのアニメーターに移動を適用しよう

では、いま確認したアニメーターに移動を適用してみましょう。

RubyControllerスクリプトを開き、Start()の上あたりに次のように書き加えてください。

Animator animator;
Vector2 lookDirection = new Vector2(1,0);

void Start()
{
    animator = GetComponent<Animator>();

変数とコンポーネント取得は先ほどと同じですが、「Vector2 lookDirection = new Vector2(1,0);」という部分が違いますね。

lookDirection」はその名の通り、Rubyが見ている方向を入れるための変数です。

Vector2(1,0)」は方向ですね。「(x軸)1,(y軸)0」なので、初期値に「右を向いている」状態を入れています。

new」はちょっと複雑な話になりますが…まあ、「変数にVector2を入れるときにはnewと書かないとダメ」くらいの認識でOKです。

 

続いて、Update()内の「if (isInvincible)」よりも上を次のように書き加えてください。

    horizontal = Input.GetAxis("Horizontal");
    vertical = Input.GetAxis("Vertical");
    Vector2 move = new Vector2(horizontal, vertical); //ここから

    if (!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f))
    {
        lookDirection.Set(move.x, move.y);
        lookDirection.Normalize();
    }

    animator.SetFloat("Look X", lookDirection.x);
    animator.SetFloat("Look Y", lookDirection.y);
    animator.SetFloat("Speed" , move.magnitude);     //ここまで

Vector2 move = new Vector2(horizontal, vertical);」は、Rubyの「移動方向」を取得するための変数moveを定義して、キー入力を入れています。

例えば「」「移動にキー入力を直接入れる」こともできますが、このように変数を定義してそのなかにキー入力を入れておけば、この変数をキー入力として使えるわけです。

次に「if (!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f))」ですが、

まず「!」は「否定そうでない)」を意味する論理演算子です。

Mathf.Approximately」は、「二つの数値(小数点を含む)を比較して、近似値を返す」コードです。これは「if文」で、「!」が付いているので、「move.x=x軸の移動方向が0じゃないとき」という条件を表しています。

ちなみに、単純に「AがBじゃないとき」を判定する場合は「A != B」という書き方が可能です

では、なぜ簡単に「move.x != 0.0f」と書かず、「Mathf.Approximately」なんていう小難しい計算式を使うのでしょうか?

実は、一致しているかどうかの比較対象がfloat数値(小数点を含む数値)の場合、UNITY(というかC#?)ではなぜか正常に機能しません。

なので、if文などでfloat数値を比較する場合は必ず「Mathf.Approximately」を使いましょう

次に「||」は「かつ」という意味です。

つまり、このif文は「x軸の移動方向が0じゃなく、かつy軸の移動方向が0じゃないとき」=「Rubyが移動しているとき」に処理するコードを書くメソッドです。

 

続いて、その内容である「lookDirection.Set(move.x, move.y);」は「Rubyが見ている方向lookDirection)にRubyの移動方向move.x, move.yを入れるよSet)」という意味です。

で、先ほど説明したとおり、これは「Rubyが移動しているとき」に処理されます。

つまり、「Rubyが移動しているとき、変数lookDirectionに移動方向を入れるよ」という意味になります。これでようやく、「移動方向見ている方向」になったわけですね。

lookDirection.Normalize();」ですが、「Normalize」は正規化という意味なんですが、「Normalize0でない値を1(または-1)にするコード」と覚えていいと思います。lookDirectionは「見ている方向」であることから、値の大小は関係ないので、きれいな数字に整えてるだけです。

方向(ベクトル)を取得する」という時には割と「Normalizeで値を1に整える」ということをすることが多いです。

例えば今回の例でいうと、「lookDirection(見てる方向)」には(move.x, move.y)、つまりx軸とy軸の値が入っていますよね。
例えば、実際の数値が(0, -2)だったすると「y軸がマイナス」なので「下」を向いています。
この数値が(0, -99999)という値でも、方向は「y軸がマイナス」なので「下」です。
このように、同じ「下を向いている状態」でも数値が異なってしまうと、別の場所から呼び出した時に想定外の数値を取得してバグが起きたりします
でも、Normalizeをかけて「方向の値は必ず-1、0、1のどれか」と決めてしまえば、別の場所から呼び出すときにも使いやすいし、見やすいし、なにかと安心安全な数値になるわけですね。

次の「animator.SetFloat("Look X", lookDirection.x);」は、コンポーネントAnimatorのParameters「Look X」に見ている方向のx軸を入れている、つまりアニメーション再生のための値を入れているわけですね。

あ、ここの「Look X」「Look Y」は事前に用意されていた「RubyのAnimator Controllerファイル」に入っていたパラメーター名です。解りやすいように、敵のアニメーションパラメーターと同じ名前で事前に設定してくれてるわけですね。

では、「animator.SetFloat("Speed" , move.magnitude);」はなんでしょう? これは、ちょっと前に説明した↓の処理のためです。

ちょっと前に「Rubyの移動速度(Speed)が0.1未満(Less)になると「Idle(待機時アニメーション)」になる、という設定になっています。ということは、この「Speed」に移動速度を割り当てる必要があるのですが、それは後でスクリプトでやります。」と説明しましたが、それが「animator.SetFloat("Speed" , move.magnitude);」です。

で、「magnitude」は(直訳すると「大きさ」ですが)UNITYでは「(方向の)長さ」という意味です。「move」にはキー入力が入っているので、「キー入力が0.1以上入っているときは待機アニメではなく移動アニメが再生される」という処理をするために「Speed」に「コンポーネントAnimator(animator)のなかに(ドット.)作っておいたパラメーター"Speed"に、キー入力(move)のなかの(ドット.)長さ(magnitude)を入れている(SetFloat)」わけですね。

ちょっとややこしいですが、分解して一つ一つ見てみれば理解できますね。

 

続いて、被ダメージ時のアニメーション再生のために、ChangeHealth()内を次のように書き加えてください。

    animator.SetTrigger("Hit");  //←これ
    isInvincible = true;
    invincibleTimer = timeInvincible;animator.SetTrigger("Hit");

SetTrigger("Hit")」と書くと、Trigger型で作られたパラメーター"Hit"を処理することができます。

これも「Speed」と同じく、ちょっと前に説明した「「Hit」にHP減少トリガーを割り当てる必要があるのですが、それは後でスクリプトでやります。」ということをやっているわけですね。

以上で、Rubyがアニメーション付きで動くようになりました。

うまく動かなかったかたはこちらの記事を参照してください。

 

…以上です!

いやーーー今回は長かったですね。大変お疲れ様でした。

 

次の記事はこちらです。

#5 攻撃とカメラ追従

 

最終的なスクリプトの内容

こんな感じになります。