i-school - 【2D】トンネル表現演出の実装例
 2Dトップダウン(トップビュー)ゲームにおける、トンネルなどの画像の重なりの表現についての実装例です。



設計


 コライダーと OnTriggerStay2D と Exit2D を活用し、コライダー内部にいる場合、アサインさせている画像の表示優先順位よりもプレイヤーの優先順位を下げることで、
トンネル内や、建物内部にいるかのようにプレイヤーを見えない状態に切り替え、コライダー外に出た時に元の優先順位にもどすように制御します。

 このような制御を加えることで、各オブジェクトごとに画像の優先順位の切り替えが可能になり、2Dゲーム内でのトンネルなどの表現が実現できます。


描画関係


 描画についてと、移動範囲の制御については別々の処理として考えます。

 描画用のゲームオブジェクトと、その切り替えを行うゲームオブジェクトとは別々に用意する方式です。
このように分離して管理することで、切り替え対象の画像をアサイン変更するだけで変更可能になります。


<見えないコライダーのゲームオブジェクト 配置例>



<見えないコライダーのゲームオブジェクト インスペクター例>



 この場合には、トンネル部分に見えないコライダーのゲームオブジェクトを配置し、
この部分に侵入した際に画像の表示優先順位を切り替えて、トンネル部分が最前面にくるように制御します。
そのようにすることでキャラが隠れ、あたかもトンネル内にいるかのように演出することが可能です。


Tunnel スクリプトの作成


 描画の優先順位変更トリガー用のオブジェクトにアタッチするスクリプトです。
先ほどもお伝えした通り、トリガー用のオブジェクトと画像のゲームオブジェクトは別々で考えてください。

 tunnelSprite 変数にアサインされているゲームオブジェクトの SpriteRenderer を制御することで、画面の表示優先順位を切り替える仕組みです。


using UnityEngine;

/// <summary>
/// 描画の優先順位変更トリガー用のオブジェクトにアタッチするスクリプト
/// トリガー用のオブジェクトと画像のゲームオブジェクトは別々で考える
/// </summary>
public class Tunnel : MonoBehaviour
{
    [SerializeField] private Renderer tunnelSprite;  //キャラや敵よりも描画の優先順位の高い画像。今回はトンネル用の画像だが、どのゲームオブジェクトのRendererでもいい。

    public int TunnelSpriteOrderNum => tunnelSprite.sortingOrder;  //トンネル用の画像のOrder in Layerの値のプロパティ
}


SpriteOrderSwitcher スクリプトの作成


 プレイヤーや敵などの移動するキャラにアタッチし、表示の優先順位を変更するスクリプトです。
プレイヤーの表示優先順位をすべての移動キャラに適用してしまうと、敵側の表示が正しい優先順位で表示されません。
(例えば、プレイヤーはトンネル内であったとしても、すべての敵がトンネル内にいるとは限りません。)

 よって、このスクリプトは、移動するキャラごとに個別にアタッチし、それぞれが自分自身の位置によって表示優先順位を決定するようにします。
そのようにすることで画面の整合性が保たれるように制御できます。


using UnityEngine;

/// <summary>
/// プレイヤーや敵などのキャラにアタッチし、表示の優先順位を変更するスクリプト
/// </summary>
public class SpriteOrderSwitcher : MonoBehaviour
{
    private SpriteRenderer sr;     // アタッチしたキャラの SpriteRenderer
    private int defaultOrderNum;   // SpriteRenderer の初期値。優先順位を元の状態に戻すにときに使う

    private void Reset() {
        if(transform.GetChild(0).TryGetComponent(out sr)) {
            Debug.Log($"SpriteRenderer 取得しました : {sr}");
        } else {
            Debug.Log($"SpriteRenderer 取得できません : {sr}");
        }

        // 初期値設定
        defaultOrderNum = sr.sortingOrder;
    }

    private void Start() {
        Reset();
    }
    
    private void OnTriggerStay2D(Collider2D other) {
        
        // 今回の場合、キャラ(プレイヤーや敵)がトンネルに侵入している間
        if (other.TryGetComponent(out Tunnel tunnel)) {
            
            // このキャラの表示優先順位をトンネルよりも低い値に設定する
            // 固定値で値をセットせず変数で指定することで、トンネルにアサインされている画像に対応できる
            sr.sortingOrder = tunnel.TunnelSpriteOrderNum - 1;
        }
    }

    private void OnTriggerExit2D(Collider2D other) {
        
        // 今回の場合、キャラ(プレイヤーや敵)がトンネルから出たとき
        // out キーワード時の変数を _ にすると、Tunnnel クラスを取得はする(判定はする)が、取得した情報は if 文内で使わないことを明示的に示せる
        if (other.TryGetComponent(out Tunnel _)) {
            
            // このキャラの表示優先順位を元の値に戻す
            sr.sortingOrder = defaultOrderNum;
        }
    }
}


作成したスクリプトを必要なゲームオブジェクトにアタッチする


 キャラの侵入判定用の見えないコライダーのゲームオブジェクトに Tunnel スクリプトをアタッチします。
TunnelSprite 変数には、トンネルなどの、キャラよりも表示優先順位を上に変えたいゲームオブジェクトをアサインして利用します。

 移動するキャラには SpriteOrderSwitcher スクリプトをアタッチします。



キャラの侵入判定用の見えないコライダーのゲームオブジェクト



移動するキャラ



ゲームを実行して動作を確認する


 プレイヤー用キャラを移動させて、見えないコライダーのゲームオブジェクトに侵入してください。
同時に画像の表示優先順位が切り替わり、プレイヤー用キャラが見えなくなれば制御成功です。

 その後、見えないコライダーのゲームオブジェクトの範囲から抜けたとき、再度、プレイヤー用キャラが画面に表示されれば、こちらも制御成功です。

 どのような処理が行われ、どのような挙動になれば適切であるかを自分で判断できるようにしましょう。
そのためには、デバッグ前に処理の見直しを行い、どのようになる想定なのかをイメージした上で、デバッグを行っていくようにしてみてください。


複数階層構造時のコライダーの切り替え


 上記の処理に加えて、階段の上下移動による複数階層構造時のコライダーの切り替え機能の実装例です。

 例えば、キャラが2階部分にいる際にはトンネル部分は通行不可にする必要があり、
逆にトンネル内にいる場合には2階部分を通行不可にする必要があります。

 それらの切り替えを侵入位置により切り替える方法でのアプローチです。


MapManager スクリプトを作成する


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

//階段を登ったら、トンネル出入り用のコライダーのオン、オフを切り替える
public class MapManager : MonoBehaviour
{
    public bool isClimbedStairs = false;  //階段を登ったか、登ってないか(建物の上にいるか、下にいるか)

    //切り替えるコライダー
    [SerializeField] private Collider2D leftTunnelWall;
    [SerializeField] private Collider2D rightTunnelWall;
    [SerializeField] private Collider2D gateTopWall;
    [SerializeField] private TilemapCollider2D wall_switchTilemap;


    /// <summary>
    /// 階段を登ったかどうか(建物の上にいるかどうか)を判定し、それに合わせて各地のコライダーのオンオフを切り替える
    /// (これを切り替えないと、意図しない場所でキャラが引っかかって通れない場所が発生したりする)
    /// </summary>
    /// <param name="isClimbedStairs">階段を登ったか、登ってないか(建物の上にいるか、下にいるか)</param>
    public void JudgeClimbedStairs()
    {
        //階段を登った(建物の上にいる)際の処理
        if (isClimbedStairs)
        {
            leftTunnelWall.enabled = false;
            rightTunnelWall.enabled = false;
            gateTopWall.enabled = true;
            wall_switchTilemap.enabled = true;
        }

        //階段を降りた(建物の下にいる)際の処理
        if (!isClimbedStairs)
        {
            leftTunnelWall.enabled = true;
            rightTunnelWall.enabled = true;
            gateTopWall.enabled = false;
            wall_switchTilemap.enabled = false;
        }

        Debug.Log("コライダーのオン、オフを切り替えました");
    }
}


MapManager ゲームオブジェクトを作成し、MapManager スクリプトをアタッチして設定を行う


 ヒエラルキーの空いている部分で右クリックを行いメニューを開き、Create Empty を選択します。
MapManager に名前を変更してください。

 続いて、先ほど作成した MapManager スクリプトをアタッチします。

 複数のアサイン用の変数がありますので、コライダーを持つゲームオブジェクトより、制御したいゲームオブジェクトをアサインしてください。
ここでは階層構造用に用意した見えないコライダーのゲームオブジェクトをそれぞれアサインしています。


<アサイン例>












SpriteOrderSwitcher スクリプトの修正を行う


 以前の手順で作成してある SpriteOrderSwitcher スクリプトの修正を行い、複数階層構造のコライダー切り替えに対応させます。


using UnityEngine;

/// <summary>
/// プレイヤーの描画処理とマップのコライダー処理
/// </summary>
public class SpriteOrderSwitcher : MonoBehaviour
{
    private SpriteRenderer sr;  //アタッチしたキャラのSpriteRenderer

    private int defaultOrderNum;  //SpriteRendererの初期値。優先順位をもとの状態に戻すときに使う

    private float xCoordinate;  //OnTriggerEnter時にx座標を記録しておく
    private float yCoordinate;

    [SerializeField] private MapManager mapManager;

    [SerializeField] private int TunnnelSpriteOrderNum;  //トンネルの描画値


    void Start()
    {
        Reset();       
    }

    private void Reset()
    {
        if (transform.GetChild(0).TryGetComponent(out sr))
        {
            defaultOrderNum = sr.sortingOrder;

            //Debug.Log($"SpriteRendererを取得しました:{sr}");
        }
    }

    /// <summary>
    /// コライダーに入った時、xまたはy座標を記録する
    /// </summary>
    /// <param name="other"></param>
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.gameObject.name == "LayerTrigger0")  //<= 今回の場合には階段は2つあり、それぞれ向きが違うのでゲームオブジェクトの名前で分岐
        {
            yCoordinate = transform.position.y;

            Debug.Log($"yCoordinate:{yCoordinate}");
        }

        if (other.gameObject.name == "LayerTrigger1")
        {
            xCoordinate = transform.position.x;
        }
    }

    /// <summary>
    /// コライダーに出入りする際の座標の差に応じてコライダーと描画を切り替える
    /// </summary>
    /// <param name="col"></param>
    private void OnTriggerExit2D(Collider2D col)
    {
        if (col.gameObject.name == "LayerTrigger0")
        {
            //コライダーから出た時と入った時の座標が一定以上離れていたら
            if (Mathf.Abs(yCoordinate - transform.position.y) > 2f)
            {
                //真偽値を反転させる
                mapManager.isClimbedStairs = !mapManager.isClimbedStairs;

                mapManager.JudgeClimbedStairs();

                //キャラの描画順をトンネルよりも高くする
                SwitchSpriteOrder(mapManager.isClimbedStairs);
            }
        }

        if (col.gameObject.name == "LayerTrigger1")
        {
            if (Mathf.Abs(xCoordinate - transform.position.x) > 2f)
            {
                mapManager.isClimbedStairs = !mapManager.isClimbedStairs;

                mapManager.JudgeClimbedStairs();

                SwitchSpriteOrder(mapManager.isClimbedStairs);
            }
        }

        //次回の判定に備えて、各変数を初期化
        xCoordinate = 0;
        yCoordinate = 0;
    }

    /// <summary>
    /// 各キャラの描画順の切り替え
    /// </summary>
    /// <param name="bringToFront">キャラの描画をbase_switchよりも前面に持ってくるかどうか</param>
    private void SwitchSpriteOrder(bool bringToFront)
    {
        if (bringToFront)
        {
            sr.sortingOrder = TunnnelSpriteOrderNum + 2;
        }
        else
        {
            sr.sortingOrder = defaultOrderNum;
        }

        //Debug.Log("SwitchSpriteOrderが動きました");
    }
}


階段部分などに見えないコライダーのゲームオブジェクトを作成して配置する


 コライダーの切り替え対象となる階段部分に見えないコライダーのゲームオブジェクトを作成してください。
画像の切り替えも発生する場合には、こちらに Tunnel スクリプトをアタッチして、切り替え対象となる画像を持つゲームオブジェクトをアサインしてください。


ゲームを実行して動作を確認する


 階段部分に侵入したり、抜けたりして、階層構造に対応したコライダーの切り替えが出来ているか、確認してください。

 こちらのゲームの階段部分に採用されています。

Unityroom
Space Survivors

https://unityroom.com/games/spacesurvivors



 以上で完成です。