【Unity】アニメーションのお勉強(BlendTree編)

クリップの遷移まで出来るようになりました。

ロボのモーションが向く方向で変わるとうれしくなりますヽ(´▽`)/~♪
こうなると折角なので動かせるようにしたいと思うのが親心。

動かせるように処理をしていきたいのですが…

実は、当初はクリップ遷移までしか考えてませんでした。
なので、ここからは内容が薄くなるかも…
ご了承下さいませ(ToT)>゛スンマセン

動きを作ると言いましたが問題点が山盛りでっす。

単純に移動スクリプトを組めば良いだけなのですが、
折角なので、動きに連動したモーションも考える必要がありそうです。

例えば、ロボの向きが変わるとモーションも変わるのですが、
keyを離してもモーションが動き続ける事です。

ロボ自体は止まってるのに、足や手は動き続けるのっておかしいですよね。

それなら停止している画像クリップを作成して、
keyを離したら遷移すればいいと思うのですが、
トランジションが複雑になりすぎます。

正直悩みました。

もう少し簡単に処理できないかとGoogle先生にお伺いすると…

BlendTreeを使え!と教えて頂きました。

ブレンドコーヒー??ダメダコリャ!( ~っ~)/ σ(^_^;)

BlendTreeとは、
ゲームアニメーションでは 2 つかそれ以上のモーション間でブレンドを行うことがよくあります…
Unity スクリプトリファレンス ブレンドツリー から抜粋

読みました…
チンプンカンプンでっす(*°ρ°) ボー

分からんものは仕方がない
情報をさがせー!と言う事でGoogle先生にお伺いしてみると
ありました!

UnityでRPGを作るpart1 歩行アニメーションと操作 - Qiita
技術覚書Qiitaいつもお世話になります。
情報を公開されている方、この場をお借りしてお礼申し上げます。
m(_ _)mアリガトウゴザイマス

これがまさにやりたい事です。
早速、勉強でっす。

このページは、凄いです。
スプライトの処理からBlendTreeの設定
実際のキャラ移動までを1ページでまとめておられます。

また、分かりやすい。
全てが参考になります。有難うございます。

このページを見ていて気付いたのですが、
前記事で作成したアニメーションクリップの直せなかった部分を修正します。
robo79.jpg
最後に入れたスプライトが0:01秒しか再生されない部分です。

0:30秒に入れたスプライトが0:31秒で終了するので、
0:39秒に同じスプライトを入れて対応してました。

Qiitaのページでは、ファイル名の横にSampleが表示されるので、4にすると書いてますが、
私のUnityでは、Sampleの項目が無いんですよね~ (;^_^Aワタシダケ

困ったな~と言う事で調べてみたら、
古いバージョンのUnityには、表示されていたそうですが、
現バージョンは非表示になっているそうです。
( v。v)oミンナトオナジデヨカッタ

早速修正します。
robo95.jpg
ウィンドウのタイムバーの右端にある・をクリックします。
robo96.jpg
タブの中にあるShowSampleRateをクリックすると

robo97.jpg
無事にSampleが表示されました。

この数値を4に変更します。
robo98.jpg
タイムラインの幅が0:10秒に変わるので、スプライトを1メモリ毎に配置しなおすと
4枚目のスプライトも同じ再生時間になりました。

Sampleって最初は、スプライトの数を設定かなと思ったのですが、
まさかサンプルレートとは思いませんでした。(;^_^A

Sampleを4にセットすると単純に1:00秒を4等分すると言う事になります。
初期値は60なので、アニメーションを開いた時は、1:00秒=0:60秒納得です。

移動速度とモーションが合わない時は、この数値を調整すると幸せになりそうです。

かなり本題から離れたので、BlendTree編再会します。

Qiitaのページで言うと、AnimatorControllerの作成からになります。
robo99.jpg
アニメーターの何も無いところを右クリックしてタブを開きます。
ステートの作成→新規のブレンドツリーからをクリックします。

BlendTreeのステートが作成されます。
続いて、前回まで作ったAnimationステートを全て削除します。

え!? 削除するの…
苦労して作ったのに…
渋々削除します。(T-T) グスッ
robo100.jpg
アニメーターの中がスッキリしました。
けど少し悲しい(TεT) イイサ・・・

気を取り直して続けます。
続いてパラメーターの設置です。
robo101.jpg
numの下にBlendが追加されています。
今回は、どちらも使わないので削除します。

numよさらば(TωT)/~~~ BYE BYE

替わりにXとYのパラメーターを追加します。
型はfloat型です。
robo102.jpg
Qiitaでは、小文字を使っておられましたが、今回は大文字で設定しました。

パラメーターの設定ができたので、
BlendTreeステートをダブルクリックして開きます。
robo103.jpg
Qiitaにあるスクショと同じ物がでてきたので一安心

出てきたBlendTreeをクリックすると
robo104.jpg
インスペクターで設定ができます。

Qiitaに書いてある通りにパラメーターを設定していきます。
robo105.jpg
ブレンドタイプを2DSimpleDirectional、二つの方向軸から読み取るんですな。
パラメーターX、Y この2つの変数から2軸方向を判断する。なるほど~
Motion、各方向を数値化するわけですな。

このMotionは、トランジションのConditionsと同じで条件文になるわけですね~

しかし、トランジションで繋ぎまくった、ステートを思いだすと
なんとBlendTreeのすっきりしてる事、わかりやすいです。

ここまでくるとスクリプトの出番ですなヽ(´▽`)/~♪マッテマシタ
と、その前にQiitaでは、Rigidbodyから移動処理をされています。
今回、Transformで移動処理を作ってみます。

本来、キャラにはRigidbodyを入れるのが当たり前なのですが、
あえて違う事をするのがケロ流と言うか、ただのひねくれ者(;^_^A
スクリプトを考えてみたいと思います。
SwitchScript

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SwitchScript : MonoBehaviour
{
private Animator roboAnim; //ロボのアニメーター取得用変数
private Transform roboTran; //ロボのTransform取得用変数

public float speed; //ロボの移動スピード

private Vector2 inputAxis; //矢印keyのベクトル

// Start is called before the first frame update
void Start()
{
roboAnim = GetComponent<Animator>(); //ロボのアニメーター取得
roboTran = GetComponent<Transform>(); //ロボのTransform取得
}

// Update is called once per frame
void Update()
{
//key入力があれば呼び出す
if (Input.anyKey)
{
KeySelect();
}
}

//入力keyから移動処理を行う
void KeySelect()
{
//矢印keyのベクトル入力
inputAxis.x = Input.GetAxis("Horizontal");
inputAxis.y = Input.GetAxis("Vertical");

//ロボの座標から移動量を計算
float roboX = roboTran.localPosition.x + Time.deltaTime * speed * inputAxis.x;
float roboY = roboTran.localPosition.y + Time.deltaTime * speed * inputAxis.y;

//移動座標を代入
roboTran.localPosition = new Vector2(roboX, roboY);
}
}

前記事で作成したSwitchScriptを書き直してみました。

Qiitaの記事を参考に… (°-°;)ヾ(-_- ;) パクっただけやろ!
Transform移動用にしてみました。

Rigidbodyの移動は、FixedUpdateで移動処理をしますが、
Transform移動は、Update内で処理ができます。

ただ、Update内に移動処理を作ると常に働いてしまうので、
Input.anyKeyで判断して、keyが押されてる時だけ働くようにしています。

移動処理については、
ロボの現座標にdeltaTime×1秒の移動距離で計算しています。
最後にkeyのベクトルを掛けているのは、矢印keyを押し続けると
ベクトルは、右ならXが0~1に増えて行きます。
加速を表現する際には便利です。

X,Yの移動量を計算して最後にTransformに代入すればOKでっす。
ロボのSwitchScriptコンポーネントの速度を200に設定して試してみます。
無事動いてくれました。

これで無事にロボが移動するようになったので、
アニメーションの遷移を実装します。

Qiitaのスクリプトをそのままは使えないのでTransform用に調整します。
SwitchScript

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SwitchScript : MonoBehaviour
{
private Animator roboAnim; //ロボのアニメーター取得用変数
private Transform roboTran; //ロボのTransform取得用変数

public float speed; //ロボの移動スピード

// Start is called before the first frame update
void Start()
{
roboAnim = GetComponent<Animator>(); //ロボのアニメーター取得
roboTran = GetComponent<Transform>(); //ロボのTransform取得
}

// Update is called once per frame
void Update()
{
if (Input.anyKeyDown)
{
Vector2? action = this.actionKeyDown();
if (action.HasValue)
{
// キー入力があればAnimatorにstateをセットする
setStateToAnimator(vector: action.Value);
return;
}
}
//矢印keyのベクトル入力
Vector2 vector = new Vector2(
(int)Input.GetAxis("Horizontal"),
(int)Input.GetAxis("Vertical"));

// キー入力が続いている場合は、入力から作成したVector2を渡す
// キー入力がなければ null
setStateToAnimator(vector: vector != Vector2.zero ? vector : (Vector2?)null);

//key入力があれば移動処理を行う
if (Input.anyKey)
{
RoboMove(vector);
}
}

//ロボの移動処理
void RoboMove(Vector2 vector)
{
//ロボの座標から移動量を計算
float roboX = roboTran.localPosition.x + Time.deltaTime * speed * vector.x;
float roboY = roboTran.localPosition.y + Time.deltaTime * speed * vector.y;

//移動座標を代入
roboTran.localPosition = new Vector2(roboX, roboY);
}

/**
* Animatorに状態をセットする
*
*/
private void setStateToAnimator(Vector2? vector)
{
if (!vector.HasValue)
{
roboAnim.speed = 0.0f;
return;
}

Debug.Log(vector.Value);
roboAnim.speed = 1.0f;
roboAnim.SetFloat("X", vector.Value.x);
roboAnim.SetFloat("Y", vector.Value.y);

}

/**
* 特定のキーの入力があればキーにあわせたVector2インスタンスを返す
* なければnullを返す
*/
private Vector2? actionKeyDown()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) return Vector2.up;
if (Input.GetKeyDown(KeyCode.LeftArrow)) return Vector2.left;
if (Input.GetKeyDown(KeyCode.DownArrow)) return Vector2.down;
if (Input.GetKeyDown(KeyCode.RightArrow)) return Vector2.right;
return null;
}
}

こんな感じでしょうか。
ロボの移動処理を分離して、vectorを引数に処理してみました。
アニメーターに渡すパラメーターですが、Qiitaでは小文字のxyを使っておられます。
このスクリプトでは、大文字に変更しているので注意です。

これで動かしてみます。
robo106.jpg
アニメーションも機能して無事に動いてくれてますが…
おお!ロボよ何処へ行く!

画面外まで行ってしまいました。

Fieldも用意してるので、移動範囲を設定する必要があります。
移動処理の計算部分を修正します。

Fieldの範囲は、X軸-320~320、Y軸-340~530に限定します。
SwitchScript

//ロボの移動範囲
private const float Xmax = 320;
private const float Xmin = -320;
private const float Ymax = 530;
private const float Ymin = -340;


//key方向からロボの移動処理を行う
void RoboMove(Vector2 vector)
{
//ロボの座標から移動量を計算
float tempX = roboTran.localPosition.x + Time.deltaTime * speed * vector.x;
float tempY = roboTran.localPosition.y + Time.deltaTime * speed * vector.y;

//移動範囲を設定する
float roboX = Mathf.Clamp(tempX, Xmin, Xmax);
float roboY = Mathf.Clamp(tempY, Ymin, Ymax);

//移動座標を代入
roboTran.localPosition = new Vector2(roboX, roboY);
}

これでフィールドから出なくなるので、動かしてみます。


key操作が録画できないので、keyを押してる間矢印を表示してみました。
アニメーションの切り替え、フィールドの範囲指定もできてます。
無事、完成する事ができました。ヽ(´▽`)/~♪

しかし…
BlendTreeの話のはずが、ほぼほぼ違う内容に…
(;^o^) \(ToT )あんたほんとにそれでいいの

最後にBlendTreeについて少し触れます。

アニメーションのブレンディングは、ステートの遷移ではできません。
ステート遷移は、一つ一つのアニメーションを切り替えによって行います。

BlendTreeでは、一つ一つのアニメーションを切り替える際、
中間の部分をブレンディングによって調整してくれるそうです。

2Dでは、恩恵は少ないかもしれませんが、
3Dなら傾きの調整も行ってくれるんだとか。

例えば斜めに歩くなんてのも前方・横向きの比率から斜めの向きを
調整して自動的に再生してくれるみたいです。

Qiitaで良い表現をされていました。
”よしなにしてくれる”

2Dで使うならObjectのスピードに合わせて、可変するものとか
エリア別でアクションが変わるものにも使えそうです。

パラメーターに渡す数値しだいで、いろいろとできそうなので、
どんどん使って行くのがいいように思います。

BlendTreeについては、ざっくりとした説明でしたが、
何かの参考になれば幸いです。

それでは、今日はこのへんで
(o・・o)/~マタネェ

この記事へのコメント