さて、前回は接地判定を実装しました。今回はジャンプを実装していきたいと思います。
この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。
<今までのあらすじ>
プレイヤーの操作を受けとって、アニメーションと移動する処理を作成しました。
そして、接地判定を作り、地面に接地しているかどうかを判定できるようになりました。

現在プレイヤーについているコードは↓の通りです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class player : MonoBehaviour
{
//インスペクターで設定する
public float speed; //移動速度
//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private string groundTag = "Ground";
private bool isGroundEnter, isGroundStay, isGroundExit;
private bool isGround = false;
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 xSpeed = 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;
}
rb.velocity = new Vector2(xSpeed, rb.velocity.y);
//接地しているか判断する為のフラグを1フレームごとにリセットする
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;
}
}
}
<物理法則を無視したジャンプ>
さて、ジャンプを実装していくのですが、本記事では物理法則を無視したジャンプを実装しようと思います。
なんじゃそら?って思う人も多い事と思いますが、マリオなんかはまさに物理法則を無視していますよね。例えば、ジャンプ中に方向転換できたり、小ジャンプ大ジャンプがあったり、マントを使うと何故かジャンプした高さより高く飛んだり。
このように、ゲームの場合は物理法則を無視する事によってちょっとファンタスティックと言いますか、現実ではできない表現をする事ができます。
さて、移動の記事でも言いましたが、Rigidbodyのvelocityに直接、値を代入すると物理法則を無視できるのでそれを利用していこうと思います。
<重力を自前にしよう>
さて、ジャンプを実装する前に重力を自前の物にしたいと思います。物理法則をとことん無視していきましょう。

Rigidbody2DのGracity Scaleを0にしてしまいましょう。これで重力は0になります。
そして、ちょっとコードを書き換えます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class player : MonoBehaviour
{
//インスペクターで設定する
public float speed; //移動速度
public float gravity; //重力 New
//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private string groundTag = "Ground";
private bool isGroundEnter, isGroundStay, isGroundExit;
private bool isGround = false;
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 xSpeed = 0.0f;
float ySpeed = -gravity; //New
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); //New
//接地しているか判断する為のフラグを1フレームごとにリセットする
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;
}
}
}
変数gravityをインスペクターから設定できるようにしました。
ySpeedという変数を作ることにより、縦軸の速度も自由に変更する事ができるようになりました。ここに初期値として重力を入れています。
<ジャンプする処理を記述しよう>
ではジャンプするための処理を記述していきましょう。
とりあえず、ジャンプするスピードの変数を追加しましょう。これもインスペクターから設定する事ができるようにします。
public float jumpSpeed;
地面についていたらジャンプできるようにしたいので、isGroundのフラグを使ってジャンプできる場所を指定します。
float verticalKey = Input.GetAxis("Vertical");
if(isGround)
{
if (verticalKey > 0)
{
ySpeed = jumpSpeed;
}
}
地面についている時、「↑」キーを押すと縦軸の速さが決まります。
キーを他の物にしたい方は上部メニューのEdit>ProjectSettingから左メニューのInputを選択して、Verticalの中のPositive Buttonを変更すればOKです。

↑の設定がよくわからないよという人は、移動の記事を見てみてください。移動の記事は↓になります
さて、インスペクターでjumpSpeedに値を入れて、再生すると

ピコピコ上がるだけでジャンプしません。これは地面から離れた瞬間isGroundがfalseになってしまうからですね。この問題を解決するにはジャンプ中だというフラグが必要です。
private bool isJump = false;
isJumpというフラグを用意して、ジャンプ中に上ボタンを押しっぱなしにしていたら、ジャンプし続け、離すと地面に着地するまでジャンプできないようになりました。
if(isGround)
{
if (verticalKey > 0)
{
ySpeed = jumpSpeed;
isJump = true;
}
else
{
isJump = false;
}
}
else if(isJump)
{
if (verticalKey > 0)
{
ySpeed = jumpSpeed;
}
else
{
isJump = false;
}
}
これで大ジャンプ小ジャンプをする事ができます。地面についている場合とジャンプ中の場合は違うのでelse ifでどちらかしか通らないようにします。

さて、現状のままだと無限に飛び続けられるので上限をつけましょう。
ジャンプした場所を記録するために、変数を作ります。
private float jumpPos = 0.0f;
また、ジャンプで飛べる高さを決める変数を調節しやすいようにpublicで定義します
public float jumpHeight;
これは、ジャンプした場所の高さを入れておく変数です。
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;
}
}
↑のようにジャンプした場所を記録し、その位置からジャンプした高さ以上に飛ぼうとした時isJumpをfalseにしています。こうする事で、ジャンプする高さに制限をつける事ができます。

とりあえず、ジャンプできるようにはなりました。
なーんか違和感があると思うのですが、安心してください。まだ途中です。
とりあえず、違和感の正体を一個一個修正していきましょう。まずは、立ちっぱでジャンプするのをなんとかしましょうか
<アニメーションをつける>
さて、現在のアニメーションは↓のようになっているかと思います。

アニメーションについて忘れてしまったよとか、わからないよという方は↓の記事を参考にしてください。
ここにジャンプするための矢印を繋いでいきましょう

立ち状態でも走り状態でもジャンプしたいので2つから矢印を引きます。

Parametersで+をクリックして、Boolを追加してください。

自分はjumpというパラメータ名にしました。
次に矢印をクリックして、インスペクターで

Has Exit Timeのチェックを外し、Transition Durationを0にしましょう。そして、Conditionsを+して、jumpを選択しましょう。
もう一本の矢印も同じようにしましょう。
次は、ジャンプ上昇から下降に向かって矢印を引きます。

こちらも同じようにしますが、jumpがfalseの時に遷移するようにします。

ジャンプ下降中もジャンプなのでは?と思うかもしれませんが、スクリプトで
else if(isJump)
{
//上ボタンを押されている。かつ、現在の高さがジャンプした位置から自分の決めた位置より下ならジャンプを継続する
if (verticalKey > 0 && jumpPos + jumpHeight > transform.position.y)
{
ySpeed = jumpSpeed;
}
else
{
isJump = false;
}
}
↑のような条件になっているので、実は下降中はisJumpはfalseとなります。つまり、矢印はisJumpのフラグをそのまま突っ込めばOKな状態になります。
次は着地します

上昇中と下降中の両方から立ちモーションに矢印を引きましょう。上昇から立ちに移動する時は上昇しながら何かに乗った時などがあります。
またBool型を追加します。

groundという名前にしました。引いた矢印両方にtrueを入れましょう。

さらに立ち、走りモーションからジャンプ(下降)に向かって矢印を引きます

これらにはgroundがfalseになった時にしましょう

これは、ジャンプをせずに落下するパターンが存在するからです。
<矢印に優先順位をつけよう>
さて、ここでちょっと疑問にならないでしょうか?矢印が2本以上出ているやつで、両方の条件を満たした時、どっちに行くの?という話です。
これは、アニメーターの箱をクリックしてインスペクターを見るとわかります。

右側のTransitionsと書いてあるところがわかるでしょうか。これは矢印を表しています。これは上から条件を見ますので、現在だと立ちモーションの時「run」も「jump」もtrueで「ground」がfalseだった場合、上が優先されるので「player_run」に遷移します。
どの条件も満たしているならジャンプして欲しいので、優先順位を入れ替えましょう。

ドラッグで入れ替える事ができます。
立ち、走りからの遷移はジャンプ(上昇)優先、2番目ジャンプ(下降)に、最後走りにし、ジャンプ上昇からの遷移は着地優先にしましょう。
そしてスクリプトからアニメーターにフラグを渡してあげればOKです。
anim.SetBool("jump", isJump);
anim.SetBool("ground", isGround);
↑をFixedUpdateの中に書けばフラグをそのまま渡してあげる事ができます。
現在のスクリプトは↓のような感じになっています。
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;
}
}
}

はい、ちょっとジャンプに違和感がなくなったでしょうか。まぁ、まだ違和感があると思いますが、それは次回解説しようと思います。
何かうまくいかない事があった場合は↓の記事を参考にしてみてください