前回のパートではプレイヤーを十字キーで動かすことに成功した。
しかし、壁やアイテムをすり抜けてしまう状態だった。
このゲームでプレイヤーの移動を阻む物体は、壁と敵の2つであり、敵はまだ作っていないのでこのパートでは壁について対処していく。
まずは壁をすり抜けないようにし、さらに記事の後半ではプレイヤーが壁を破壊できるようにもする。
前回の記事はこちら。
⇒ パート④:プレイヤーを移動できるようにする@2Dローグライク公式tutorial解説【Unity2018】
※この記事は、unity公式チュートリアル「2D Roguelike tutorial」を解説した連載記事です。
この連載記事共通の事項(実施している環境等)は以下の記事を参照ください。
⇒ 表紙:2Dローグライク公式tutorial解説【Unity2018】
壁をすり抜けないようにする
MovingObjectの修正
前回作成したMovingObject
はPlayer
の親クラスとした。
これは今後作る敵クラスでも同じように処理させたいためだったことを思い出そう。
このパートのはじめで壁と敵の2つが移動を妨げるものといったが、それはプレイヤー視点の話であり、
敵から見れば、壁とプレイヤーが移動を妨げるものとなる。
これは「xxと△△がぶつかったら動けない」という風に言い換えられる。
つまり、プレイヤーと敵の「ぶつかったら動けない」という処理は同じなので、
MovingObject
に処理を書くことができる。
MovinObject
の変更はStart()
とMove()
、そしてMove()
を呼び出すAttemptMove()
である。
public abstract class MovingObject : MonoBehaviour { public LayerMask blockingLayer;//←追加 private BoxCollider2D boxCollider;//←追加 //その他のフィールドは変更なしなので省略 protected virtual void Start() { this.rb2d = GetComponent<Rigidbody2D>(); this.boxCollider = GetComponent<BoxCollider2D>(); //←追加 this.inverseMoveTime = 1f / moveTime; } protected bool Move(int xDir, int yDir, out RaycastHit2D hit) { Vector2 start = transform.position; Vector2 end = (start + new Vector2(xDir, yDir)); this.boxCollider.enabled = false; hit = Physics2D.Linecast(start, end, this.blockingLayer); this.boxCollider.enabled = true; if (hit.transform == null) { StartCoroutine(SmoothMovement(end)); return true; } return false; } protected virtual void AttemptMove(int xDir, int yDir) { RaycastHit2D hit; bool canMove = Move(xDir, yDir, out hit); } //その他のメソッドは変更なしなので省略 }
BoxCollider2Dをアタッチする
上記の修正を適応した状態で、一度unityエディタ側に戻ろう。
Start()
メソッドでは、BoxCollider2D
というコンポーネントを取得しているので、
BoxCollider2D
コンポーネントがアタッチされていることが前提でこのスクリプトは動いている。
これは前回のパートで登場したRigidbody2D
と同じだ。
さっそく「Player」プレハブにBoxCollider2D
をアタッチしよう。
コライダーとは物理衝突範囲を定義するコンポーネントである。
物理演算を用いて衝突したことを感知(=「当たり判定」)させたいゲームオブジェクトにつける、ということをまずは覚えておこう。
BoxCollider2D
は四角形に当たり判定範囲を指定することができるコライダーの1つであり、
当たり判定範囲は、Sceneビューで緑の線で囲まれた範囲となる。
緑の線に囲まれた範囲に何かが当たると当たったことを感知できるゾ!
BoxCollider2D#Offset#Size
をXYともに0.9に変えて範囲のサイズを変え、回りに余裕を持たせておこう。
ちなみに「Edit Collider」を押すと、Sceneビューでもマウスカーソルでサイズを調整できる。
これで「Player」ゲームオブジェクトは当たったことを感知できる物体となった。
移動可能かチェックする
MovingObject#Move()
の説明に戻ろう。
壁をすり抜けない=壁にぶつかりそうだったら移動しない、という処理はどのように行えば良いだろうか。
様々な方法があるとは思うが、
ここでは移動する前に移動する予定の位置に壁があるかどうかをチェックし、なければ移動する。あれば移動しない。という風に実装しよう。
移動する予定の位置に壁があるか?はPhysics2D#Linecast
を用いてチェックすることができる。
hit = Physics2D.Linecast(start, end, this.blockingLayer);
Physics2D#Linecast
は引数で渡すstartからendの位置にレイ=光線を引き、
線上にコライダーがアタッチされたゲームオブジェクトがあるかどうかを返す。
物体に当たった場合は、戻り値のRaycastHit2D
にそのゲームオブジェクトの情報を詰めて返す。
最後の引数である blockingLayer の型はLayerMask
だ。これはインスペクターで指定するLayerの事である。
このLayerを設定したゲームオブジェクトのみ当たり判定対象とすることができる。
上記のコードは自分がいる位置から移動予定の位置まで線を引き、その間に指定したLayerが設定されている物体があるかどうかをチェックしていることになる。
Physics2D#Linecast()
の前後で自身のコライダーをon/offしている点に注意しよう。
これは自分自身にアタッチしているコライダーも当たり判定に引っかかってしまうためだ。
this.boxCollider.enabled = false; hit = Physics2D.Linecast(start, end, this.blockingLayer); this.boxCollider.enabled = true;
上図は、
Physics2D#Linecast
で引いている線がどのようになっているのかをDebug.DrawLine
を用して表示してみたものだ。
移動する都度、赤く表示した線上に物体があるかどうかをチェックしていることがわかる。
Layer の設定
ここまでの説明で、まだ足りていない設定がある。unityエディタ側に戻ろう。
移動を妨げる物体は、プレハブの種類で言うと、プレイヤー・敵・内壁・外壁の4つだった。
まだ作成していない敵以外の3種類のすべてのプレハブのLayerを「BlockingLayer」にする。
移動を妨げる=ブロックする物体という意味合いだ。
そして、「Player」プレハブのPlayer#blockingLayer
で「BlockingLayer」を選択し、
移動を妨げるレイヤーを指定する。
また、Physics2D#Linecast()
で当たり判定ができるのはコライダーだけだった。
「Player」プレハブにははすでにつけていたので、残りの内壁・外壁プレハブにも BoxCollider2D
をつけよう。
敵プレハブは作ったときに設定することとする。
ここまで壁をすり抜けないようにする準備が整った。ゲームを起動して確認してみよう。
内壁を破壊できるようにする
このパートはまだ終わらない。
このゲームではプレイヤーは壁を破壊できる仕様なのである。破壊できる壁は、内壁のみで外壁は破壊できない。
Wall
新規に「Wall」スクリプトを作成しよう。
プレイヤーが壁を破壊しようとした時に、Wall#DamageWall()
を呼びだす想定である。
DamageWall()
では、壁のHPを引数で受け取るダメージ量の分だけ減らしていく。
そして、HPが0になった時にこのオブジェクトは非アクティブにして壁を破壊してしまおう。
using UnityEngine; public class Wall : MonoBehaviour { public Sprite dmgSprite; //破壊されているときの画像 public int hp = 4; //内壁のhp private SpriteRenderer spriteRenderer; void Awake() { spriteRenderer = GetComponent<SpriteRenderer>(); } public void DamageWall(int loss) { spriteRenderer.sprite = dmgSprite; hp -= loss; if (hp <= 0) { gameObject.SetActive(false); } } }
移動の妨げになった物体が何かを判別する
次に壁を壊すプレイヤー側の実装を行う。
MovingObject
に新規にOnCantMove()
メソッドを追加する。
これは移動できなかった際に呼ばれる処理を書くメソッドでabstractだ。
protected abstract void OnCantMove<T>(T component) where T : Component;
abstract メソッドは親クラスで定義だけ行い、実際の処理は子クラスで実装することを強制するものだ。
つまり親クラスのMovingObject
では「移動できなかった時に行う処理」とだけ定義しておき、「壁が邪魔で移動できなかった時は、壁を破壊する」という実際の処理は子クラスのPlayer
で書くこととなる。
T
はComponent
型である。Component
型はゲームオブジェクトにアタッチされる全てのコンポーネントの基底クラスである。
このメソッドでは、移動できない要因となった物体の情報をComponent
型で受け取り、子クラス側で処理を行う。
例えば、移動後の位置に壁があり移動できなかった、といった場合は、Component
型に「壁」という情報がComponent
型で渡ってくる。ということだ。
MovingObject
の変更はAttemptMove()
と新規に追加するOnCantMove()
だ。
protected virtual void AttemptMove<T>(int xDir, int yDir) where T : Component { RaycastHit2D hit; bool canMove = Move(xDir, yDir, out hit); if(hit.transform == null){ return; //何も当たってない } T hitComponent = hit.transform.GetComponent <T> (); if(!canMove && hitComponent != null){ //移動できなかったし、当たったものがある OnCantMove (hitComponent); } } protected abstract void OnCantMove<T>(T component) where T : Component;
当たり判定を行ったRaycastHit2D
で当たったものがある場合、
以下のように当たったものを取得することができる。
この後、Player
クラスにて、T
にはWall
を指定する。
壁と当たった場合は、GetComponent()
でWall
が取れるという訳である。
T hitComponent = hit.transform.GetComponent <T> ();
次にPlayer
だ。
親のMovingObject
に加えたメソッド定義の変更はPlayer
にも適応する必要がある。
OnCantMove()
でWall#DamageWall()
を呼びwallDamage
で定義したダメージ量を与える。
using UnityEngine; public class Player : MovingObject { public int wallDamage = 1; private void Update() { if (!GameManager.instance.playersTurn) return; int horizontal = 0; int vertical = 0; horizontal = (int)(Input.GetAxisRaw("Horizontal")); vertical = (int)(Input.GetAxisRaw("Vertical")); Debug.Log(vertical +"," + horizontal); if (horizontal != 0) { vertical = 0; } if (horizontal != 0 || vertical != 0) { AttemptMove<Wall>(horizontal, vertical); //壁の当たり判定をチェックする } } protected override void AttemptMove<T>(int xDir, int yDir) { base.AttemptMove<T>(xDir, yDir); GameManager.instance.playersTurn = false; } protected override void OnCantMove<T>(T component) { Wall hitWall = component as Wall; //Tが壁なら hitWall.DamageWall (wallDamage); } }
「Wall」ゲームオブジェクトの設定
ここまでで壁を破壊する準備が整ったのでUnityエディタ側に戻ろう。
内壁プレハブに作成したWall
コンポーネントをアタッチする。
Wall#dmgSprite
には壁がダメージを受けた時の画像を設定しよう。
Wall#hp
は変えても変えなくてもどちらでも良い。
プレハブ名 | dmgSprite |
---|---|
Wall1 | Scavengers_SpriteSheet_48 |
Wall2 | Scavengers_SpriteSheet_49 |
Wall3 | Scavengers_SpriteSheet_50 |
Wall4 | Scavengers_SpriteSheet_51 |
Wall5 | Scavengers_SpriteSheet_52 |
Wall6 | Scavengers_SpriteSheet_52 |
Wall7 | Scavengers_SpriteSheet_53 |
Wall8 | Scavengers_SpriteSheet_54 |
ここまで出来たらさっそくゲームを起動してプレイヤーが壁を破壊できることを確認してみよう。
次回予告
このパートではRaycastHit2D
を用いて当たり判定を行い、壁とプレイヤーがぶつかる時の処理を実装した。
次のパートではプレイヤーがアイテムとぶつかる時の処理を実装する。
壁と違い、プレイヤーはアイテムを入手するので、このパートで行ったような移動の妨げは行わず、
今回とはまた違った方法で当たり判定を行う方法が登場する。
⇒ 次パートはこちら