2020/11/09
というわけで、先日やっとUnityを2017にアップデートしました。
まだリリースノートもまともに読んでいなかったりしますが、ブログ的に古いUnityのままやっていくのもアレなので上げてしまいました。既存プロジェクトが問題なく動いたので大丈夫でしょう。
そんなところで、今回のお題は「アクションゲームっぽい消える足場」です!
物理法則を無視することが多いステージギミックの中でも特に強烈なものですが、アクションゲームにおいては最早突っ込むのも無粋なくらい登場します。
単に一定間隔で生成・消滅するものもあれば、「スイッチ押下後の一定時間のみ」といったものもあります。
今回はその実装方法について考えてみます。
基本的な考え方
アクションゲームでよく見る消える足場といえば、「一定時間ごとに足場を生成・消滅させる」ような挙動です。これを間に受けて実装すると、オブジェクトの生成・削除を足場数分繰り返すことになり、効率が悪いです。
ここは「判定の有効・無効」と「マテリアルの色変更」を使い、1オブジェクトで生成・消滅を繰り返すようにします。ゲーム的にはあたかも生成・消滅を繰り返しているような感じになり、挙動も問題ないはずです。
ということを念頭に置き、Let’sコーディング (`・ω・´)
足場用オブジェクト作成
足場となるオブジェクトを作成します。特にこだわらずCube
をベースに適当にマテリアルを設定します。
最近購入したテクスチャ系アセット内にちょうどいいものがありました。
後々透明にする関係上、マテリアルのレンダリングモードはFade
にしておきます。Transparent
ではアルファ値を0にしても完全に消えませんが、足場消滅時も出現位置を示唆するという意味では有りかもしれません。
制御用スクリプト作成
以降「生成」「消滅」といった言葉を用いますが、前述したように「判定の有効・無効」「色変更」によって実現するので、「足場としての機能」のことを指します。
所謂「言葉の綾」というやつなので、いい感じに脳内補完していただければと思います。
足場の生成と消滅
足場用のスクリプトを作成し、生成&消滅のロジックを考えます。
パッと浮かぶ方法は「生成・消滅時間を管理するカウンタ」をフィールドに持たせ、Update()
内で加算し、一定値以上になったら生成・消滅の処理を行いつつカウンタをリセットする方法です。
が、「フィールド変数が増える」「Update()
内の記述が多くなる」「時間をフレーム単位で指定する必要がある」などの理由からあまりスマートとは言えません。
ということで、前回も使ったDOTween
のSequence
で組み立ててみることにしました。
using DG.Tweening; using UnityEngine; public class DisappearBlock : MonoBehaviour { public float enable_time; public float disable_time; public float animate_time = 0.5f; private Collider boxCollider; private Material material; void Start () { boxCollider = this.GetComponent<Collider>(); material = this.GetComponent<MeshRenderer>().material; Sequence sequence = createSequence(); sequence.Play(); } /// <summary> /// ループ処理用シーケンスを作成する /// </summary> private Sequence createSequence() { // Sequence生成 Sequence sequence = DOTween.Sequence(); // 無限ループにする sequence.SetLoops(-1); // 判定無効化 sequence.AppendCallback( () => boxCollider.isTrigger = true ); sequence.Append( DOTween.ToAlpha( () => material.color, color => material.color = color, 0, animate_time ) ); // 消滅時間分だけ待機 sequence.AppendInterval(disable_time); // 判定有効化 sequence.AppendCallback( () => boxCollider.isTrigger = false ); sequence.Append( DOTween.ToAlpha( () => material.color, color => material.color = color, 1f, animate_time ) ); // 生成時間分だけ待機 sequence.AppendInterval(enable_time); return sequence; } }
DOTween
超便利!
今回のキモである「数秒待つ→有効化→数秒待つ→無効化…」というロジックを組み立てるのがとてもやりやすいです。加えて間にコールバックでTween系以外の処理を挟めるのもGOODです。
ここでは透明度を戻す手前のコールバックで無効化していますが、これをコールバック後に記述すると完全に透明になってからすり抜けるようになります。作成しているゲームに合わせて選択しましょう。
動かすと上のような感じです。
足場復活時の衝突判定
機能的にはこれで十分ですが、まだ1つ問題があります。
復活時に他のコライダーと重なってしまうと、重なったオブジェクトが吹っ飛んでしまいます。キャラクターオブジェクトでこれが起きると大問題です。
UnityのAPI的には、「2つの判定が重ならない位置を割り出す」ための関数としてComputePenetration()
があり、本ブログでも以前紹介していたりします。
詳細は上記記事にありますが、2D系アクションに適用するのは厳しいので、素直に自分でずらすことにします。
先ほどのcreateSequence()
を書き換えてみます。
public Vector3 overlapShiftAmount; /// <summary> /// ループ処理用シーケンスを作成する /// </summary> private Sequence createSequence() { // Sequence生成 Sequence sequence = DOTween.Sequence(); // 無限ループにする sequence.SetLoops(-1); // 判定無効化 sequence.AppendCallback( () => boxCollider.isTrigger = true ); sequence.Append( DOTween.ToAlpha( () => material.color, color => material.color = color, 0, animate_time ) ); // 消滅時間分だけ待機 sequence.AppendInterval(disable_time); // 判定有効化 sequence.AppendCallback( () => { boxCollider.isTrigger = false; Collider[] overLapColliders = Physics.OverlapBox(transform.position, boxCollider.bounds.extents); foreach(var c in overLapColliders) { if (c.gameObject.tag == "Player") { c.transform.Translate(overlapShiftAmount); } } } ); sequence.Append( DOTween.ToAlpha( () => material.color, color => material.color = color, 1f, animate_time ) ); // 生成時間分だけ待機 sequence.AppendInterval(enable_time); return sequence; }
判定有効化時のコールバック関数内でPhysics.OverlapBox
を用いて調べ、PlayerタグがついたオブジェクトをTranslate()
で直にずらしてしまいます。
ずらす方向としてoverlapShiftAmount
フィールドを定義し、足場の配置位置に合わせて変えれるようにします。
また、足場用オブジェクトにRigidBody
を設定すると計算する間もなく吹っ飛んでしまうので注意します。物理演算は不要なので付けなくてもよいでしょう。
フレームレートが低すぎてさっぱり分かりませんが、一応下にずれています。
あとがき
そんなわけで、アクションゲームっぽい消える足場を実装してみました。
最近友人の家で「スーパーファミコン」あたりのレトロゲームをやっておりますが、昔の2Dアクションもギミックはかなり豊富ですね。そのあたりをUnity風に作ってみるのも面白いかもしれません。
にしてもDOTween
便利で助かります。もっと多用してもいいかも。