【Unity入門】2Dアクションを作ろう【移動表現】

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

<今までのあらすじ>

以前に移動とジャンプについて解説しました。

jump_gif

しかしながら、全部味気ないと言いますか、水の中にでもいるの?みたいな感じになっているので、移動の表現方法について解説していきたいと思います。

現在プレイヤーにくっついているスクリプトは↓です。

クリックすると展開します
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;//ジャンプする高さ
 
     //プライベート変数
     private Animator anim = null;
     private Rigidbody2D rb = null;
     private string groundTag = "Ground";
     private bool isGroundEnter, isGroundStay, isGroundExit;
     private bool isGround = false;
     private bool isJump = false;
     private float jumpPos = 0.0f;
     
     void Start()
     {
         //コンポーネントのインスタンスを捕まえる
         anim = GetComponent<Animator>();
         rb = GetComponent<Rigidbody2D>();
     }
 
     void FixedUpdate()
     {
         //接地しているかどうかの判定をとる
         if(isGroundEnter || isGroundStay)
         {
             isGround = true;
         }
         else if(isGroundExit)
         {
             isGround = false;
         }
 
         //キー入力されたら行動する
         float horizontalKey = Input.GetAxis("Horizontal");
         float verticalKey = Input.GetAxis("Vertical");
         float xSpeed = 0.0f;
         float ySpeed = -gravity;
         if(isGround)
         {
             if (verticalKey > 0)
             {
                 ySpeed = jumpSpeed;
                 jumpPos = transform.position.y;        //ジャンプした位置を記録する
                 isJump = true;
             }
             else
             {
                 isJump = false;
             }
         }
         else if(isJump)
         {
             //上ボタンを押されている。かつ、現在の高さがジャンプした位置から自分の決めた位置より下ならジャンプを継続する
             if (verticalKey > 0 && jumpPos + jumpHeight > transform.position.y)
             {
                 ySpeed = jumpSpeed;
             }
             else
             {
                 isJump = false;
             }
         }
         
         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;
         }
         rb.velocity = new Vector2(xSpeed, ySpeed);
         anim.SetBool("jump", isJump);
         anim.SetBool("ground", isGround);
 
         //接地しているか判断する為のフラグをリセットする
         isGroundEnter = false;
         isGroundStay = false;
         isGroundExit = false;
     }

     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;
         }
     }
 }

<等速直線運動>

さて、今まで、velocityに直接固定された値を代入してきました。という事は、「ボタンを押したら」「落下したら」という区切りはあるものの、動いている時の速度は一定です。

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

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;

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

Unityで時間を測る方法は

Time.deltaTime

という命令をしようします。これは、前のフレームから現在のフレームまでの時間差を表します。

まー、難しい事は考えずにこれを足していけば時間を測れると思ってください。

dashTime += Time.deltaTime;

走っている間、前のフレームから現在のフレームまでの時間差を毎回毎回足していけば走り出してからの時間になります。

ちなみに「FixedUpdateの中ではTime.fixedTimeを使うんや」と言う人がいますが、FixedUpdateの中ではTime.deltaTimeを使った場合と値が変わらないです。Updateの中でTime.deltaTimeとTime.fixedTimeの差を見るのが正解だと思います。

走り出したらこれを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 AnimationCurve dashCurve;  //new!
     public AnimationCurve jumpCurve;  //new!
 
     //プライベート変数
     private Animator anim = null;
     private Rigidbody2D rb = null;
     private string groundTag = "Ground";
     private bool isGroundEnter, isGroundStay, isGroundExit;
     private bool isGround = false;
     private bool isJump = false;
     private float jumpPos = 0.0f;
     private float dashTime, jumpTime;  //new!
     
     void Start()
     {
         //コンポーネントのインスタンスを捕まえる
         anim = GetComponent<Animator>();
         rb = GetComponent<Rigidbody2D>();
     }
 
     void FixedUpdate()
     {
         //接地しているかどうかの判定をとる
         if(isGroundEnter || isGroundStay)
         {
             isGround = true;
         }
         else if(isGroundExit)
         {
             isGround = false;
         }
 
         //キー入力されたら行動する
         float horizontalKey = Input.GetAxis("Horizontal");
         float verticalKey = Input.GetAxis("Vertical");
         float xSpeed = 0.0f;
         float ySpeed = -gravity;
         if(isGround)
         {
             if (verticalKey > 0)
             {
                 ySpeed = jumpSpeed;
                 jumpPos = transform.position.y;        //ジャンプした位置を記録する
                 isJump = true;
             }
             else
             {
                 isJump = false;
             }
             jumpTime = 0.0f;
         }
         else if(isJump)
         {
             //上ボタンを押されている。かつ、現在の高さがジャンプした位置から自分の決めた位置より下ならジャンプを継続する
             if (verticalKey > 0 && jumpPos + jumpHeight > transform.position.y)
             {
                 ySpeed = jumpSpeed;
                 jumpTime += Time.deltaTime;  //new!
             }
             else
             {
                 isJump = false;
                 jumpTime = 0.0f;  //new!
             }
         }
         
         if (horizontalKey > 0)
         {
             transform.localScale = new Vector3(1, 1, 1);
             anim.SetBool("run", true);
             xSpeed = speed;
             dashTime += Time.deltaTime;  //new!
         }
         else if (horizontalKey < 0)
         {
             transform.localScale = new Vector3(-1, 1, 1);
             anim.SetBool("run", true);
             xSpeed = -speed;
             dashTime += Time.deltaTime;  //new!
         }
         else
         {
             anim.SetBool("run", false);
             xSpeed = 0.0f;
             dashTime = 0.0f;  //new!
         }
         rb.velocity = new Vector2(xSpeed, ySpeed);
         anim.SetBool("jump", isJump);
         anim.SetBool("ground", isGround);
 
         //接地しているか判断する為のフラグをリセットする
         isGroundEnter = false;
         isGroundStay = false;
         isGroundExit = false;
     }

     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;
         }
     }
 }

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

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

ではこれにグラフを適用しましょう。

       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 AnimationCurve dashCurve; //ダッシュの速さ表現
     public AnimationCurve jumpCurve; //ジャンプの速さ表現
 
     //プライベート変数
     private Animator anim = null;
     private Rigidbody2D rb = null;
     private string groundTag = "Ground";
     private bool isGroundEnter, isGroundStay, isGroundExit;
     private bool isGround = false;
     private bool isJump = false;
     private float jumpPos = 0.0f;
     private float dashTime, jumpTime;
     private float beforeKey;  //new!
     
     void Start()
     {
         //コンポーネントのインスタンスを捕まえる
         anim = GetComponent<Animator>();
         rb = GetComponent<Rigidbody2D>();
     }
 
     void FixedUpdate()
     {
         //接地しているかどうかの判定をとる
         if(isGroundEnter || isGroundStay)
         {
             isGround = true;
         }
         else if(isGroundExit)
         {
             isGround = false;
         }
 
         //キー入力されたら行動する
         float horizontalKey = Input.GetAxis("Horizontal");
         float verticalKey = Input.GetAxis("Vertical");
         float xSpeed = 0.0f;
         float ySpeed = -gravity;
         if(isGround)
         {
             if (verticalKey > 0)
             {
                 ySpeed = jumpSpeed;
                 jumpPos = transform.position.y;        //ジャンプした位置を記録する
                 isJump = true;
             }
             else
             {
                 isJump = false;
  
             }
             jumpTime = 0.0f;
         }
         else if(isJump)
         {
             //上ボタンを押されている。かつ、現在の高さがジャンプした位置から自分の決めた位置より下ならジャンプを継続する
             if (verticalKey > 0 && jumpPos + jumpHeight > transform.position.y)
             {
                 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;
             dashTime += Time.deltaTime;
         }
         else if (horizontalKey < 0)
         {
             transform.localScale = new Vector3(-1, 1, 1);
             anim.SetBool("run", true);
             xSpeed = -speed;
             dashTime += Time.deltaTime;
         }
         else
         {
             anim.SetBool("run", false);
             xSpeed = 0.0f;
             dashTime = 0.0f;
         }

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

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

         rb.velocity = new Vector2(xSpeed, ySpeed);
         anim.SetBool("jump", isJump);
         anim.SetBool("ground", isGround);
 
         //接地しているか判断する為のフラグをリセットする
         isGroundEnter = false;
         isGroundStay = false;
         isGroundExit = false;
         beforeKey = horizontalKey;  //new!
     }
     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;
         }
     }
 }

<わからない事があったら>

このサイトの説明ではよくわからなかったとか、もっと知りたい事などがあれば、また別の勉強方法があるので違った切り口を使ってみるのもいいと思います。

<オススメの本>

本で詳しい解説がされているので書籍を買ってみるというのも手の一つです。最近はKindle版があるので届くまで待つ事もなく場所も取らないのでとても良いです。

<オンラインスクール>

オンラインスクールでは人に質問する事ができるので、行き詰まってしまった方にオススメです。 無料体験もあるので試しに見てみるのも手ですよ



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