今までで移動、ジャンプ、アニメーション、カメラをプレイヤーに追従と様々なものを作ってきました。今回から敵を作っていきましょう。
↑の動画でも解説しています。わからない、うまくいかない事があったら質問される前に、一回、動画の方で手順を確認してください
この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。
<マリオのクリボーみたいな敵を作ってみよう・その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です。
では、これで再生してみましょう。

とりあえず、これで敵と接触する判定を作れました。
では、敵と接触したらダウンするようにしましょう。以前作成したアニメーションがあると思います。

アニメーションの事がわからない、忘れてしまったという人は↓の記事へどうぞ
さて、以前はアニメーションを線でつないでいましたが、今回は繋がずに行こうと思います。
何故なら、やられモーションはどの状態からでも遷移する上に、ダウンした後は何の状態にも遷移しないからです。
まぁ、要はたくさん線を繋ぐのがめんどくさいし、メリットもないので省略しましょうという事です。ダウンに関しては行ったり来たりがないですからね。
その為に追加するコードは↓です。
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
}
何かうまくいかない事があった場合は↓の記事を参考にしてみてください
最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。
また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。