さて、前回は接地判定を実装しました。今回はジャンプを実装していきたいと思います。
↑の動画でも解説しています。動画と併用してもらうとわかりやすいかなと思います。わからない、うまくいかない事があったら質問される前に、一回、動画の方で手順を確認してください
この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。
<今までのあらすじ>
プレイヤーの操作を受けとって、アニメーションと移動する処理を作成しました。
そして、接地判定を作り、地面に接地しているかどうかを判定できるようになりました。
↓のようにキャラクターの下に接地判定の用のオブジェクトが存在しています。
コードは以下のようになっているかと思います。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { //インスペクターで設定する public float speed; public GroundCheck ground;//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private bool isGround = false;
void Start()
{
//コンポーネントのインスタンスを捕まえる
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
//接地判定を得る
isGround = ground.IsGround();
//キー入力されたら行動する
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);
}
}
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;
}
}
}
Playerのインスペクターに接地判定用のスクリプトがアタッチされています。
<物理法則を無視したジャンプ>
さて、ジャンプを実装していくのですが、本記事では物理法則を無視したジャンプを実装しようと思います。
なんじゃそら?って思う人も多い事と思いますが、マリオなんかはまさに物理法則を無視していますよね。例えば、ジャンプ中に方向転換できたり、小ジャンプ大ジャンプがあったり、マントを使うと何故かジャンプした高さより高く飛んだり。
このように、ゲームの場合は物理法則を無視する事によってちょっとファンタスティックと言いますか、現実ではできない表現をする事ができます。
また、物理法則を無視することによって予想だにしないバグを避けることもできます。
ちょっと昔のゲームだと、プレイしていて物理演算が荒ぶったせいで遥か彼方に吹っ飛んで行った等の経験がある方もいらっしゃるのではないでしょうか。こういった自体を避けることができます。
よく他の解説者さんはRigidbody.AddForceで解説されている方が多いのですが、この記事では↑のバグを避けるためと、ファンタスティックな表現をするために物理演算をシカトします。
さて、移動の記事でも言いましたが、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 public GroundCheck ground; //接地判定//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private bool isGround = false;
void Start() { //コンポーネントのインスタンスを捕まえるanim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}void FixedUpdate()
{
//接地判定を得る
isGround = ground.IsGround();
//キー入力されたら行動する
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
}
}
変数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にしています。こうする事で、ジャンプする高さに制限をつける事ができます。
↓今までのを全て合わせるとこのようになります。
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 GroundCheck ground; //接地判定//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private bool isGround = false;
private bool isJump = false;
private float jumpPos = 0.0f;
void Start()
{
//コンポーネントのインスタンスを捕まえる
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
//接地判定を得る
isGround = ground.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;
}
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);
}
}
インスペクターで値を設定するのを忘れずに
とりあえず、ジャンプできるようにはなりました。
<天井にぶつかった時の処理を追加しよう>
ジャンプができたように思えますが、実はまだ足りません。
天井などで頭をぶつけた時に、高さ制限に到達することができないのでふわふわと浮き続けてしまいます。
これを対策していきます。まず、接地判定で使っているゲームオブジェクトをコピー&ペーストしてもう一個増やしてください。そして名前を適当にHeadCheckとかに変えます。
そして↑のように頭の上に持ってきてください。
さらにジャンプに秒数制限をつけていきましょう。何らかのギミックで天井にもつかず高さにも到達しないものを作ってしまった時用の対策です。(そんなの作るかどうか疑問ですが)
これらを対策すると↓のようになります。
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;//ジャンプ制限時間 New public GroundCheck ground; //接地判定 public GroundCheck head;//頭ぶつけた判定 New//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private bool isGround = false;
private bool isHead = false;
//Newprivate bool isJump = false;
private float jumpPos = 0.0f;
private float jumpTime = 0.0f;
//Newvoid Start()
{
//コンポーネントのインスタンスを捕まえる
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
//接地判定を得る
isGround = ground.IsGround();
isHead = head.IsGround(); //New
//キー入力されたら行動する
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; //New
}
else
{
isJump = false;
}
}
else if (isJump)
{
//New
//上方向キーを押しているか
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; //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);
}
}
まず、頭の判定をつけます。↓をインスペクターで設定するのを忘れないでください。
public GroundCheck head;//頭ぶつけた判定
さらにジャンプに秒数制限をかける変数を追加しています。
public float jumpLimitTime;//ジャンプ制限時間 private float jumpTime = 0.0f; private bool isHead = false;
そして、各種ジャンプに対する条件に頭がぶつかった時と時間制限を追加しています。少し見づらくなってきたので、条件を分解し、わかりやすいように変更しました
//上方向キーを押しているか
bool pushUpKey = verticalKey > 0;
//現在の高さが飛べる高さより下か
bool canHeight = jumpPos + jumpHeight > transform.position.y;
//ジャンプ時間が長くなりすぎてないか
bool canTime = jumpLimitTime > jumpTime;
if (pushUpKey && canHeight && canTime && !isHead)
{ }
Unityで時間を測る方法は
Time.deltaTime
という命令を使用します。これは、前のフレームから現在のフレームまでの時間差を表します。また、FixedUpdate内だと内容が変わりゲーム内時間を進める秒数になります。詳しくは↓の記事で解説しています。
まー、難しい事は考えずにこれを足していけば時間を測れると思ってください。
jumpTime += Time.deltaTime;
これはFixedUpdateの中なので、ジャンプ中、進んだゲーム内時間を毎回毎回足していけばジャンプし出してからの時間になります。
ちなみに「FixedUpdateの中ではTime.fixedTimeを使うんや」と言う人がいますが、FixedUpdateの中ではTime.deltaTimeを使った場合と値が変わらないです。詳しくは↑のリンクで解説しています
これでジャンプに時間制限をかけることができました。何らかのギミックでジャンプしても高さが変わらない状態になっても大丈夫そうです。
さて、よーやくこれでジャンプの制御ができるようになりました。
が、なーんか違和感があると思います。安心してください。まだ途中です。
制御ができるようになっただけでまだまだ表現としてはイマイチです。
とりあえず、違和感の正体を一個一個修正していきましょう。まずは、立ちっぱでジャンプするのをなんとかしましょうか
<アニメーションをつける>
さて、現在のアニメーションは↓のようになっているかと思います。
アニメーションについて忘れてしまったよとか、わからないよという方は↓の記事を参考にしてください。
ここにジャンプするための矢印を繋いでいきましょう
立ち状態でも走り状態でもジャンプしたいので2つから矢印を引きます。
Parametersで+をクリックして、Boolを追加してください。
自分はjumpというパラメータ名にしました。
次に矢印をクリックして、インスペクターで
Has Exit Timeのチェックを外し、Transition Durationを0にしましょう。そして、Conditionsを+して、jumpを選択しましょう。
もう一本の矢印も同じようにしましょう。
次は、ジャンプ上昇から下降に向かって矢印を引きます。
こちらも同じようにしますが、jumpが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; } }
↑のような条件になっているので、実は下降中はisJumpはfalseとなります。つまり、矢印はisJumpのフラグをそのまま突っ込めばOKな状態になります。
次は着地します
上昇中と下降中の両方から立ちモーションに矢印を引きましょう。上昇から立ちに移動する時は上昇しながら何かに乗った時などがあります。
またBool型を追加します。
groundという名前にしました。引いた矢印両方にtrueを入れましょう。また、ジャンプがfalseの場合という条件も付けます
さらに立ち、走りモーションからジャンプ(下降)に向かって矢印を引きます
これらには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;//高さ制限 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); //New anim.SetBool("ground", isGround); //Newrb.velocity = new Vector2(xSpeed, ySpeed);
}
}
はい、ちょっとジャンプに違和感がなくなったでしょうか。まぁ、まだ違和感があると思いますが、それは次回解説しようと思います。
何かうまくいかない事があった場合は↓の記事を参考にしてみてください
最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。
また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。