ゴマちゃんフロンティア

気まぐれと勢いで作るUnityゲーム開発日記です

【Unity】「下からすり抜け可能な床」を3D版Colliderで実現する方法

time 2017/06/11

というわけで、今回のお題は「下からすり抜け可能な床」です!

現実的には原理の分からない床なのですが、アクションゲームでは割と定番の要素です。特に2Dゲームでよく見かける気がしますが、3Dゲームでもたまにあったりします。
また、ゲームによっては下方向への入力で上から下へすり抜けられるものもあります。

この手の話はぐぐればヒットするのですが、そのほとんどは2D版のCollider系コンポーネントを使用しているようです。自分は3Dで作っているので使うことができません。
そんなわけで、3D版コライダーで実現する方法について考えてみます。

下から上へのすり抜け

上手くいかなかった方法

3DオブジェクトPlaneを配置し、その下からジャンプすることで「それっぽい挙動には」なります。
しかし、Planeの判定内でジャンプを止めた場合、物理演算の再計算が入った際に吹っ飛びます。

うーん・・・これでは使い物にならなそうです。
ついでにペラペラなコライダーのみではなく、もう少し厚みのある判定でもすり抜けられるようにしたいですね。

ちなみにこの方法を知ったのはUnity4.3の頃で、その時はこんな強烈な吹き飛び方はしなかった気がします。Unity5で物理演算が大幅に変わったとかあったので、その影響かもしれませんね。

判定による物理演算の切り替え

となれば、スクリプトからコライダーの判定を制御するしかありません。
すり抜け床の下に判定を持たせ、プレイヤーが入ってきたらすり抜けるように切り替えてあげます。

この手のシチュエーションではOnCollisionEnter()を使いたくなりますが、プレイヤーキャラクターがCharacterControllerを使用しているため、Collision系では判定できません。また、「CharacterControllerの判定(≒CapsuleCollider)のTrigger系関数がやや遅れて実行される」という現象に見舞われたため、キャラクターの下に1つ子オブジェクトを追加し、それにBoxColliderを持たせて制御することにしました。
子オブジェクトは判定できるように「TriggerCollider」というタグをつけておきます。

上のカプセル状のコライダーがCharacterControllerのもので、箱状のコライダーが今回追加したBoxColliderです。
大きさはキャラクターに合わせてください。

すり抜け床も「床に乗るためのコライダー」と「すり抜け制御に関するコライダー」とで分けてしまいます。

すり抜け用コライダーとなる子オブジェクトを追加した上で以下のスクリプトを設定し、トリガー接触時の判定を親オブジェクトへ伝達するようにします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TriggerBubbling : MonoBehaviour {

    public bool call_enter;
    public bool call_exit;

    private GameObject Parent;

    void Start () {
        Parent = transform.parent.gameObject;
    }

    private void OnTriggerEnter(Collider c) {
        if (call_enter) {
            Parent.SendMessage("OnChildTriggerEnter", c);
        }
    }

    private void OnTriggerExit(Collider c) {
        if (call_exit) {
            Parent.SendMessage("OnChildTriggerExit", c);
        }
    }
}

SendMessage()の実行先に関数がないとエラーが出てしまうので、インスペクター上でコールするか選択できるようにしました。今回はEnterのみですね。

次に親オブジェクト側にすり抜けを制御するスクリプトを追加します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SlidingFloor : MonoBehaviour {

    private BoxCollider fllorCollider;

    void Start () {
        fllorCollider = this.GetComponent<BoxCollider>();
    }

    private void OnChildTriggerEnter(Collider c) {
        if (c.gameObject.tag == "TriggerCollider") {
             fllorCollider.isTrigger = true;
        }
    }

    private void OnTriggerExit(Collider c) {
        if (c.gameObject.tag == "TriggerCollider") {
            fllorCollider.isTrigger = false;
        }
    }
}

OnChildTriggerEnter()TriggerBubblingから呼ばれます。Unity標準ではないので注意してください。
isTriggerを切り替えるとすり抜けます。enabledを切り替えてもいけますが、その場合はOnTriggerExit()が呼ばれません。
そのあたりは後述のレイヤーマスク切り替えで対応しています。

挙動自体はいい感じですね!

レイヤーマスクの制御

下から上へのすり抜けはできましたが、isTriggerがtrueになっている間はプレイヤー以外のオブジェクトもすり抜けてしまいます。ということで、「Player」レイヤーのオブジェクトのみを通すようにしてみます。
厳密には「ぶつかってきたオブジェクトのみ」を通したいところですが、やり方が思いつかなかったのでレイヤー制御で妥協しました。

手始めに、すり抜け床のレイヤーを専用のものに切り替えます。
今回はそのまんまな「SlidingFloor」というレイヤーを追加しました。

このとき「子オブジェクトも変えます?」と聞かれますが、変えてしまうと判定用のコライダーが機能しなくなってしまうので、親オブジェクトのみを切り替えるようにしましょう。
また、子オブジェクトのTriggerBubblingでExit時も呼ばれるようにチェックを入れておきます。

あとは先程のSlidingFloorクラスを少し修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SlidingFloor : MonoBehaviour {

    private int player_layer;
    private int sliding_floor_layer;

    void Start () {
        // 各レイヤー情報を取得
        player_layer = LayerMask.NameToLayer("Player");
        sliding_floor_layer = LayerMask.NameToLayer("SlidingFloor");
    }

    private void OnChildTriggerEnter(Collider c) {
        if (c.gameObject.tag == "TriggerCollider") {
             Physics.IgnoreLayerCollision(player_layer, sliding_floor_layer);
        }
    }

    private void OnChildTriggerExit(Collider c) {
        if (c.gameObject.tag == "TriggerCollider") {
            Physics.IgnoreLayerCollision(player_layer, sliding_floor_layer, false);
        }
    }
}

自身のコライダーの参照が要らなくなったので、コードの長さは大して変わっていません。
Physics.IgnoreLayerCollision()でレイヤー同士の衝突有無を設定できます。判定内に入ったらすり抜けるように衝突を無効化し、判定から出たら衝突を有効化します。
Physics.IgnoreLayerCollision()の第3引数にfalseを設定することで再度有効になるよう戻すことができます。

挙動自体はisTrigger版と変わらないので割愛します。

上から下へのすり抜け

せっかくなので、「床の上にいるとき、下入力ですり抜ける」挙動も作ってみました。
ゲーム的には2Dなので、下への移動入力を判定して制御します。ただし、その入力判定制御をSlidingFloorに実装するのはおかしい気がするので、PlayerクラスのUpdate()内に書き足します。

// 地上にいるときに下入力した場合の処理
if (Input.GetAxis("Vertical") < -0.5f && charMotor.IsGrounded()) {
    // すり抜け床の上にいる場合はレイヤーマスク変更
    int sliding_floor_layer = LayerMask.NameToLayer("SlidingFloor");
    if (Physics.Raycast(transform.position, -transform.up, sliding_floor_layer)) {
        int player_layer = LayerMask.NameToLayer("Player");
        Physics.IgnoreLayerCollision(player_layer, sliding_floor_layer);
    }
}

charMotorCharacterMotorのインスタンスです。
GetAxis()で指定する軸はゲームによって違うかもしれないので注意してください。自分の場合はデフォルトのVerticalを使いました。
あとは適当に下にレイを放ち判定するだけ!

うん、いい感じではないでしょうか。

あとがき

ということで、すり抜け可能な床を実装してみました!
メジャー所でよく見かけるものでも、仕組みを考えてみると結構難しかったりしますね。
これがあると上下にステージを展開しやすいので助かります。

スポンサーリンク

down

コメントする



ツイッター