Unity 2Dアクションの作り方【移動を滑らかに表現しよう】

前回はジャンプを実装しました。しかし、何だか移動やジャンプがカクカクしいというかちょっと違和感があると思うので工夫していこうと思います。↑の動画でも解説しています。わからない、うまくいかない事があったら質問される前に、一回、動画の方で手順を確認してください

この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。

<今までのあらすじ>

クリックすると展開します

現在プレイヤーを↓のようなスクリプトで制御しています。

using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 public class Player : MonoBehaviour
 {
     //インスペクターで設定する
     public float speed; //速度
     public float gravity; //重力
     public float jumpSpeed;//ジャンプする速度
     public float jumpHeight;//高さ制限
     public float jumpLimitTime;//ジャンプ制限時間
     public GroundCheck ground; //接地判定
     public GroundCheck head;//頭ぶつけた判定

     //プライベート変数
     private Animator anim = null;
     private Rigidbody2D rb = null;
     private bool isGround = false;
     private bool isJump = false;
     private bool isHead = false; 
     private float jumpPos = 0.0f;
     private float jumpTime = 0.0f;

     void Start()
     {    
          //コンポーネントのインスタンスを捕まえる
          anim = GetComponent<Animator>();
          rb = GetComponent<Rigidbody2D>();
     }

     void FixedUpdate()
     {
          //接地判定を得る
          isGround = ground.IsGround();
          isHead = head.IsGround(); 

          //キー入力されたら行動する
          float horizontalKey = Input.GetAxis("Horizontal");
          float xSpeed = 0.0f;
          float ySpeed = -gravity;
          float verticalKey = Input.GetAxis("Vertical");
          if (isGround)
          {
              if (verticalKey > 0)
              {
                  ySpeed = jumpSpeed;
                  jumpPos = transform.position.y; //ジャンプした位置を記録する
                  isJump = true;
                  jumpTime = 0.0f;
              }
              else
              {
                  isJump = false;
              }
          }
          else if (isJump)
          {
             //上方向キーを押しているか
       bool pushUpKey = verticalKey > 0;
       //現在の高さが飛べる高さより下か
       bool canHeight = jumpPos + jumpHeight > transform.position.y;
       //ジャンプ時間が長くなりすぎてないか
       bool canTime = jumpLimitTime > jumpTime;

       if (pushUpKey && canHeight && canTime && !isHead)
              {
                  ySpeed = jumpSpeed;
                  jumpTime += Time.deltaTime;
              }
              else
              {
                  isJump = false;
                  jumpTime = 0.0f;
              }
          }
          if (horizontalKey > 0)
          {
              transform.localScale = new Vector3(1, 1, 1);
              anim.SetBool("run", true);
              xSpeed = speed;
          }
          else if (horizontalKey < 0)
          {
              transform.localScale = new Vector3(-1, 1, 1);
              anim.SetBool("run", true);
              xSpeed = -speed;
          }
          else
          {
              anim.SetBool("run", false);
              xSpeed = 0.0f;
          }
          anim.SetBool("jump", isJump);
          anim.SetBool("ground", isGround);
          rb.velocity = new Vector2(xSpeed, ySpeed);
      }
 }

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

public class GroundCheck : MonoBehaviour
{
     private string groundTag = "Ground";
     private bool isGround = false; 
     private bool isGroundEnter, isGroundStay, isGroundExit;

     //接地判定を返すメソッド
   //物理判定の更新毎に呼ぶ必要がある
     public bool IsGround()
     {    
          if (isGroundEnter || isGroundStay)
          {
              isGround = true;
          }
          else if (isGroundExit)
          {
              isGround = false;
          }

          isGroundEnter = false;
          isGroundStay = false;
          isGroundExit = false;
          return isGround;
     }

     private void OnTriggerEnter2D(Collider2D collision)
     {
          if (collision.tag == groundTag)
          {
              isGroundEnter = true;
          }
     }

     private void OnTriggerStay2D(Collider2D collision)
     {
          if (collision.tag == groundTag)
          {
              isGroundStay = true;
          }
     }

     private void OnTriggerExit2D(Collider2D collision)
     {
          if (collision.tag == groundTag)
          {
              isGroundExit = true;
          }
     }
 }

↓上下に接地判定がついている状態になっています。

head check

これまでに移動とジャンプの制御、そしてアニメーションをつけることができました。

jump_gif

<等速直線運動になっている>

さて、今まで、velocityに直接固定された値を代入してきました。

という事は、「ボタンを押したら」「落下したら」という区切りはあるものの、動いている時の速度は一定です。

つまり言うと、今までのやり方は等速直線運動をしていたと言えます。

Point

Rigidbody.velocity = 固定の値  は等速直線運動

値を代入し続けている限り一定速度で動きます。

jump animation

余談ですが、この状態をゆっくりにするとまるで水の中にいるかのような表現ができます。

slow animation

いや、まぁ水中でもないのに水中みたいな動きされてもって感じなので移動が滑らかになるように工夫していきましょう。

<グラフで速さを表そう>

さて、では移動を滑らかに表現するためにグラフを使用しましょう。

グラフとか何やら難しそうですが、今回は簡単な方法をとりたいと思います。

とりあえず、現在はどうあろうが、速さが一定です。これは、横のスピードもそうだし、ジャンプの上昇速度も落下速度もみんな一定です。

Uniform linear motion graph
現在の状態

これを

Graph of acceleration

こんな感じに速さが徐々に上がっていったら加速的な表現ができそうです。

Graph of jump acceleration

逆にこんな感じにすれば、初速が早く徐々にゆっくりになるのでジャンプの上昇を表せそうです。

本来なら、↑のようなグラフになるように数式を組み立てて、自分で色々考えなければいけませんが、難しい事考えるのはイヤなので、↑のようなグラフを適当に作りたいと思います。

<AnimationCurveを使おう>

スクリプトに

public AnimationCurve dashCurve;
public AnimationCurve jumpCurve;

と書いてみてください。

AnimationCurveという型の変数が作成されています。これはUnityの機能で、グラフを書く事ができるようになります。

↑のようにpublicで書くとインスペクターにAnimationCurveが登場します。

inspector animation curve

何やら灰色の長方形が登場しました。これをクリックしてみてください。

animation curve window

すると↑のようなウィンドウが開いたと思います。

下の方に5つある線が入ったヤツをクリックすると

sample animation curve

何やらグラフが現れたと思います。下のヤツはグラフのサンプルみたいな感じです。適当にクリックしてみてください。

それではこのグラフを先ほど書いたようなグラフにしてみましょう。

make graph

点をクリックする事で移動させる事ができ、点から出ている棒のような物を動かす事で線の角度を変える事ができます。

また、右クリックする事でAdd Keyというものが出てきて

animation curve add key

点を増やす事ができます

rotate handle key

端っこではない点はハンドルが2個あり、片方を回すともう片方も回ります。

また、この新たに作成した点は右クリックをすることによってメニューが出てきます

delete key

・Delete Keyで点を削除する事ができます。

・Edit Keyで点の位置を直接数字で変更する事ができます。

・Clamped Auto〜Flatはハンドルを勝手に計算して回してくれます。

・Brokenで右のハンドルと左のハンドルが連動しなくなります。

↓これはBrokenした状態です。

broken handle

これで、様々なグラフを作る事ができます。

y軸を速さ、x軸を時間として自分の好きなようにグラフを書いてみましょう。こういったグラフを作ることによって様々な表現をする事ができるようになります。

スポンサーリンク

<グラフをスクリプトに適用しよう>

さて、グラフは用意できたと思うので、これを計算に入れてみましょう。

private float dashTime, jumpTime;

まず、時間を測る変数を用意します。これは、用意したグラフの横軸が時間を想定していたからですね。

走り出したらこれをdashTimeに時間を足して、止まったらdashTimeを0にします。ジャンプも同じようにすると

クリックすると展開します
using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 public class Player : MonoBehaviour
 {
     //インスペクターで設定する
     public float speed; //速度
     public float gravity; //重力
     public float jumpSpeed;//ジャンプする速度
     public float jumpHeight;//高さ制限
     public float jumpLimitTime;//ジャンプ制限時間
     public GroundCheck ground; //接地判定
     public GroundCheck head;//頭ぶつけた判定
     public AnimationCurve dashCurve;  //New
     public AnimationCurve jumpCurve;  //New

     //プライベート変数
     private Animator anim = null;
     private Rigidbody2D rb = null;
     private bool isGround = false;
     private bool isJump = false;
     private bool isHead = false; 
     private float jumpPos = 0.0f;
     private float dashTime, jumpTime;  //New
     private float beforeKey;  //New

     void Start()
     {
          //コンポーネントのインスタンスを捕まえる
          anim = GetComponent<Animator>();
          rb = GetComponent<Rigidbody2D>();
     }

     void FixedUpdate()
     {
          //接地判定を得る
          isGround = ground.IsGround();
          isHead = head.IsGround(); 

          //キー入力されたら行動する
          float horizontalKey = Input.GetAxis("Horizontal");
          float xSpeed = 0.0f;
          float ySpeed = -gravity;
          float verticalKey = Input.GetAxis("Vertical");

          if (isGround)
          {
              if (verticalKey > 0)
              {
                  ySpeed = jumpSpeed;
                  jumpPos = transform.position.y; //ジャンプした位置を記録する
                  isJump = true;
                  jumpTime = 0.0f;
              }
              else
              {
                  isJump = false;
              }
          }
          else if (isJump)
          {
              //上方向キーを押しているか
       bool pushUpKey = verticalKey > 0;
       //現在の高さが飛べる高さより下か
       bool canHeight = jumpPos + jumpHeight > transform.position.y;
       //ジャンプ時間が長くなりすぎてないか
       bool canTime = jumpLimitTime > jumpTime;

       if (pushUpKey && canHeight && canTime && !isHead)
              {
                  ySpeed = jumpSpeed;
                  jumpTime += Time.deltaTime;
              }
              else
              {
                  isJump = false;
                  jumpTime = 0.0f;
              }
          }

          if (horizontalKey > 0)
          {
              transform.localScale = new Vector3(1, 1, 1);
              anim.SetBool("run", true);
              dashTime += Time.deltaTime;  //New
              xSpeed = speed;
          }
          else if (horizontalKey < 0)
          {
              transform.localScale = new Vector3(-1, 1, 1);
              anim.SetBool("run", true);
              dashTime += Time.deltaTime;  //New
              xSpeed = -speed;
          }
          else
          {
              anim.SetBool("run", false);
              xSpeed = 0.0f;
              dashTime = 0.0f;  //New
          }

          //前回の入力からダッシュの反転を判断して速度を変える New
          if (horizontalKey > 0 && beforeKey < 0)
          {
              dashTime = 0.0f;
          }
          else if (horizontalKey < 0 && beforeKey > 0)
          {
              dashTime = 0.0f;
          }
          beforeKey = horizontalKey;

          //アニメーションカーブを速度に適用 New
          xSpeed *= dashCurve.Evaluate(dashTime);
          if (isJump)
          {
              ySpeed *= jumpCurve.Evaluate(jumpTime);
          }
          anim.SetBool("jump", isJump);
          anim.SetBool("ground", isGround);
          rb.velocity = new Vector2(xSpeed, ySpeed);
      }
 }

こんな感じになりました。

これでdashTimeとjumpTimeから、走り始めてからの時間とジャンプし始めてからの時間を取る事ができます。

この時間をグラフの横軸に持ってきて、縦軸の値を受け取るには↓のようにします。

xSpeed *= dashCurve.Evaluate(dashTime);
if (isJump)
{
     ySpeed *= jumpCurve.Evaluate(jumpTime);
}

AnimationCurve.Evaluate(指定した時間)で、その時間での値を受け取る事ができます

これをvelocityに突っ込む直前に入れます。y軸はジャンプ中だけ適用して、落下は等速にしたいと思います。

グラフの縦軸をよーく見てください。1.0と書いてある場所があります。ここに点を合わせておけば、掛け算にする事で、×1.0の時インスペクターで設定した値、×0で0になります。

graph 0 and 1

ここで注意して欲しいのが完全に0になってしまうと止まってしまうので、最低位置を考えて設定してください。それとそこに達するまでの時間は横軸の数字になるのでここもよく見ましょう。

自分は次のようにしました。

↓ダッシュ時

dash graph

↓ジャンプ時

jump graph

今回ジャンプは飛べる高さで制御しているため、ジャンプ速度が0になってしまうとちょっとまずいため、最低値を0.5に設定しています

また振り返った時に速度を落とす処理は↓のようにしています。。

  private float beforeKey;

前回のキー入力を保存する変数を作ります。

if (horizontalKey > 0 && beforeKey < 0)
{
      dashTime = 0.0f;
}
else if (horizontalKey < 0 && beforeKey > 0)
{
      dashTime = 0.0f;
} 
beforeKey = horizontalKey;

そして、前回のキー入力と方向が違ったらダッシュ時間を0に戻すようにしてあげれば、振り返った時速度の掛け算が元に戻ります。

これで再生すると

move control

ちょっと変化がわかりづらいかもしれませんが、自分でコントロールすると違いがわかります。

振り返った時に速度が落ちたり、加速減速が使えて移動やジャンプの表現に深さを出せそうです。

ちょっと移動の表現方法にアクセントをつけられたのではないでしょうか。アニメーションカーブは色々な表現に使えるのと覚えておくといいと思います。

まぁ、この辺は好みなので、前のままがよかったと言う人は戻しても別にOKです。各自工夫してくださいね。

ちなみに、こんなメンドイ事しなくても物理法則適用すればよくね?って意見もあると思いますが、後で色々なアクションを追加した時に物理法則が邪魔になりやすい(そっちの方がメンドくさい)ので今回は使いません。

<まとめ>

今回でスクリプトは以下のようになりました。

クリックすると展開します
using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 public class Player : MonoBehaviour
 {
     //インスペクターで設定する
     public float speed; //速度
     public float gravity; //重力
     public float jumpSpeed;//ジャンプする速度
     public float jumpHeight;//高さ制限
     public float jumpLimitTime;//ジャンプ制限時間
     public GroundCheck ground; //接地判定
     public GroundCheck head;//頭ぶつけた判定
     public AnimationCurve dashCurve;  //New
     public AnimationCurve jumpCurve;  //New

     //プライベート変数
     private Animator anim = null;
     private Rigidbody2D rb = null;
     private bool isGround = false;
     private bool isJump = false;
     private bool isHead = false; 
     private float jumpPos = 0.0f;
     private float dashTime, jumpTime;  //New
     private float beforeKey;  //New

     void Start()
     {
          //コンポーネントのインスタンスを捕まえる
          anim = GetComponent<Animator>();
          rb = GetComponent<Rigidbody2D>();
     }

     void FixedUpdate()
     {
          //接地判定を得る
          isGround = ground.IsGround();
          isHead = head.IsGround(); 

          //キー入力されたら行動する
          float horizontalKey = Input.GetAxis("Horizontal");
          float xSpeed = 0.0f;
          float ySpeed = -gravity;
          float verticalKey = Input.GetAxis("Vertical");

          if (isGround)
          {
              if (verticalKey > 0)
              {
                  ySpeed = jumpSpeed;
                  jumpPos = transform.position.y; //ジャンプした位置を記録する
                  isJump = true;
                  jumpTime = 0.0f;
              }
              else
              {
                  isJump = false;
              }
          }
          else if (isJump)
          {
              //上方向キーを押しているか
       bool pushUpKey = verticalKey > 0;
       //現在の高さが飛べる高さより下か
       bool canHeight = jumpPos + jumpHeight > transform.position.y;
       //ジャンプ時間が長くなりすぎてないか
       bool canTime = jumpLimitTime > jumpTime;

       if (pushUpKey && canHeight && canTime && !isHead)
              {
                  ySpeed = jumpSpeed;
                  jumpTime += Time.deltaTime;
              }
              else
              {
                  isJump = false;
                  jumpTime = 0.0f;
              }
          }

          if (horizontalKey > 0)
          {
              transform.localScale = new Vector3(1, 1, 1);
              anim.SetBool("run", true);
              dashTime += Time.deltaTime;  //New
              xSpeed = speed;
          }
          else if (horizontalKey < 0)
          {
              transform.localScale = new Vector3(-1, 1, 1);
              anim.SetBool("run", true);
              dashTime += Time.deltaTime;  //New
              xSpeed = -speed;
          }
          else
          {
              anim.SetBool("run", false);
              xSpeed = 0.0f;
              dashTime = 0.0f;  //New
          }

          //前回の入力からダッシュの反転を判断して速度を変える New
          if (horizontalKey > 0 && beforeKey < 0)
          {
              dashTime = 0.0f;
          }
          else if (horizontalKey < 0 && beforeKey > 0)
          {
              dashTime = 0.0f;
          }
          beforeKey = horizontalKey;

          //アニメーションカーブを速度に適用 New
          xSpeed *= dashCurve.Evaluate(dashTime);
          if (isJump)
          {
              ySpeed *= jumpCurve.Evaluate(jumpTime);
          }
          anim.SetBool("jump", isJump); //New
          anim.SetBool("ground", isGround); //New
          rb.velocity = new Vector2(xSpeed, ySpeed);
      }
 }

何かうまくいかない事があった場合は↓の記事を参考にしてみてください

最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。

また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。


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