Transparency Sort ModeがCustom Axisの時に前後が入れ替わる位置を表示する

初めに

2Dのゲームで画面を立体的に見せるためにY軸が下にあるものほど手前にオブジェクト描画するという技法があります。

これ「Isometric Sorting」と呼ぶのかわからないですが(自分の中ではY軸ソートと呼んでいました)が、Unityでは Transparency Sort Mode = Custom Axis、Transparency Sort Axis を X=0, Y=1, Z=1★(これ重要) とすることで実現できます。

(まぁUnityの場合、Z軸があるのでそっちで前後を制御したほうが柔軟かもしれませんが。

詳しくはこっち

http://tsubakit1.hateblo.jp/entry/2017/04/14/024945

Universal RP の時の Transparency Sort Mode の設定方法はこっち

https://takap-tech.com/entry/2020/04/28/211622

で、前後関係を設定できるのですが、この時前後が入れ替わるタイミングは画像の中心(Pivotを無視した画像の矩形の中心)ですが Z=1 にしている場合でZの位置を変更している場合どこで前後が入れ替わるのかわかりません。

そこで画像がシーンビュー上で前後が入れ替わる場所に線を引くコンポーネントを作成しました。

コンポーネントをアタッチするとこんな感じなります。

f:id:Takachan:20200917225634g:plain
前後が入れ替わる位置が線で表示される

実装コード

シーンエディター上にGizmoな線を引いています。

// TransparencyHelper.cs

using UnityEngine;

namespace Takap.Utility
{
    /// <summary>
    /// CustomAxisがY=1,Z=1の時に前後関係が入れ替わる場所に補助線を引きます
    /// </summary>
    [RequireComponent(typeof(SpriteRenderer))]
    public class TransparencyHelper : MonoBehaviour
    {
        // 
        // 説明:
        // オブジェクトにアタッチすると
        // 2Dで制作時の Transparency Sort Mode = 'CustomAxis' で各軸の設定が
        // X=0, Y=1, Z=1(★) の時にどの位置で前後関係が入れ替わるかを表示する補助線をSceneエディター状に表示します。
        // 
        // 補助線を引く以外の機能はありません。
        // 

#if true
        // 常に表示が必要な場合 #if 'true'
        protected void OnDrawGizmos()
#else
        // オブジェクトが選択されているときだけ表示が必要なら #if 'false'
        protected void OnDrawGizmosSelected()
#endif
        {
            var t = this.transform;
            Vector3 wpos = t.GetPos();

            var sr = this.GetComponent<SpriteRenderer>();
            float harfWidth = sr.bounds.size.x / 2.0f;

            // Y軸ソートはPivotなどに関係なく Sprite の中心で起きるのでそれを考慮する
            float z = sr.bounds.center.y + wpos.z;
            GizmoDrawer.DispCrossMark(new Vector2(sr.bounds.center.x,
                sr.bounds.center.y + wpos.z), 0.1f, Color.red);

            // Gizmoで位置を横線でする
            var s = new Vector3(sr.bounds.center.x - harfWidth - harfWidth * 0.5f, z, wpos.z);
            var e = new Vector3(sr.bounds.center.x + harfWidth + harfWidth * 0.5f, z, wpos.z);
            Gizmos.color = Color.green;
            Gizmos.DrawLine(s, e);
        }
    }
}

画像サイズが途中で変わる場合

このZの位置ですが画像の見かけの大きさが変わっても中心からの距離で設定されます。

なので途中で画像の大きさが変わったときにサイズを追従してほしい場合コンポーネントをオブジェクトにアタッチします。

コンポーネントのインスペクターに基準の値を設定するとそれ以降常に更新し続けてくれるようになります。

f:id:Takachan:20200917230029g:plain
大きさを変えた時も線も位置を自動で更新する

// TransparencyUpdater .cs

using UnityEngine;

namespace Takap.Utility
{
    /// <summary>
    /// CustomAxisがY=1,Z=1の時に前後関係が入れ替わる場所に補助線を引きます
    /// </summary>
    [ExecuteAlways]
    [RequireComponent(typeof(SpriteRenderer))]
    public class TransparencyUpdater : MonoBehaviour
    {
        // 
        // 説明:
        // オブジェクトにアタッチすると
        // 2Dで制作時の Transparency Sort Mode = 'CustomAxis' で各軸の設定が
        // X=0, Y=1, Z=1(★) の時にどの位置で前後関係が入れ替わるかを表示する補助線をSceneエディター状に表示します。
        // 
        // 画像のスケールが変更されたことをフレームごとに検出して
        // Zの位置を自動的に設定した位置に追従するように調整します
        //

        //
        // Inspector
        // - - - - - - - - - - - - - - - - - - - -

        // 基準倍率
        [SerializeField] private float baseScaleY = 1.0f;
        // 基準倍率の時のZの位置
        [SerializeField] private float zPosition = 0;

        //
        // Fields
        // - - - - - - - - - - - - - - - - - - - -

        // キャッシュ
        private Transform myTransform;
        // 直前のサイズ
        private Vector3 previousScale;

        //
        // Runtime impl
        // - - - - - - - - - - - - - - - - - - - -

        protected void Start()
        {
            this.myTransform = this.transform;

            this.updateZPosition();
        }

        protected void Update()
        {
            var scale = this.myTransform.lossyScale;

            if (Mathf.Approximately(scale.x, this.previousScale.x) &&
                Mathf.Approximately(scale.y, this.previousScale.y))
            {
                return;
            }

            this.updateZPosition();
        }

#if true
        // 常に表示が必要な場合 #if 'true'
        protected void OnDrawGizmos()
#else
        // オブジェクトが選択されているときだけ表示が必要なら #if 'false'
        protected void OnDrawGizmosSelected()
#endif
            var t = this.transform;
            Vector3 wpos = t.GetPos();

            var sr = this.GetComponent<SpriteRenderer>();
            float harfWidth = sr.bounds.size.x / 2.0f;

            // Y軸ソートはPivotなどに関係なく Sprite の中心で起きる
            float z = sr.bounds.center.y + wpos.z;
            GizmoDrawer.DispCrossMark(new Vector2(sr.bounds.center.x,
                sr.bounds.center.y + wpos.z), 0.1f, Color.red);

            // デバッグ用の表示
            var s = new Vector3(sr.bounds.center.x - harfWidth - harfWidth * 0.5f, z, wpos.z);
            var e = new Vector3(sr.bounds.center.x + harfWidth + harfWidth * 0.5f, z, wpos.z);
            Gizmos.color = Color.green;
            Gizmos.DrawLine(s, e);
        }

        // 現在のYの位置に応じてZの位置を更新する
        private void updateZPosition()
        {
            float z = this.myTransform.lossyScale.y / this.baseScaleY * this.zPosition;
            this.myTransform.SetPosZ(z);
            this.previousScale = this.myTransform.lossyScale;
        }
    }
}