2020/11/09
というわけで、近頃は移動制御やアニメーション制御の修正が主となっており、ブログ的には話題に乏しい日々です。
間が空きすぎるとモチベーションが保てなくなるので、そのうち使いそうなものとして「乗ると崩れるブロック」を作ってみます!
主に2Dアクションゲームでよく見かける気がしますね。
「乗ると崩れる」とまでは言わずとも、「乗ると落下」とか「乗ると消える」といったブロックはかなりの確率で見かけます。
今回作るものも見た目は「乗ると崩れる」ですが、ゲーム的な仕様は「乗ると判定が消えてすり抜ける」に近い感じです。
オブジェクトとコンポーネントの用意
ブロックとして箱状のオブジェクトを使いますが、ただのCubeでは「崩れる」という表現が難しいので、Blenderで適当に分割したものを用意しておきます。
「適当に分割」と簡単に言いつつ、実際はかなり苦戦していたりします。ここで言う「分割」は「オブジェクトとしての分割」なので、ループカットやナイフツールでは頂点が増えるだけで目的を達成できません。ぐぐってもいい感じの方法がヒットしなかったため、「箱をループカット→半分選択してP
で分割→コピペ」という地味な方法をとりました。
インポートしたモデルに空のGameObjectを子オブジェクトとして追加し、RigidBody``BoxCollider
コンポーネントを設定しておきます。
この子オブジェクトは「プレイヤーが上に乗っているか」に使用するので、RigidBody
のIs Kinematic
にチェックを入れ、BoxCollider
の判定を各ブロックから少し上に動かしておきます。加えてIsTrigger
にチェックを入れておきましょう。
マテリアルやテクスチャはお好みで。岩やブロックなどの無機質なものなら無料でも良い感じのAssetがあったりするので、探してみるとよいかと。
インポートしたメッシュはUV展開しておかないと上手くテクスチャを適用できないことに注意しましょう。
子オブジェクトの衝突判定の検知
スクリプト作成前の下準備として、子オブジェクトから親オブジェクトへOnTriggerEnter
時の判定を通知できるようにしておきます。親側にBoxCollider
を2つ設定するのも手ですが、GetComponent()
した時に区別がつかなくなるのでおすすめしません。空の子オブジェクトを作って分けたのもそのためです。
以前何かの記事で紹介したと思いますが、復習を兼ねてもう一度書いておきます。
まず以下のようなスクリプトを作成し、子オブジェクトに設定します。
using UnityEngine; public class TriggerBubbling : MonoBehaviour { private GameObject Parent; void Start() { Parent = transform.parent.gameObject; } private void OnTriggerEnter(Collider c) { Parent.SendMessage("OnChildTriggerEnter", c); } }
続いて親オブジェクト側にブロックを制御するためのスクリプト (ThroughBlock
クラス) を設定します。先ほどの子オブジェクトでトリガーを検知するとOnChildTriggerEnter()
が呼ばれるので、これを実装しておきます。
using UnityEngine; public class ThroughBlock : MonoBehaviour { protected BoxCollider boxCollider; void Start() { boxCollider = gameObject.GetComponentInChildren<BoxCollider>(); } void Update() { } private void OnChildTriggerEnter(Collider c) { if (c.gameObject.tag == "Player") { Debug.Log("test"); } } }
これで子オブジェクトの判定を親オブジェクト側で通知できるようになりました。
判定の制御
「崩れる」と書くと大袈裟ですが、ゲーム的は「判定をなくす」だけなので、ロジック的にはシンプルです。先ほどのThroughBlock
クラスにあれこれ書いていきます。
using System.Collections.Generic; using System.Linq; using UnityEngine; public class ThroughBlock : MonoBehaviour { protected BoxCollider boxCollider; protected List<Rigidbody> separateBlocks; void Start() { boxCollider = GetComponent<BoxCollider>(); separateBlocks = GetComponentsInChildren<Rigidbody>().ToList(); } private void OnChildTriggerEnter(Collider c) { if (c.gameObject.tag == "Player") { boxCollider.enabled = false; var random = new System.Random(); var min = -3; var max = 3; separateBlocks.ForEach(r => { r.isKinematic = false; var vect = new Vector3(random.Next(min, max), 0, random.Next(min, max)); r.AddForce(vect, ForceMode.Impulse); }); } } }
見た目的に「崩れる」感じを出すため、OnTriggerEnter()
時に分割していたCubeをバラバラに物理演算させます。System.Random
クラスを使ってAddForce()
の掛かる方向をランダムにします。
崩したブロックが全て綺麗な直方体なのでかなり違和感がありますが、そのあたりは気にしないようにします!
「バラバラになったブロック」はエフェクトのような視覚的なものなので、「キャラクター」のオブジェクトとはレイヤーを分けて衝突しないようにしています。
逆にレイヤーを分けなければ、本当の意味で”崩れるブロック”を表現できます。挙動がややこしくなりますが、もうちょっと大きめのオブジェクトを分割するのであれば有かもしれませんね。
あとがき
ということで、「乗ると崩れるブロック」を作ってみました。
応用すれば「乗ってから崩れるまでの猶予を付ける」「ちくわブロックのように判定を伴ったまま落下」などもできるので、やる気になった方はトライしてみてください。