Unity 2Dアクションの作り方【敵・当たり判定編】
今までで移動、ジャンプ、アニメーション、カメラをプレイヤーに追従と様々なものを作ってきました。今回から敵を作っていきましょう。
↑の動画でも解説しています。わからない、うまくいかない事があったら質問される前に、一回、動画の方で手順を確認してください
この記事は本のように順を追って解説しています。この記事は途中のページになります。この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。
Unity入門【初心者為の使い方講座】【実際にゲームを作って解説】Unity初心者入門講座としてエディタの使い方を解説しています。インストールから基本、プログラミング、チュートリアルとして2Dアクションゲームの作り方を解説しています。また、動画での解説も行っているので未経験の人でもわかりやすいかなと思います。dkrevel.com2022.04.25ページメニュー- <マリオのクリボーみたいな敵を作ってみよう・その1>
- <敵の下書きを作ろう>
- <当たり判定をつけよう>
- 四角い敵のコライダー
- 基本的な2Dコライダー
- 複数コライダーを使う場合
- 特殊な2Dコライダー
- <敵との衝突判定をとる>
- <まとめ>
<マリオのクリボーみたいな敵を作ってみよう・その1>
まず、敵を作るにあたって最初なので簡単に作れるものにしようと思います。
と、いうわけで、マリオのクリボーのような比較的動きとしては簡単な敵を作っていきます。クリボーのようなと言っても、別にデザインを似せる必要はなく、「挙動がクリボーの様な」という意味です。
まず、クリボーの動きをあげると
- 衝突するとプレイヤーはやられる
- ふんづけるとプレイヤーがちょっと跳ねて敵はやられる
- 画面内に入ると動き出す
- 動いている間はひたすら一定方向に動く
- 壁に当たると反対側へ動く
こんな感じでしょうか。
今回はこの当たり判定の部分
- 衝突するとプレイヤーはやられる
この部分を実装していこうと思います。
<敵の下書きを作ろう>
このサイトでは下書きを作って、後でちゃんとした素材で上書きするという手法を取っています。
下書きから入った方が必要な素材数の把握や、かかる時間などを把握しやすいからです。また、思いもよらないトラブルに出会った場合、下書きだと対処しやすいです。
というわけで、例によって敵の下書きをパパッと作ってしまいましょう。
大きさは適当で大丈夫ですが、2のべき乗だとメモリ的にお得です。とりあえず自分は128×128で作りました。
↓歩きモーション1
↓歩きモーション2
↓やられた
まぁ、こんな感じで適当でいいです。
こういう風に作ると、とりあえず敵を移動させたくなりますが、先に移動を実装しちゃうと踏んづけるのにいちいちタイミングを合わせるのがめんどくさいので、デバッグのやりやすさを取って、当たり判定から実装します。
<当たり判定をつけよう>
四角い敵のコライダーとりあえず、シーンに敵をおいて当たり判定をつけましょう。
プレイヤーと同じ様に設定したいところなんですがCapsule Collider 2Dだとちょっと形が違うのでBox Collider 2Dを使います。
あれ?Box Collider 2Dだとコライダーに引っかかるんじゃなかったっけ?という話なんですが、実は角を丸めることができます。
とりあえず、Box Collider 2Dをつけて、当たり判定を実際のサイズよりちょっと小さくしてください。
当たり判定の緑の枠が見えやすいようにテクスチャを薄くしています。
Box Collider 2DのところのEdge Radiusという項目をいじってみてください。
設定した大きさのコライダーよりちょっと大きな角が丸いコライダーができたと思います。
これで角がつっかえることがなくなりました。
このように、四角い形状の場合でも角に丸みをつけることで動かせるようになります。
Rigidbody2Dも忘れずつけておきましょう。ConstaintsのFreeze Rotation Zにチェックする事を忘れずに。
基本的な2Dコライダーさて、今回は四角いコライダーを使いましたが、四角以外の形状にしたい場合は
では、四角以外の形状の敵を作りたい場合はどうすればいいのでしょうか。
答えは形状に合わせてコライダーの種類を変えればいいのです。
コライダーには色々な種類があります。基本形となるコライダーは↓の3種類です。これらを組み合わせて形を作るといいと思います。
使いたい形状に合わせてコライダーを使い分けてください。
- Circle Collider 2D ・・・円形のコライダー
- Box Collider 2D ・・・四角形のコライダー
- Capsule Collider 2D ・・・カプセル型のコライダー
コライダーを複数組み合わせても構いませんが、その場合注意点があります。
1. たくさんつけすぎると重くなる2. 同じゲームオブジェクトに同じコライダーを複数付けるとGetComponentでインスタンスを捕まえた時に判別しにくい
これらの点に注意しましょう。
↓複数コライダーをつけてみた例です。
ちなみに、同じゲームオブジェクトおよび子オブジェクトにつけたコライダーは重なっても大丈夫で、まとめて一つのコライダーであるかのように扱われます。
同じ種類のコライダーを複数使いたい場合は、子オブジェクトに持たせた方がいいかもしれません。インスタンスを捕まえる時にめんどくさいので。
このように、同じコライダーを複数使う場合は子オブジェクトに持たせるといいです。注意点としては、必ずRigidbodyは一番親につける事です。子に付けるとそれはそれで挙動が変わります。
特殊な2Dコライダー少し毛色の変わったコライダーが他にも3種類ありますが、これらは使い方がわかってから使うようにした方がいいかも。同じ種類のコライダーと衝突できなかったり、重くなったりするので取り扱いは慎重に。
- Polygon Collider 2D・・・自由に形状を決められるコライダー
- Edge Collider 2D・・・自由に形状を決められるコライダー
- Composite Collider 2D・・・Box Collider 2D と Polygon Collider 2D を結合するコライダー
以前に使用した↓のように自由な形状で使えます。
これらは、動くオブジェクトではなく、ステージのような固定されているものに使うといいかもしれません。
スポンサーリンク<敵との衝突判定をとる>
さて、敵の当たり判定ができたので、敵とぶつかった時の判定を作っていきましょう。
地面との接地判定を作った時のようにタグで判別したいと思います。敵用のタグを追加しましょう。
自分はEnemyという名前でタグを作成しました。
タグを追加したら、敵のタグの変更を忘れずに。
子オブジェクトにコライダーを持たせていた場合は、子オブジェクトのタグも変更することを忘れないでください
それでは、このEnemyタグとの衝突判定を取りたいと思います。
今までのプレイヤーのスクリプトに衝突判定を追加しました。
クリックすると展開しますプレイヤーの制御するスクリプトに衝突判定を追加したもの 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; #endregion #region//プライベート変数 private Animator anim = null; private Rigidbody2D rb = null; private bool isGround = false; private bool isJump = false; private bool isRun = false; private bool isHead = false; private float jumpPos = 0.0f; private float dashTime = 0.0f; private float jumpTime = 0.0f; private float beforeKey = 0.0f; private string enemyTag = "Enemy"; //New! #endregion void Start() { //コンポーネントのインスタンスを捕まえる anim = GetComponent<Animator>(); rb = GetComponent<Rigidbody2D>(); } void FixedUpdate() { //接地判定を得る isGround = ground.IsGround(); isHead = head.IsGround(); //各種座標軸の速度を求める float xSpeed = GetXSpeed(); float ySpeed = GetYSpeed(); //アニメーションを適用 SetAnimation(); //移動速度を設定 rb.velocity = new Vector2(xSpeed, ySpeed); } /// <summary> /// Y成分で必要な計算をし、速度を返す。 /// </summary> /// <returns>Y軸の速さ</returns> private float GetYSpeed() { float verticalKey = Input.GetAxis("Vertical"); float ySpeed = -gravity; 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 (isJump) { 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; } beforeKey = horizontalKey; xSpeed *= dashCurve.Evaluate(dashTime); beforeKey = horizontalKey; return xSpeed; } /// <summary> /// アニメーションを設定する /// </summary> private void SetAnimation() { anim.SetBool("jump", isJump); anim.SetBool("ground", isGround); anim.SetBool("run", isRun); } #region//接触判定 New! private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.tag == enemyTag) { Debug.Log("敵と接触した!"); } } #endregion }さて、このスクリプトの中で今回、追加されているのは
private string enemyTag = "Enemy"; #region//接触判定 private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.tag == enemyTag) { Debug.Log("敵と接触した!"); } } #endregionこの部分ですね。
このOnCollisionEnter2DというのもUnityがあらかじめ用意してくれている特別な関数で、コライダーの衝突を検知する際に使用します。
接地判定の時に用いたOnTrigger○○2Dと似た感じですね。
ただし、OnTriggerの場合は引数がCollider2Dであるのに対して、OnCollisionの場合は引数がCollision2Dと違うので注意してください。
OnTriggerの場合は当たり判定の無い領域にCollider2Dが入ってきたというものです。
OnCollisionの場合は当たり判定のあるもの同士がぶつかった「衝突データ」です。その為引数がCollider2DではなくCollision2Dなのです。
Collision2Dはコライダー ではなく、衝突のデータであるため、
collision.collider.tagこのように、衝突から、衝突した際のコライダーにアクセスし、更にその中のタグにアクセスする必要があります。
このOnCollision○○2Dも、Enter,Stay,ExitとOnTriggerの時と同じように色々種類があるのですが、今回は敵に当たったらやられるようにしたいのでEnterだけでOKです。
では、これで再生してみましょう。
とりあえず、これで敵と接触する判定を作れました。
では、敵と接触したらダウンするようにしましょう。以前作成したアニメーションがあると思います。
アニメーションの事がわからない、忘れてしまったという人は↓の記事へどうぞ
Unity 2Dアクションの作り方【アニメーション作成】Unity初心者の方でもわかりやすいように簡単な2Dアクションゲームの作り方を解説しています。今回はアニメーションの作り方です。animatorとanimationは違う物なので注意してください。dkrevel.com2020.09.21さて、以前はアニメーションを線でつないでいましたが、今回は繋がずに行こうと思います。
何故なら、やられモーションはどの状態からでも遷移する上に、ダウンした後は何の状態にも遷移しないからです。
まぁ、要はたくさん線を繋ぐのがめんどくさいし、メリットもないので省略しましょうという事です。ダウンに関しては行ったり来たりがないですからね。
その為に追加するコードは↓です。
anim.Play("player_down");animはアニメーターを突っ込んでいる変数です。
今まではSetBoolでアニメーターに対してパラメータを渡していましたが、中にあるアニメーションを直接再生する事もできます。それがPlayです。
Play(“アニメーターに追加したアニメーションの名前”);
で再生できます。
更に、ダウンしたというフラグを作ってダウンしたら動かないようにしましょう。
#region//接触判定 private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.tag == enemyTag) { anim.Play("player_down"); isDown = true; } } #endregion敵と接触したら、アニメーションを再生してフラグを立てます。
private bool isDown = false; void FixedUpdate() { if (!isDown) { //接地判定を得る isGround = ground.IsGround(); isHead = head.IsGround(); //各種座標軸の速度を求める float xSpeed = GetXSpeed(); float ySpeed = GetYSpeed(); //アニメーションを適用 SetAnimation(); //移動速度を設定 rb.velocity = new Vector2(xSpeed, ySpeed); } else { rb.velocity = new Vector2(0, -gravity); } }そして、フラグが立っている状態であるなら操作をできないようにします。
アニメーションのループを切るのを忘れないようにしましょう。
<まとめ>
はい。これで敵との接触判定ができました。
しかし、このままでは敵を踏んづけてもやられてしまうので、それを次回解説していきたいと思います。
今回作成したスクリプトは↓のような感じです。
クリックすると展開します敵との接触を追加したプレイヤーのスクリプト 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; #endregion #region//プライベート変数 private Animator anim = null; private Rigidbody2D rb = null; private bool isGround = false; private bool isJump = false; private bool isRun = false; private bool isHead = false; private bool isDown = false; private float jumpPos = 0.0f; private float dashTime = 0.0f; private float jumpTime = 0.0f; private float beforeKey = 0.0f; private string enemyTag = "Enemy"; #endregion void Start() { //コンポーネントのインスタンスを捕まえる anim = GetComponent<Animator>(); rb = GetComponent<Rigidbody2D>(); } void FixedUpdate() { if (!isDown) { //接地判定を得る isGround = ground.IsGround(); isHead = head.IsGround(); //各種座標軸の速度を求める float xSpeed = GetXSpeed(); float ySpeed = GetYSpeed(); //アニメーションを適用 SetAnimation(); //移動速度を設定 rb.velocity = new Vector2(xSpeed, ySpeed); } else { rb.velocity = new Vector2(0, -gravity); } } /// <summary> /// Y成分で必要な計算をし、速度を返す。 /// </summary> /// <returns>Y軸の速さ</returns> private float GetYSpeed() { float verticalKey = Input.GetAxis("Vertical"); float ySpeed = -gravity; 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 (isJump) { 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; } beforeKey = horizontalKey; xSpeed *= dashCurve.Evaluate(dashTime); beforeKey = horizontalKey; return xSpeed; } /// <summary> /// アニメーションを設定する /// </summary> private void SetAnimation() { anim.SetBool("jump", isJump); anim.SetBool("ground", isGround); anim.SetBool("run", isRun); } #region//接触判定 private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.tag == enemyTag) { anim.Play("player_down"); isDown = true; } } #endregion }何かうまくいかない事があった場合は↓の記事を参考にしてみてください
Unityでうまくいかないできない場合に確認すること【初心者向け】Unityで詰まった場合に、どこをまず確認すべきかについて解説しています。Unityでうまくいかない、できない事があった場合はまず落ち着いて各種項目を確認していきましょう。dkrevel.com2020.04.07最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。
また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。
目 次Unity入門【初心者為の使い方講座】【実際にゲームを作って解説】Unity初心者入門講座としてエディタの使い方を解説しています。インストールから基本、プログラミング、チュートリアルとして2Dアクションゲームの作り方を解説しています。また、動画での解説も行っているので未経験の人でもわかりやすいかなと思います。dkrevel.com2022.04.25次の記事Unity 2Dアクション作り方【敵・踏んづける編】Unity初心者の方でもわかりやすいように簡単な2Dアクションゲームの作り方を解説しています。今回は敵を踏みつける判定の作り方です。キャラクターの足元の判定を取らなければならないため、踏む幅を計算しなければなりません。dkrevel.com2020.09.18