前回はコンティニューやステージクリア、メッセージ表示といったゲーム中のイベントを作成しました。今回はジャンプ台を作成していきます。最後までついて来て頂ければ↑のようなゲームを作る事ができます。
この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。
<今まで実装した物>
さて、ジャンプ台を作っていきたいところですが、実はもうほぼできています。
過去に作成した物を流用すればOKです。では過去に作ったものを見てみましょう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
#region//インスペクターで設定する
[Header("移動速度")] public float speed;
[Header("重力")] public float gravity;
[Header("ジャンプ速度")] public float jumpSpeed;
[Header("ジャンプする高さ")] public float jumpHeight;
[Header("ジャンプする長さ")] public float jumpLimitTime;
[Header("接地判定")] public GroundCheck ground;
[Header("天井判定")] public GroundCheck head;
[Header("ダッシュの速さ表現")] public AnimationCurve dashCurve;
[Header("ジャンプの速さ表現")] public AnimationCurve jumpCurve;
[Header("踏みつけ判定の高さの割合(%)")] public float stepOnRate;
[Header("ジャンプする時に鳴らすSE")] public AudioClip jumpSE;
[Header("やられた鳴らすSE")] public AudioClip downSE;
[Header("コンティニュー時に鳴らすSE")] public AudioClip continueSE;
#endregion
#region//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private CapsuleCollider2D capcol = null;
private SpriteRenderer sr = null;
private MoveObject moveObj = null;
private bool isGround = false;
private bool isJump = false;
private bool isHead = false;
private bool isRun = false;
private bool isDown = false;
private bool isOtherJump = false;
private bool isContinue = false;
private bool nonDownAnim = false;
private bool isClearMotion = false;
private float jumpPos = 0.0f;
private float otherJumpHeight = 0.0f;
private float dashTime = 0.0f;
private float jumpTime = 0.0f;
private float beforeKey = 0.0f;
private float continueTime = 0.0f;
private float blinkTime = 0.0f;
private string enemyTag = "Enemy";
private string deadAreaTag = "DeadArea";
private string hitAreaTag = "HitArea";
private string moveFloorTag = "MoveFloor";
private string fallFloorTag = "FallFloor";
#endregion
void Start()
{
//コンポーネントのインスタンスを捕まえる
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
capcol = GetComponent<CapsuleCollider2D>();
sr = GetComponent<SpriteRenderer>();
}
private void Update()
{
if (isContinue)
{
//明滅 ついている時に戻る
if (blinkTime > 0.2f)
{
sr.enabled = true;
blinkTime = 0.0f;
}
//明滅 消えているとき
else if (blinkTime > 0.1f)
{
sr.enabled = false;
}
//明滅 ついているとき
else
{
sr.enabled = true;
}
//1秒たったら明滅終わり
if (continueTime > 1.0f)
{
isContinue = false;
blinkTime = 0.0f;
continueTime = 0.0f;
sr.enabled = true;
}
else
{
blinkTime += Time.deltaTime;
continueTime += Time.deltaTime;
}
}
}
void FixedUpdate()
{
if (!isDown && !GManager.instance.isGameOver && !GManager.instance.isStageClear)
{
//接地判定を得る
isGround = ground.IsGround();
isHead = head.IsGround();
//各種座標軸の速度を求める
float xSpeed = GetXSpeed();
float ySpeed = GetYSpeed();
//アニメーションを適用
SetAnimation();
//移動速度を設定
Vector2 addVelocity = Vector2.zero;
if (moveObj != null)
{
addVelocity = moveObj.GetVelocity();
}
rb.velocity = new Vector2(xSpeed, ySpeed) + addVelocity;
}
else
{
if (!isClearMotion && GManager.instance.isStageClear)
{
anim.Play("player_clear");
isClearMotion = true;
}
rb.velocity = new Vector2(0, -gravity);
}
}
/// <summary>
/// Y成分で必要な計算をし、速度を返す。
/// </summary>
/// <returns>Y軸の速さ</returns>
private float GetYSpeed()
{
float verticalKey = Input.GetAxis("Vertical");
float ySpeed = -gravity;
//何かを踏んだ際のジャンプ
if (isOtherJump)
{
//現在の高さが飛べる高さより下か
bool canHeight = jumpPos + otherJumpHeight > transform.position.y;
//ジャンプ時間が長くなりすぎてないか
bool canTime = jumpLimitTime > jumpTime;
if (canHeight && canTime && !isHead)
{
ySpeed = jumpSpeed;
jumpTime += Time.deltaTime;
}
else
{
isOtherJump = false;
jumpTime = 0.0f;
}
}
//地面にいるとき
else if (isGround)
{
if (verticalKey > 0)
{
if (!isJump)
{
GManager.instance.PlaySE(jumpSE);
}
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 (isJump || isOtherJump)
{
ySpeed *= jumpCurve.Evaluate(jumpTime);
}
return ySpeed;
}
/// <summary>
/// X成分で必要な計算をし、速度を返す。
/// </summary>
/// <returns>X軸の速さ</returns>
private float GetXSpeed()
{
float horizontalKey = Input.GetAxis("Horizontal");
float xSpeed = 0.0f;
if (horizontalKey > 0)
{
transform.localScale = new Vector3(1, 1, 1);
isRun = true;
dashTime += Time.deltaTime;
xSpeed = speed;
}
else if (horizontalKey < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
isRun = true;
dashTime += Time.deltaTime;
xSpeed = -speed;
}
else
{
isRun = false;
xSpeed = 0.0f;
dashTime = 0.0f;
}
//前回の入力からダッシュの反転を判断して速度を変える
if (horizontalKey > 0 && beforeKey < 0)
{
dashTime = 0.0f;
}
else if (horizontalKey < 0 && beforeKey > 0)
{
dashTime = 0.0f;
}
xSpeed *= dashCurve.Evaluate(dashTime);
beforeKey = horizontalKey;
return xSpeed;
}
/// <summary>
/// アニメーションを設定する
/// </summary>
private void SetAnimation()
{
anim.SetBool("jump", isJump || isOtherJump);
anim.SetBool("ground", isGround);
anim.SetBool("run", isRun);
}
/// <summary>
/// コンティニュー待機状態か
/// </summary>
/// <returns></returns>
public bool IsContinueWaiting()
{
if (GManager.instance.isGameOver)
{
return false;
}
else
{
return IsDownAnimEnd() || nonDownAnim;
}
}
//ダウンアニメーションが完了しているかどうか
private bool IsDownAnimEnd()
{
if (isDown && anim != null)
{
AnimatorStateInfo currentState = anim.GetCurrentAnimatorStateInfo(0);
if (currentState.IsName("player_down"))
{
if (currentState.normalizedTime >= 1)
{
return true;
}
}
}
return false;
}
/// <summary>
/// コンティニューする
/// </summary>
public void ContinuePlayer()
{
GManager.instance.PlaySE(continueSE);
isDown = false;
anim.Play("player_stand");
isJump = false;
isOtherJump = false;
isRun = false;
isContinue = true;
nonDownAnim = false;
}
//やられた時の処理
private void ReceiveDamage(bool downAnim)
{
if (isDown || GManager.instance.isStageClear)
{
return;
}
else
{
if (downAnim)
{
anim.Play("player_down");
}
else
{
nonDownAnim = true;
}
isDown = true;
GManager.instance.PlaySE(downSE);
GManager.instance.SubHeartNum();
}
}
#region//接触判定
private void OnCollisionEnter2D(Collision2D collision)
{
bool enemy = (collision.collider.tag == enemyTag);
bool moveFloor = (collision.collider.tag == moveFloorTag);
bool fallFloor = (collision.collider.tag == fallFloorTag);
if (enemy || moveFloor || fallFloor)
{
//踏みつけ判定になる高さ
float stepOnHeight = (capcol.size.y * (stepOnRate / 100f));
//踏みつけ判定のワールド座標
float judgePos = transform.position.y - (capcol.size.y / 2f) + stepOnHeight;
foreach (ContactPoint2D p in collision.contacts)
{
if (p.point.y < judgePos)
{
if (enemy || fallFloor)
{
ObjectCollision o = collision.gameObject.GetComponent<ObjectCollision>();
if (o != null)
{
if (enemy)
{
otherJumpHeight = o.boundHeight; //踏んづけたものから跳ねる高さを取得する
o.playerStepOn = true; //踏んづけたものに対して踏んづけた事を通知する
jumpPos = transform.position.y; //ジャンプした位置を記録する
isOtherJump = true;
isJump = false;
jumpTime = 0.0f;
}
else if(fallFloor)
{
o.playerStepOn = true;
}
}
else
{
Debug.Log("ObjectCollisionが付いてないよ!");
}
}
else if(moveFloor)
{
moveObj = collision.gameObject.GetComponent<MoveObject>();
}
}
else
{
if (enemy)
{
ReceiveDamage(true);
break;
}
}
}
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.collider.tag == moveFloorTag)
{
//動く床から離れた
moveObj = null;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == deadAreaTag)
{
ReceiveDamage(false);
}
else if (collision.tag == hitAreaTag)
{
ReceiveDamage(true);
}
}
#endregion
}
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ObjectCollision : MonoBehaviour { [Header("これを踏んだ時のプレイヤーが跳ねる高さ")] public float boundHeight;/// <summary>
/// このオブジェクトをプレイヤーが踏んだかどうか
/// </summary>
[HideInInspector] public bool playerStepOn;
}
これらを工夫したらジャンプ台を作成する事ができそうです。
<ジャンプ台のアニメーション>
スクリプトを弄る前に動きを見るために絵をまずは用意したいと思います。とりあえず、今回も下書きで適当に描いてみましょうか
縮んだ状態
伸びた状態
まぁ、下書きなので適当で置いておいて、とりあえずこの2コマのアニメーションでジャンプ台が動くのを表現します。
通常状態のアニメーションと
ジャンプ台が作動した時のアニメーションを作成します。
アニメーションの作り方を忘れてしまった方は↓の記事を参考にしてください。
で、Triggerにonのパラメータを追加し
通常状態→ジャンプ状態 のところはonが入ったら遷移、Transition Durationを0に
ジャンプ状態→通常状態 のところはHas Exit Timeにチェック、Exit Timeを1にTransition Durationを0に設定します。
アニメーションの制御のやり方を忘れてしまった方は↓の記事を参考にしてください。
はい、とりあえずアニメーションはできました。
<ジャンプ台を踏んだ時の制御を作成>
では、ジャンプ台を踏んだ時の処理を追加していきましょう。でもその前に、ジャンプ台はジャンプ台と認識させなければいけないのでタグをつけましょう。
自分はJumpStepというタグを作って設定しました。
敵を踏んだ時に作ったものは高さを変える事ができましたが、速さは変えられなかったので速さを変えるパラメーターを追加します。で、踏んだ場合の制御を書いたジャンプ台のスクリプトを用意します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ObjectCollision : MonoBehaviour { [Header("これを踏んだ時のプレイヤーが跳ねる高さ")] public float boundHeight; [Header("これを踏んだ時のプレイヤーが跳ねる速さ")] public float jumpSpeed;/// <summary>
/// このオブジェクトをプレイヤーが踏んだかどうか
/// </summary>
[HideInInspector] public bool playerStepOn;
}
using System.Collections; using System.Collections.Generic; using UnityEngine; public class JumpObject : MonoBehaviour { private ObjectCollision oc; private Animator anim;// Start is called before the first frame update
void Start()
{
oc = GetComponent<ObjectCollision>();
anim = GetComponent<Animator>();
if(oc == null || anim == null)
{
Debug.Log("ジャンプ台の設定が足りていません");
Destroy(this);
}
}
// Update is called once per frame
void Update()
{
if (oc.playerStepOn)
{
anim.SetTrigger("on");
oc.playerStepOn = false;
}
}
}
これら2つのスクリプトをジャンプ台にくっつけ、BoxCollider2DとAnimatorもセットしましょう。
こんな感じです。
次はプレイヤー側がジャンプ台を踏んだことを認識し、跳べるようにします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
#region//インスペクターで設定する
[Header("移動速度")] public float speed;
[Header("重力")] public float gravity;
[Header("ジャンプ速度")] public float jumpSpeed;
[Header("ジャンプする高さ")] public float jumpHeight;
[Header("ジャンプする長さ")] public float jumpLimitTime;
[Header("接地判定")] public GroundCheck ground;
[Header("天井判定")] public GroundCheck head;
[Header("ダッシュの速さ表現")] public AnimationCurve dashCurve;
[Header("ジャンプの速さ表現")] public AnimationCurve jumpCurve;
[Header("踏みつけ判定の高さの割合(%)")] public float stepOnRate;
[Header("ジャンプする時に鳴らすSE")] public AudioClip jumpSE;
[Header("やられた鳴らすSE")] public AudioClip downSE;
[Header("コンティニュー時に鳴らすSE")] public AudioClip continueSE;
#endregion
#region//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private CapsuleCollider2D capcol = null;
private SpriteRenderer sr = null;
private MoveObject moveObj = null;
private bool isGround = false;
private bool isJump = false;
private bool isHead = false;
private bool isRun = false;
private bool isDown = false;
private bool isOtherJump = false;
private bool isContinue = false;
private bool nonDownAnim = false;
private bool isClearMotion = false;
private float jumpPos = 0.0f;
private float otherJumpHeight = 0.0f;
private float otherJumpSpeed = 0.0f; //New
private float dashTime = 0.0f;
private float jumpTime = 0.0f;
private float beforeKey = 0.0f;
private float continueTime = 0.0f;
private float blinkTime = 0.0f;
private string enemyTag = "Enemy";
private string deadAreaTag = "DeadArea";
private string hitAreaTag = "HitArea";
private string moveFloorTag = "MoveFloor";
private string fallFloorTag = "FallFloor";
private string jumpStepTag = "JumpStep";
#endregion
void Start()
{
//コンポーネントのインスタンスを捕まえる
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
capcol = GetComponent<CapsuleCollider2D>();
sr = GetComponent<SpriteRenderer>();
}
private void Update()
{
if (isContinue)
{
//明滅 ついている時に戻る
if (blinkTime > 0.2f)
{
sr.enabled = true;
blinkTime = 0.0f;
}
//明滅 消えているとき
else if (blinkTime > 0.1f)
{
sr.enabled = false;
}
//明滅 ついているとき
else
{
sr.enabled = true;
}
//1秒たったら明滅終わり
if (continueTime > 1.0f)
{
isContinue = false;
blinkTime = 0.0f;
continueTime = 0.0f;
sr.enabled = true;
}
else
{
blinkTime += Time.deltaTime;
continueTime += Time.deltaTime;
}
}
}
void FixedUpdate()
{
if (!isDown && !GManager.instance.isGameOver && !GManager.instance.isStageClear)
{
//接地判定を得る
isGround = ground.IsGround();
isHead = head.IsGround();
//各種座標軸の速度を求める
float xSpeed = GetXSpeed();
float ySpeed = GetYSpeed();
//アニメーションを適用
SetAnimation();
//移動速度を設定
Vector2 addVelocity = Vector2.zero;
if (moveObj != null)
{
addVelocity = moveObj.GetVelocity();
}
rb.velocity = new Vector2(xSpeed, ySpeed) + addVelocity;
}
else
{
if (!isClearMotion && GManager.instance.isStageClear)
{
anim.Play("player_clear");
isClearMotion = true;
}
rb.velocity = new Vector2(0, -gravity);
}
}
/// <summary>
/// Y成分で必要な計算をし、速度を返す。
/// </summary>
/// <returns>Y軸の速さ</returns>
private float GetYSpeed()
{
float verticalKey = Input.GetAxis("Vertical");
float ySpeed = -gravity;
//何かを踏んだ際のジャンプ
if (isOtherJump)
{
//現在の高さが飛べる高さより下か
bool canHeight = jumpPos + otherJumpHeight > transform.position.y;
//ジャンプ時間が長くなりすぎてないか
bool canTime = jumpLimitTime > jumpTime;
if (canHeight && canTime && !isHead)
{
ySpeed = otherJumpSpeed;
jumpTime += Time.deltaTime;
}
else
{
isOtherJump = false;
jumpTime = 0.0f;
}
}
//地面にいるとき
else if (isGround)
{
if (verticalKey > 0)
{
if (!isJump)
{
GManager.instance.PlaySE(jumpSE);
}
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 (isJump || isOtherJump)
{
ySpeed *= jumpCurve.Evaluate(jumpTime);
}
return ySpeed;
}
/// <summary>
/// X成分で必要な計算をし、速度を返す。
/// </summary>
/// <returns>X軸の速さ</returns>
private float GetXSpeed()
{
float horizontalKey = Input.GetAxis("Horizontal");
float xSpeed = 0.0f;
if (horizontalKey > 0)
{
transform.localScale = new Vector3(1, 1, 1);
isRun = true;
dashTime += Time.deltaTime;
xSpeed = speed;
}
else if (horizontalKey < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
isRun = true;
dashTime += Time.deltaTime;
xSpeed = -speed;
}
else
{
isRun = false;
xSpeed = 0.0f;
dashTime = 0.0f;
}
//前回の入力からダッシュの反転を判断して速度を変える
if (horizontalKey > 0 && beforeKey < 0)
{
dashTime = 0.0f;
}
else if (horizontalKey < 0 && beforeKey > 0)
{
dashTime = 0.0f;
}
xSpeed *= dashCurve.Evaluate(dashTime);
beforeKey = horizontalKey;
return xSpeed;
}
/// <summary>
/// アニメーションを設定する
/// </summary>
private void SetAnimation()
{
anim.SetBool("jump", isJump || isOtherJump);
anim.SetBool("ground", isGround);
anim.SetBool("run", isRun);
}
/// <summary>
/// コンティニュー待機状態か
/// </summary>
/// <returns></returns>
public bool IsContinueWaiting()
{
if (GManager.instance.isGameOver)
{
return false;
}
else
{
return IsDownAnimEnd() || nonDownAnim;
}
}
//ダウンアニメーションが完了しているかどうか
private bool IsDownAnimEnd()
{
if (isDown && anim != null)
{
AnimatorStateInfo currentState = anim.GetCurrentAnimatorStateInfo(0);
if (currentState.IsName("player_down"))
{
if (currentState.normalizedTime >= 1)
{
return true;
}
}
}
return false;
}
/// <summary>
/// コンティニューする
/// </summary>
public void ContinuePlayer()
{
GManager.instance.PlaySE(continueSE);
isDown = false;
anim.Play("player_stand");
isJump = false;
isOtherJump = false;
isRun = false;
isContinue = true;
nonDownAnim = false;
}
//やられた時の処理
private void ReceiveDamage(bool downAnim)
{
if (isDown || GManager.instance.isStageClear)
{
return;
}
else
{
if (downAnim)
{
anim.Play("player_down");
}
else
{
nonDownAnim = true;
}
isDown = true;
GManager.instance.PlaySE(downSE);
GManager.instance.SubHeartNum();
}
}
#region//接触判定
private void OnCollisionEnter2D(Collision2D collision)
{
bool enemy = (collision.collider.tag == enemyTag);
bool moveFloor = (collision.collider.tag == moveFloorTag);
bool fallFloor = (collision.collider.tag == fallFloorTag);
bool jumpStep = (collision.collider.tag == jumpStepTag);
if (enemy || moveFloor || fallFloor || jumpStep)
{
//踏みつけ判定になる高さ
float stepOnHeight = (capcol.size.y * (stepOnRate / 100f));
//踏みつけ判定のワールド座標
float judgePos = transform.position.y - (capcol.size.y / 2f) + stepOnHeight;
foreach (ContactPoint2D p in collision.contacts)
{
if (p.point.y < judgePos)
{
if (enemy || fallFloor || jumpStep)
{
ObjectCollision o = collision.gameObject.GetComponent<ObjectCollision>();
if (o != null)
{
if (enemy || jumpStep)
{
otherJumpHeight = o.boundHeight; //踏んづけたものから跳ねる高さを取得する
otherJumpSpeed = o.jumpSpeed; //ジャンプするスピード
o.playerStepOn = true; //踏んづけたものに対して踏んづけた事を通知する
jumpPos = transform.position.y; //ジャンプした位置を記録する
isOtherJump = true;
isJump = false;
jumpTime = 0.0f;
}
else if(fallFloor)
{
o.playerStepOn = true;
}
}
else
{
Debug.Log("ObjectCollisionが付いてないよ!");
}
}
else if(moveFloor)
{
moveObj = collision.gameObject.GetComponent<MoveObject>();
}
}
else
{
if (enemy)
{
ReceiveDamage(true);
break;
}
}
}
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.collider.tag == moveFloorTag)
{
//動く床から離れた
moveObj = null;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == deadAreaTag)
{
ReceiveDamage(false);
}
else if (collision.tag == hitAreaTag)
{
ReceiveDamage(true);
}
}
#endregion
}
はい、完成しました。
ジャンプ台が跳べる高さや飛ぶ速さなどはインスペクターで調整してください。
また、敵についていたスクリプトをいじったので、敵のインスペクターも変更するのを忘れないでください。
敵を何体も置いてしまっている人は敵をプレハブ化して元を変更したら全部に変更が行き届くようにしましょう。
何かうまくいかない事があった場合は↓の記事を参考にしてみてください
最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。
また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。