今回はUnity上で、マリオなどのアクションゲームで目にする"下から叩くとアイテムを出すブロック"を再現してみたいと思います。Unity2Dになりますので、3Dとの違いにご注意を。
完成版のGIFです。こんな感じでニュッと出るようにしました。明らかに敵っぽい目つきですが、アイテムのフラワーです。
Unityにはcollider(コライダー)コンポーネントが準備されているので、それを各ゲームオブジェクトにアタッチすれば、簡単に"当たり判定"を付けることができます。
今回は2Dになるので、BoxCollider2Dという感じで後ろに"2D"がつきます。付け忘れると動かないので注意しましょう。
で、ボックスにとりあえずコライダーを付ければ、プレイヤーキャラとの単純な衝突は検出できるようになるので、アイテムボックスのスクリプト側にOnCollisionEnter2Dを書いて、そこに"アイテムを飛び出させるコード"を書けば何とかなりそうです。
下からだけに反応させる!
しかし!このままでは、ボックスのどの位置にプレイヤーが当たってもアイテムが飛び出してしまいます。下から当たった時にだけアイテムが出るようにするにはどうすればいいか?自分が思いついたのは"二重コライダー"です。
つまり、ブロックにコライダーを二個設置することで、プレイヤーがブロックの下に当たったかを検出するという方法です。通常のコライダー以外にトリガーモードのコライダーを下側に設置します。
少し見えにくいですが、下に少しだけ飛び出している、コライダーがあるのが分かるかと思います。(緑の四角)
要はこのコライダーでプレイヤーが下にいることを確認しつつ、メインのBOXコライダーに当たったか?という判定を行うわけです。
なお、下のコライダーはトリガーモードでの使用、といなります。
Diamondというのは、赤いダイヤ模様の部分なので無視してください。
"UnderTrigger"というのがそれです。コライダーのみアタッチされています。ヒエラルキー上では、ItemBoxのチャイルドに置かれています。
IsTriggerにチェックをいれると、トリガーモードにできます。これにより単純にここに重なっているかどうかを検出できるようになります。"当たり"はなくなります。
↓アイテムボックス本体のインスペクターはコチラです。
Box Collider 2Dがアタッチされています。これが単純にブロックとしての当たり判定になります。最終的なプレイヤーとの衝突はここが検出します。
下にある、Item Box Controllerが実際のスクリプト になります。
コードを確認してみる!
各変数から説明していきます。
"content"
まさしく箱の中身でアイテムです。今回はフラワーですが、Unityのインスペクター上でアイテムのプレファブをドラッグすれば、いろんなアイテムに入れ替えられます。
悩んだんですが、Startメソッド、つまりシーンがロードされた時点でプレファブをInstantiateしています。でSetActiveでfalseにして隠しておきます。
"movePoint"
アイテムの飛び出す位置です。本来はアイテムごとに飛び出る位置を調整する必要があるでしょうが、ここでは省略しています。Startメソッドで座標を指定しています。
ニュッと出すためにVector2.Lerpを使いますが、きちんと動くためにここで宣言しておく必要があります。(フィールドというやつです。)
"IsOpened"
これは開いたかどうか?当然最初は開いてないのでfalseです。プレイヤーがきちんと下にあたったらtrueにします。
"IsActive"
動かすのにVector2.Lerpを使うと移動し終えるまでUpdateでループさせる必要があるんですよね。で移動し終わった後もコードが実行され続けてパフォーマンス的にまずいと思うので、アイテムがきちんと出たらコレをfalseにして、つまりボックスの動作がオフになった、ということにしてコードを実行されなくするためのフラグになります。
"sr"
これはSpriteRendererのオブジェクトです。叩き終わった後、もうアイテムが入ってないよ!ってことを示すために色を変えたいんですが、その際に使います。
"m_Colliders"
このコードでもっとも重要な複数のコライダーを扱うためにBoxCollider2Dのオブジェクトを配列として宣言します。Startメソッド内でGetComponentsInChildrenを使って、ボックス本体のと下側の二つの参照を得ています。なので・・・
m_Colliders[0]はItemBoxのコライダー、m_Collider[1]は下部のトリガーをそれぞれ指すことになります。
"playerCollider"
プレイヤー自身のコライダも必要なので、オブジェクトを作り、StartメソッドでFindWityhTagを使い、参照を得ています。
実際の処理の流れ
というわけで準備は整いました!
プレイヤーがブロック下にぶつかると、どういう流れで処理が進んでいくかを、実際に説明していきたいと思います。
まずはOnCollisionEnter2Dが作動します。二つの条件を判定します。
- 下部トリガーがプレイヤーのコライダーを検出しているか?
- 本体に当たったゲームオブジェクトのタグが"Player"か?
最初の判定はm_Colliders[1].IsTouching(playerCollider)という文で行います。IsTouchingはコライダのメソッドで、引数として入力されたコライダが自分と触れ合っているかを判定し、触れていればtrueを返します。二番目は公式チュートリアルでも出てくるCompareTagで判定。
col.gameObject.CompareTag("Player")でプレイヤーのタグをチェックします。
どちらもtrueとなったら以下の文が実行されます。
content.gameObject.SetActive(true);
IsOpened = true;
StartCoroutine(WaitSwitchOff);
SetActiveでアクティブにし、IsOpenedをtrueにすることで移動の処理が始まります。
StartCoroutineはIEnumratorを使って、WaitForSecondを含むメソッドを実行する際に必要ですが、コレによりアイテムが出た後、IsActiveがfalseになるようにしています。一秒もあれば移動も終わるので、1.0f秒待機、としています。
さて!ではアイテムが飛び出るための処理です!
Updateメソッド上にあります。この処理自体をメソッド化してもよかったのですが、これだけなので、そのままにしています。
if( IsOpend && IsActive) { //移動の処理 }
まずは箱がオープンされたか?アイテムボックスは仕事を終えてないか?という条件判定です。これについては、上記で説明したとおりです。
content.transform.position = Vector2.Lerp( ~ 以下略 ;
これはつまり、フレームごとにアイテム自身の位置と目標地点を0.3fの割合で線形補間していく、というもので、要は単純に移動させるのではない、ニュッという感じの動きになります。生々しい動きになるわけです。
sr.color = Color.Lerp(sr.color, Color.gray, 0.4f);
については、正直ブロックを叩いた時点で実行していいですね。
ボックスの色を変える実行文です。
そして、先ほど言ったように移動し終わった頃には、WaiSwitchOffメソッド内の
IsActive = false;
が実行され、移動の処理は行われなくなります。
if(IsOpend && IsActive)の部分がfalse(偽)になるからです。
まとめ!
以上で、プレイヤーが下から叩いたかを判定し、アイテムを出して、ボックス上部に移動させる、という動作の実現についてのレポートになります。
まだ改善点、付け加える部分はありますが、プレイヤーをジャンプさせてブロックを叩いて、その勢いのままにアイテムが飛び出してくる、という手ごたえのあるレスポンスは再現できたのかなと思います。
というわけで、Unity上でのまともな実践を扱ったエントリはこれが初になるんですかね?ネタはいろいろあるので、ドンドンやって生きたいと思います!!