-100p

-10p

+10p

+100p

Unity分割テレポートシェーダー

Unity uGUI用、分割テレポートシェーダー。画像をスライスして座標移動。
(Slice Teleport Shader for Unity uGUI)

関連ページ
昔のSFCや初期PSの作品では、キャラクターが粒子状になって画面上方に向かいテレポートする演出がよくあった。
ああ言うのが作りたいなと思って、x軸に画像を分割してそれっぽい処理が作れたので共有。ただちょっと使いにくいかも。

このシェーダーを作成後に更に研究を重ねて 粒子テレポートシェーダー も作れたのでそちらも併せてどうぞ。
似たようなカテゴリーで、 ドラクエのワープ演出っぽいシェーダー もある。

分割テレポートシェーダー概要

まず実装結果のサンプル動画は次の通り。

今回のシェーダーは事前に対象画像の座標やスケール情報をC#からシェーダーに受け渡す必要がある。
この辺りの処理が複雑で説明するのも少し面倒なので、SliceTeleportControllerというC#の仲介クラスも一緒に作っておいた。
実装に必要なシェーダー、マテリアル、C#スクリプトをまとめてUnityPackage化して、こちら にアップロードしてある。

なおSliceTeleportControllerではTweenという機能を使ってアニメーションを実装している。
このため、あらかじめ DOTween をアセットストアからインポートしておく必要あり。無料で便利なのでオススメ。

TweenではなくUnity Coroutineで対応したい場合は、SliceTeleportController内のコードを少し書き換える必要がある。
複雑なことはやっていないので、書き換える場合もそんなに難しくないと思う。

後述するがテレポート演出させたい画像に対しては、結構制約がある。
例えばSpriteEditorでスライスしてある画像や、アトラス内にパッケージしてある画像は上手く座標移動ができない。
周囲に透明部分のマージンがない画像は謎の線が走ったりするし、MeshTypeが適切でないと奇妙に画像が途切れる。

分割テレポートシェーダーの実装の仕方

SliceTeleport.UnityPackageをインポートすると、Projectに3つのファイルが追加される。

SliceTeleportControllerはSliceTeleportマテリアルを参照し、SliceTeleportマテリアルはSliceTeleportシェーダーを参照している。

まずはSliceTeleportControllerを、ImageコンポーネントがアタッチされたGameObjectにアタッチする。
するとSliceTeleportController内のReset関数によって、自動でメンバ変数の「Target」と「Canvas」に代入が実行される。

代入が実行されなかった場合(Imageコンポーネントがアタッチされてなかった場合など)はDebug.LogErrorが表示される。

アニメーションテスト

これだけでほぼ前準備は終わりなので、続いてテストに入る。
UnityのPlayボタンを押して実行状態にし、SliceTeleportControllerを右クリック、すると下の方にデバッグコマンドが出てくる。


DebugDoTeleportがテレポートアニメーションの再生、DebugUndoTeleportがテレポートアニメーションの逆再生になる。
下はSliceTeleportController内のC#のコード。
#if UNITY_EDITOR
    [ContextMenu("DebugDoTeleport")]
    private void DebugDoTeleport()
    {
        DoTeleport(8, 1.5f);
    }

    [ContextMenu("DebugUndoTeleport")]
    private void DebugUndoTeleport()
    {
        UndoTeleport(8, 1.5f);
    }
#endif

アニメーションの細かい設定

SliceTeleportController内にDoTeleportとUndoTeleportがpublic関数として用意してある。
他のC#スクリプトからこの関数にアクセスし、アニメーションを実行できる。
第1引数の跳躍距離は、ピクセル距離とかではなく自分のImageの高さの何倍かという値になるので注意。
    
/// <summary>

    
/// テレポートを実行

    
/// </summary>

    
/// <param name="distance">テレポートの跳躍距離</param>

    
/// <param name="duration">テレポートにかかる時間</param>

    public void DoTeleport(float distance, float duration)
    {
        SetBaseMaterialValue();
        target.material.SetFloat("_Distance", distance);
        target.material.SetFloat("_TeleportPow", 0);

        DOTween.To(() => target.material.GetFloat("_TeleportPow"), (val) =>
        {
            target.material.SetFloat("_TeleportPow", val);
        }, 1, duration).SetEase(Ease.InQuad);
    }

    
/// <summary>

    
/// テレポートの逆再生

    
/// </summary>

    
/// <param name="distance">テレポートの跳躍距離</param>

    
/// <param name="duration">テレポートにかかる時間</param>

    public void UndoTeleport(float distance, float duration)
    {
        SetBaseMaterialValue();
        target.material.SetFloat("_Distance", distance);
        target.material.SetFloat("_TeleportPow", 1);

        DOTween.To(() => target.material.GetFloat("_TeleportPow"), (val) =>
        {
            target.material.SetFloat("_TeleportPow", val);
        }, 0, duration).SetEase(Ease.OutQuad).OnComplete(() =>
        {
            target.material.SetFloat("_Distance", 1);
            target.material.SetFloat("_PosY", 0);
        });
    }

他の機能として、x軸にスライスする回数の変更と、色の変更処理がある。
それぞれ、DoTeleportやUndoTeleportを呼ぶ前に実行する必要がある。

下はスライス回数変更のSliceTeleportController.SetSeliceCount関数。
    
/// <summary>

    
/// テレポートする際の画像の分割数を変更する。1~20の指定が可能

    
/// </summary>

    public void SetSeliceCount(int sliceCount)
    {
        this.sliceCount = sliceCount;
    }
引数のsliceCountに2を渡すとx軸に3分割、3を渡すと5分割、4を渡すと7分割になる。
つまり1 + (sliceCount - 1) * 2みたいな計算式で我ながら少し分かり辛い。

下はSliceCountを操作した際の見た目の違い。20まで上げると最早ただの矢印になる。

上の動画ではシェーダー内のプロパティに設定してある[HideInspector]を削除し、C#を介さずに直接マテリアルのSliceCountを弄っている。
    Properties
    {
        
//以下4つの変数は適切にImageの座標を調整するために必要

        [HideInInspector]_CanvasScreenHeight("CanvasScreenHeight", float) = 1000
        [HideInInspector]_PosY("PosY", float) = 0
        [HideInInspector]_ScaleY("ScaleY", float) = 1
        [HideInInspector]_ImageHeight("ImageHeight", float) = 100

        [Space(10)]
        [HideInInspector]_SliceCount("SliceCount", Range(1, 20)) = 4 
//テレポートする際の画像の分割数

        [HideInInspector]_Distance ("Distance", Range(1, 20)) = 1 
//テレポートの移動距離

        [HideInInspector]_TeleportPow ("TeleportPow", Range(0, 1)) = 0 
//テレポートの進捗、1で完全にテレポート完了


        [Space(10)]
        [Toggle] 
        [HideInInspector]_UseMonoCol("UseMonoCol", float) = 1 
//テレポートの途中で単一色に変化するかどうか

        [HideInInspector]_MonoCol("MonoCol", Color) =  (1.0, 1.0, 1.0, 1.0) 
//単一色の色

    }
動画を撮るために一時的に[HideInspector]を削除したが、今回のシェーダーはちょっとデリケートなのであまりお勧めしない行為。
特にPosYやScaleYを無闇に弄るとエラいことになる。SliceTeleportControllerからのアクセスに限定させた方が安全。

続いて色の変更処理について。このシェーダーは転送が進むにつれて画像が単一色に変化するが、その色を変更できる。
下がその変更処理のSliceTeleportController.SetColor関数。
    
/// <summary>

    
/// テレポートする際の単一色の指定

    
/// </summary>

    public void SetColor(Color color)
    {
        isUseMonoCol = true;
        monoCol = color;
    }
下の動画は、色を変更するとどうなるかのサンプル。


また単一色の変化していく効果を切ることもできる。
下はそのSliceTeleportController.UnsetColor関数のコードとサンプル動画。
    
/// <summary>

    
/// テレポートする際に単一色に変化させない設定

    
/// </summary>

    public void UnsetColor()
    {
        isUseMonoCol = false;
    }

実装する際の注意点

テレポートシェーダーを適用させる画像は独立した画像である必要がある。
例えば下のようなキャラチップをまとめた画像があったとする。

SpriteEditorで適切に分割。

分割した一番最初の画像をImageに割り当てたとする。


この状態のImageコンポーネントに対し、テレポートシェーダーを使うと次のようになる。

色々おかしいが、まずもって画像がx軸に分割されていない。
画像全体に対しては分割処理をしているが、対象のキャラチップは画像全体の左端にあり、その狭い範囲では分割されていない。
テレポートする際の画像の座標移動も、キャラチップ単体ではなく、画像全体を基準にして実行している。

似た理屈で、画像をアトラス化してしまうと上手くシェーダーが機能しない。


まだ独立した画像であっても、周囲に透明マージンがないとおかしな事になる。
例えば下のような画像があったとする。見ての通り周囲に全くマージンがない。


これに対しテレポートシェーダーを走らせると次のようになる。

上下に謎の線が走っているのが確認できる。
内部的なことを言うと、このシェーダーはプロパティのDistanceの値の分だけ、頂点シェーダーでy軸に画像を引き伸ばしている。
この引き伸ばし処理の時に、画像端に接してる部分のピクセル色をそのままコピーし続けてしまう。

なので下の画像のように、周囲に透明部分を作って画像端のピクセル色はα=0である必要がある。

画像を用意しただけでは不十分で、インスペクター上で対象画像のMeshTypeをFullRectにする必要がある。

こうしないと親切にもUnity側で勝手にマージン部分を切り詰めてしまう。

ここまで調整すると、まともなテレポート演出が確認できる。
0
0

-100p

-10p

+10p

+100p