PR
スポンサーリンク

[Unity]アニメーションの作り方⑨ブレンドツリーで2Dアニメーションを作ろう!

Unity
スポンサーリンク

このブログで使用しているUnityのVer
・Unity 6000.0.33f1

えきふる
えきふる

おっす!記事の更新をもっと早くしたい!えきふるです!

以前の記事でUnityのアニメーション機能の1つ「BlendTree」に関してお話ししました。
BrendTreeは名前の通りアニメーションクリップ同士のブレンド具合を多次元的にコントロールして新たなアニメーションを作成する、ようは歩きと走りのアニメーションから早歩きを自然に繋げて生成するような機能でした。

でもこの説明を聞いてこう思った人も多かったんじゃないでしょうか。

いや、俺(私、僕、拙者、某、おいら、朕)は2D画像でゲーム作ってるから関係無いし。
3Dみたいに途中の絵とかないからブレンド出来ないし

・・・と。

確かに、アニメのように絵と絵の間が無いのでブレンドはできないかも知れません。
でもね、2Dでもブレンドツリーの面白い使い方が色々あるんです!

そこで今回はブレンドツリーの2Dでの使い方を見ていきましょう!
こんな使い方↓ができますよ。

サンプル1
サンプル2

ブレンドツリーの基本的な使い方が不安な方は下記から見てみて下さいね。

スポンサーリンク

ブレンドツリーでお手軽!?2Dアニメーションの作り方

それでは、実際に作りながら実例を見ていきましょう!

基本的な部分はブレンドツリーの基本と一緒なのですぐわかると思います。

RPG風4方向2Dアニメーションの使用例

まず、アニメーションを作る前に各方向の歩きモーションがある画像素材を用意します。
※私はUnityアセットストアで下記の無料の素材をお借りしました。
参考:https://assetstore.unity.com/packages/2d/characters/tiny-rpg-forest-114685#description


それでは始めましょう。
まず、Unityで適当に2D用の設定でプロジェクトを作ります。
用意した2D素材をAssetsフォルダに入れて、シーンに待機状態のキャラクタースプライト画像を置きます。

キャラクターを待機ポーズで配置

次に、配置したスプライト画像にAnimatorコンポーネントを追加しましょう。
コンポーネントを追加したらControllerの中にAnimatorControllerも作成してアタッチしておきましょう。
(ここではRPGAnimatorという名前にしました)

Animator Controller

なお、AnimatorControllerはProjectウインドウの任意のフォルダ
右クリック→Create→Animation→AnimatorControllerで作成できます。

AnimatorController作成

Animatorコンポーネントに関しては下記で詳しく解説しています。


次にブレンドツリーの設定にいきましょう。

先ほど作成してアタッチしたAnimaorControllerをクリックするとAnimatorウインドウが開くと思います。
(Inspectorからでもプロジェクトウインドウからでも大丈夫かと思います)

Animatorウインドウ上で右クリック→CreateState→FromNewBlendTreeを選択してブレンドツリーを作成して下さい。

BlendTree作成

BlenTreeステートが出来たらステートをダブルクリックしてBlendTreeレイヤーに入って下さい。

BlendTreeレイヤーの中でBlendTreeステートをクリック、
InspectorでBlendTypeを「2D Simple Directional」に設定。
さらに右下の「+」ボタンで「Add Motion Field」を選択して5つ追加して下さい。

AddMotionField 画面は4つだけど5つ作成して下さいね

追加できたら5つのモーションフィールド(上下左右+真ん中の待機モーション分)のポジションを下記の図のように設定して下さい。

そしてAnimatorウインドウの左のParameters欄
Float型パラメーター「BlendX」「BlendY」を追加して下さい。

追加したらBlendTreeのInspector欄の
Parameters」で下記の様に「BlendX」「BlendY」を選択して下さい。

BlendXYの設定

これでBlendTreeの仕込みが終わりました!


各方向のアニメーションを入れるブレンドツリーの箱が用意できたので、
モーションフィールドに入れるためのアニメーションクリップを作成していきます。

※クリップ自体の作り方は省こうと思います。基本的な部分は下記で記載してあります。

作成したアニメーションクリップに2Dスプライトを配置する場合は画像を全部を選んで放り込めば自動で並べられます(名前をアニメーションの順番にしておく必要はあると思います)

並べ方

あとは、歩行アニメーションの1ループを任意の長さに調整してあげましょう。(私は0.25秒にしました)
この時、先ほどドラッグ&ドロップした全部のキーフレームを選んでスケールすると楽にできます。

正面方向のアニメーションができたので同様に他の方向(上、左、右)のアニメーションを
それぞれクリップにして作っていきます。

また、何も操作していない時の待機時のアニメーションも作っておきます。
※今回は下記のように最初にシーンに入れたスプライト1枚だけ入れてクリップを作成しました。

もし左右で片方向きしか素材が無かった場合。
下の図のようにAnimationウインドウで「AddProperty」でScaleを追加してあげてx軸にマイナス1を入れてあげると反転するのでゲームによってはこれでも良いと思います。
(自動で最終フレームにもキーが打たれることがあるので、そこは削除しておきましょう。)

スケールで反転

4方向+待機分のアニメーションクリップが出来上がったらBlendTreeに設定していきましょう。
先ほど作ったBlendTreeのインスペクターで下記のように各方向のモーションフィールドにクリップを入れて下さい。

(私はwalk01とかの名前で作ってしまったので向きが分かりにくいですが、「walkTop」とかの名前にしておくと分かりやすかったですね。)

次にスクリプトを準備していきます。


スクリプトですが、今回は4方向の想定なので、
上下左右のボタンでスプライトの移動+移動方向に合わせてアニメーションが再生される
というスクリプトを用意してみました。

using UnityEngine;

public class walk : MonoBehaviour
{

    public Animator animator;
    private float moveX; // 左右の入力を保存
    private float moveY; // 上下の入力を保存

    void Update()
    {
        moveX = Input.GetAxisRaw("Horizontal"); // 左右の入力
        moveY = Input.GetAxisRaw("Vertical");     // 上下の入力

        animator.SetFloat("BlendX", moveX); //入力された値をそのままparametersに代入
        animator.SetFloat("BlendY", moveY); //入力された値をそのままparametersに代入
    }

    void FixedUpdate()
    {
        // 入力があった場合、スプライトの位置を変更
        Vector3 movement = new Vector3(moveX, moveY,0) * 0.01f; // 入力値に応じた移動量を設定
        transform.position += movement; // スプライトの位置を更新 
    }
}

ここは本筋ではないのでサッとそれぞれのコードの説明をしていきます。


  public Animator animator;
    private float moveX; // 左右の入力を保存
    private float moveY; // 上下の入力を保存

AnimatorでBlendTreeのparametersにアクセスするための変数を作成、
XY方向の入力値を取るためのにfloat型の変数も用意しておきます。

        moveX = Input.GetAxisRaw("Horizontal"); // 左右の入力
        moveY = Input.GetAxisRaw("Vertical");     // 上下の入力

ここでmoveXとmoveYに十字キーでの入力値を入れます。
「Input.GetAxisRaw」はUnityの入力システムで水平(Horizontal)および垂直(Vertical)方向の入力値を取得するメソッドです。
例えば水平の場合
右方向Dキー、または右矢印キー)→ 値が 1
左方向Aキー、または左矢印キー)→ 値が -1
押されていない場合値が 0
となります。
この入力値を使ってスプライトの移動とBlendTreeのアニメーションの再生をしていきます。

        animator.SetFloat("BlendX", moveX); //入力された値をそのままparametersに代入
        animator.SetFloat("BlendY", moveY); //入力された値をそのままparametersに代入

このスクリプトでAnimator内部のparametersのBlendX、BlendYに先ほどの入力値のmoveX、moveYを代入します。
※ここで十字キーの入力値が0.1.-1のどれかになるのでBlendTreeで設定した下記の値と噛み合うのがミソになります。

    void FixedUpdate()
    {
        // 入力があった場合、スプライトの位置を変更
        Vector3 movement = new Vector3(moveX, moveY,0) * 0.01f; // 入力値に応じた移動量を設定
        transform.position += movement; // スプライトの位置を更新 
    }

ここでは実際にスプライトの移動を設定しています。
FixedUpdate()メソッドに関して、Update()と違いこちらは一定間隔で処理されるため、フレームレートに非依存の処理が行えます。
今回は移動の更新タイミングが一定になって同じリズム感で移動するのでこっちを使用してみました。

Vector3 movement = new Vector3(moveX, moveY,0) * 0.01f;
ここではVector3という値型の構造体(クラスみたいなもの)の変数movementを作成、
インスタンス化してVector3の(x,y,z)の値をそれぞれ(moveX, moveY,0)の値に0.01fを掛けた値を入れて初期化しました。

これは入力した際の実際のスプライトの移動量になります。
1や-1のままだと移動量が大きすぎたので0.01を掛けて調整しています。

transform.position += movement;
ここではこのスクリプトをアタッチしたオブジェクトのtransform値(x,y,z)を取得してそこに先ほどのmovementの値「(moveX,moveY,0)*0.0.1f」を足しています。


これでスクリプトの設定は終わりです。
それでは実際にスクリプトをアタッチして設定、挙動を確認してみましょう!

Sceneの動かしたいキャラのInspectorに、先ほどのスクリプトをアタッチして下さい。

アタッチした後は「Animator」の項目で自身(キャラクターのゲームオブジェクト、BledTreeが設定してあるもの)
を入れてあげて下さい。

この設定でオブジェクトのAnimatorをスクリプトで記述したanimatorとして取得します。
(ちょっとややこしいですね・・・)

それでは、実際にゲームプレイしてどうなるか確認してみましょう!

キーボードの上下左右でしっかり動いています!
(見栄えのためにこっそり背景を置いちゃいました・・・・!)

これでこの項目は完成です!


BlendTreeを使うことでかなりシンプルな構造になっているのではないでしょうか?
実際にはコリジョン設定を使って障害物や会話システムを作ったり、リジッドボディの方で動かしたり色々検討できると思います。

ちなみに、今回は4方向で斜め移動に関してはフォローしていないため、斜め入力では変な挙動をするかもしれません。
・・・ですが斜め入力したい場合はBlendTreeで8方向分用意してあげれば良いだけですので、8方向も簡単ですね!

8方向はこんな感じの配置になる
えきふる
えきふる

8方向も4方向も基本は同じふる〜!

2D横スクロールアクションの使用例

次に2D横スクロールアクションを想定してBlendTreeを使ってみましょう。
自分もどうなるかわかりませんがとりあえずやってみます。

画像素材はUnityAssetStoreで下記をお借りして作成してみました。
https://assetstore.unity.com/packages/2d/characters/hero-knight-pixel-art-165188

今回は左右の横移動と攻撃、ジャンプを実装してみます。
※4方向と同じ部分は省略していきますね。


まず最初に、
シーンにプレイヤーのスプライト画像を入れたら「Rigidbody2D」と「BoxCollider」をアタッチして下さい。
これらは物理演算や衝突判定をする機能ですが、アクションゲームの想定なので少し使ってみます。
※画像では既に地面も置いてありますが無視して下さい。次で入れます。

2Dアクション。物理演算を入れる

Rigidbody2Dを入れることで重力計算や物理運動の処理を行えるようになります。
デフォルトで重力(GravityScale)が入っているかと思いますのでそのままで大丈夫です。

BoxCollider2Dは衝突判定、いわゆる「当たり判定」の領域を決めるものになります。
これもそのままデフォルトで大丈夫です。

次に地面を作りましょう。
地面用の画像を適当に配置したら、キャラ画像と同様にBoxCollider2Dをアタッチします。
この時、地面の作り方次第ではColliderの当たり判定が変な位置にあるかもしれません。
その場合はコンポーネント内の「EditCollider」で配置を調整して下さい。

配置の調整の仕方

さらに、この先の仕込みとして地面用のBoxCollider2Dをアタッチしたゲームオブジェクトに
「Ground」というタグをつけて下さい。Inspector内の下記赤四角の部分になります。

Tagの場所

「Ground」タグは新規で追加する必要があるので、Tagの▼をクリックしてAddTagを選択

AddTag

その先でTagsの中の右下の+をクリックして「Ground」と名前をつけて新しいタグを追加して下さい。

これでタグが付けれるようになったかと思います。

このGroundタグは後でスクリプトでジャンプをさせる時に使用します。

それでは、この時点でゲームプレイをしてみて下さい。
プレイヤーが重力で落下、Collider同士が当たって地面で止まれば成功です!
※接地の範囲が気になる場合はColliderのサイズを適宜調整して下さいね。


次にアニメーションの設定をしていきましょう。

アニメーションの作り方は4方向で記載したので省略しますが、今回は
・待機モーション Idle(左右)
・歩きモーション walk(左右)
・攻撃モーション attack(左右)

の計6つ用意しました。

今回、左右反転してアニメーションを用意する場合、
自分は「SpriteRender」のFlipXにアニメーションキーを打って反転させました。

FlipX

やり方はAnimationウインドウの[Add Property]でFlipXを追加して、キーを最初のフレームに打つだけです。
(デフォルトだとタイムラインの最後に自動で打たれたりするので気をつけて下さい。)

AddProperty

なぜ、スケールではなくFlipを使うのかと言うと、今回のケースの場合「Colider」の衝突判定がスケールの左右反転で1から-1になる際に一瞬0になり地面をすり抜ける可能性がある為です。


アニメーションクリップが準備出来たらAnimatorウインドウでステートを仕込みます。
ステートの扱いが不安な人は下記を参考にしてみて下さい。

今回はAnyステートを使って下記のように繋ぎました。(02とついているのが左向きのモーションになります。)

AnyState

BlendTreeの中身はwalkになっていて、左右の歩きを作ります。

BlendTree

パラメーターはfloat型の「BlendX」のみで、下図のようにポジションを左右に割り振ります。

BlendX配置

更に、この後スクリプトで各アニメーションを制御するためにParametersを追加で作ります。
・bool型の「Attack」
・int型の「Direction」
・int型の「walk」

※BlendXは既に作成済み。

Directionには最初から「1」を入れておいて下さい。
※1を右向き、-1を左向きとしてスクリプトで処理をしていきます。

パラメーターが不安な人は下記を参考に。


そして、各ステート同士の遷移ですが
↓画像のようにHasExitTimeをオフに、各数値も0、Interrption SourceはCurrentStateにして下さい。


他の全ての遷移を画像のようにしておいて下さいね。

一応、Interrption Sourceに関しての解説も載せておきますね。気になる方はどうぞ

次に各遷移に条件を付けていきます。
条件はTransitionを選択したときにInspectorに出る「Conditions」で付けられます。

各遷移の条件は下記画像と同じように付けて下さい。

各遷移の条件

簡単に各パラメーターの意味を説明すると
・「Direction」1の時右向き、-1の時左向き、というキャラの方向を示す
・「walk」0の時は歩いていない、0出ない時は歩いている状態
・「Attack」Trueの時攻撃
となっています。

例えばAnyStateからattack02への遷移の説明をすると
AttackがTrueDirectionが-1(つまり左向きの時)でこのアニメーションに遷移されクリップが再生される、と言うことになります。


ここまででやっと仕込みが終わりました。最後にスクリプトを作ってアタッチしましょう。
今回は下記のようなスクリプトを用意しました。(YokoAnimという適当な名前でスクリプトを作っちゃいました)

using UnityEngine;

public class YokoAnim : MonoBehaviour
{
    public float moveSpeed = 5f;       // 最大移動速度
    public float acceleration = 5f;   // 加速力
    public float deceleration = 3f;   // 減速力
    public float jumpForce = 7f;      // ジャンプ力
    private Rigidbody2D rb;           // Rigidbody2Dの参照
    private bool isGrounded = false;  // 地面に接触しているか判定
    private float currentSpeed = 0f;  // 現在の移動速度

    private Animator animator;        // Animatorコンポーネントの参照
    private int Direction = 0; // 最後に移動した方向(1:右, -1:左)

    private void Start()
    {
        // Rigidbody2Dのコンポーネントを取得
        rb = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
    }

    private void Update()
    {
        // 左右移動の入力取得
        float moveInput = Input.GetAxisRaw("Horizontal"); // 左右の入力
        float verticalInput = Input.GetAxisRaw("Vertical"); // 上下の入力
        animator.SetInteger("walk", (int)moveInput);//walkに左右の入力値をint型にして代入
       
        // 左右移動処理
        if (moveInput != 0)
        {
            // ボタンが押されている間は加速
            currentSpeed = Mathf.MoveTowards(currentSpeed, moveInput * moveSpeed, acceleration * Time.deltaTime);

            // 最後に移動した方向を記録
            Direction = (int)moveInput > 0 ? 1 : -1;
            animator.SetInteger("Direction", Direction);
            animator.SetFloat("BlendX", moveInput);

        }
        else
        {
            // ボタンを離した場合は減速
            currentSpeed = Mathf.MoveTowards(currentSpeed, 0, deceleration * Time.deltaTime);
            animator.SetFloat("BlendX", 0);
        }

        // 上キー(Vertical入力が正)でジャンプ
        if (verticalInput > 0 && isGrounded)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
            isGrounded = false; // ジャンプ中は地面にいない
        }

        // 左クリックが押されたら
        if (Input.GetMouseButtonDown(0)) // 0は左クリック
        {
            animator.SetBool("Attack", true); // AnimatorのAttackパラメータをオンにする
        }
    
    }

    public void ButtonUp ()
    {
            animator.SetBool("Attack", false); // AnimatorのAttackパラメータをオフにする
    }

    private void FixedUpdate()
    {
        // Rigidbody2Dの速度を設定して移動
        rb.linearVelocity = new Vector2(currentSpeed, rb.linearVelocity.y);
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            isGrounded = true; // 地面に接触している状態
        }
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            isGrounded = false; // 地面から離れた状態
        }
    }
}

非常に長いですが、やっていることの基本は4方向と同じで、
ボタン入力に応じて画像の移動と、Animatorの各parametersの値を設定しているのが殆どです。
分かり難い所だけ説明していきますね。


if (Input.GetMouseButtonDown(0)) // 0は左クリック
        {
            animator.SetBool("Attack", true); // AnimatorのAttackパラメータをオンにする
        }

ここのInput.GetMouseButtonDown(0)左クリックが押されたことを判定するメソッドです。

また、地面に接触していることを判定する下記部分

  private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            isGrounded = true; // 地面に接触している状態
        }
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            isGrounded = false; // 地面から離れた状態
        }
    }

OnCollisionEnter2D衝突判定のコールバックメソッドで、2DColliderが何かと衝突した瞬間に1度だけ呼び出されます。
そして、collision.gameObject.CompareTag(“Ground”) で、「衝突したオブジェクトのタグが Ground かどうか」をチェックしています。

つまり地面に接触した時にisGrounded = true;になるという事です。

OnCollisionExit2DはEnterの逆で、地面から離れたときにfalseになるようにしています。

  // 上キー(Vertical入力が正)でジャンプ
        if (verticalInput > 0 && isGrounded)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
            isGrounded = false; // ジャンプ中は地面にいない
        }

さらにここでジャンプ中にはisGrounded = falseになるようにしています。これで多段ジャンプを防いでいます。


と、ここでひとつ大事な仕込みを忘れていました

public void ButtonUp ()
    {
            animator.SetBool("Attack", false); // AnimatorのAttackパラメータをオフにする
    }

このメソッドを呼び出す仕組みを入れていないので、
attackのアニメーションクリップ内部に仕込みます。(左右両方分)
仕込み方は簡単で、このスクリプトをキャラのゲームオブジェクトにアタッチしたら

Animationウインドウでattackを開いて、アニメーションの最後に「AddEvent」を追加

追加したEventのFunctionで
YokoAnim(作成したスクリプト名)→Methods→ButtonUP()
を選んでください。

これでアニメーションが最後まで再生されたら自動的にAttackがfalseになります。
イベント追加に関しては下記で軽く説明しています。

これで全て終了です!
それではゲームプレイをしてどうなるか確認してみましょう。
左右で移動、上でジャンプ、左クリックで攻撃です。

上手くいきました!
更にイジる場合はジャンプ用のアニメーションを用意したり、必殺技を出したりといった応用も効くと思います。
あとは敵オブジェクトを作ってコライダーの接触範囲にいれば攻撃が効くとかやってあげれば、
簡単なアクションゲームになりそうですね。

えきふる
えきふる

BlendTreeというよりアニメーションの総合力が試される感じだったふる・・・

終わりに

今回はBlendTreeを使った2Dアニメーションの設定方法を見ていきました。
BlendTreeはアイディア次第で色々な使い方が出来る機能なので、ぜひ制作の際に頭の片隅に入れておいてみて下さい。

アドベンチャーゲームみたいな立ち絵のアニメーションもBlendTreeで作れるんじゃないかと考えているので、
纏ったらそちらも記事にしたいと思います。

えきふる
えきふる

えきふるの事は忘れても、BlendTreeの事は忘れないであげてね

◾️Unityアニメーション関連のマトメ記事はこちら

えきふるからのご相談!

当ブログ「えきふるゲームラボ」では出来るだけ分かり易く読みやすい記事の作成を目指しています!
もし読んでくださった方の中で
「ここが良く分からなかった」「ここをもう少し掘り下げて欲しい」等ありましたら
ぜひコメントで教えて下さい!

えきふる
えきふる

コメント貰えると元気も出ますので、どうぞお気軽にお願いしますふる!

※このブログは、UnityTechnologiesまたはその関連会社が後援または提携しているものではありません。「Unity」は、UnityTechnologiesまたはその関連会社の米国およびその他の国における商標または登録商標です。

コメント

タイトルとURLをコピーしました