いんでぃーづ

ゲームいろいろ、いろいろ自由

uGUIシェーダー:フキダシをわきわき動かす

年末にダウンロード版で買った ペルソナ5 をいまさらずっとやってて、このゲームはUI表現の宝庫ですね。勉強になります。

おもしろい表現たくさんあるのですが、キャラクターのセリフが乗ってるフキダシ枠の端っこが動くのがおもしろかったので、マネしてみました。

f:id:sugar_affordance:20190109172405g:plain
わきわき動く吹き出し

最終的なシェーダーコード

開幕からシェーダーのコードをぶっぱなします。これは2ラウンド目開始直後にザンギエフ *1 でクリティカルアーツ *2 を空ぶる *3 に等しい所業です。
ちなみにスト5 *4 も買いました。安かったからね。

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "Custom/Wobble"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0

        // 各頂点をどこまで動かすか
        _MovePosition00 ("Move Position 00", Vector) = (0, 0, 0, 0)
        _MovePosition01 ("Move Position 01", Vector) = (0, 0, 0, 0)
        _MovePosition10 ("Move Position 10", Vector) = (0, 0, 0, 0)
        _MovePosition11 ("Move Position 11", Vector) = (0, 0, 0, 0)
        _Scale ("Scale", Float) = 10
        _CycleSec ("Cycle Sec", Float) = 1
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile __ UNITY_UI_CLIP_RECT
            #pragma multi_compile __ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;

            // propertiesを受ける変数
            fixed4 _MovePosition00;
            fixed4 _MovePosition01;
            fixed4 _MovePosition10;
            fixed4 _MovePosition11;
            half _Scale;
            half _CycleSec;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);

                // texcoordとTimeから膨らませる位置を決定
                half cycle = abs(sin(((_Time / _CycleSec) / _CycleSec) * 360));
                float4 target =
                  (_MovePosition00 * (saturate(1 - v.texcoord.x) * saturate(1 - v.texcoord.y))) +
                  (_MovePosition01 * (saturate(1 - v.texcoord.x) * saturate(v.texcoord.y))) +
                  (_MovePosition10 * (saturate(v.texcoord.x) * saturate(1 - v.texcoord.y))) +
                  (_MovePosition11 * (saturate(v.texcoord.x) * saturate(v.texcoord.y)));
                v.vertex.xy = v.vertex.xy + (target * cycle * _Scale).xy;

                OUT.worldPosition = v.vertex;
                OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

                OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

                OUT.color = v.color * _Color;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                #ifdef UNITY_UI_CLIP_RECT
                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

                return color;
            }
        ENDCG
        }
    }
}

元コードがあるのでライセンス表示

Copyright (c) 2016 Unity Technologies

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

前準備

順番前後しますが、前準備を説明します。 (上のコードを全部コピペすれば動くので、厚顔無恥な人は一番最後のマテリアル設定まで飛んでも問題ありません)

上のコードは全部一から書いたわけではなく、Unityの公式サイトからダウンロードしたuGUI用デフォルトシェーダーをカスタマイズしています。

下の公式サイトから ビルトインシェーダー をダウンロードしましょう。

unity3d.com

f:id:sugar_affordance:20190109173039p:plain

解凍して DefaultResourcesExtra/UI/UI-Default.shader を自分のプロジェクトにコピーし、適当に名前を変えます。

変更した点は以下の三点。

properties の追加

    Properties
    {
        ...

        // 各頂点をどこまで動かすか
        _MovePosition00 ("Move Position 00", Vector) = (0, 0, 0, 0)
        _MovePosition01 ("Move Position 01", Vector) = (0, 0, 0, 0)
        _MovePosition10 ("Move Position 10", Vector) = (0, 0, 0, 0)
        _MovePosition11 ("Move Position 11", Vector) = (0, 0, 0, 0)
        _Scale ("Scale", Float) = 10
        _CycleSec ("Cycle Sec", Float) = 1

MovePositionXX は、各四隅の点をどのくらい膨らませるかの数値です。0, 1 の値はそれぞれ texcoord の値に対応しています。
Scale は数値の係数です。RectTransformのスケール感に合わせる必要があるのでややこしいんですよね。
CycleSec はわきわきを何秒ごとに繰り返すかをあらわす周期です。

変数定義

            // propertiesを受ける変数
            fixed4 _MovePosition00;
            fixed4 _MovePosition01;
            fixed4 _MovePosition10;
            fixed4 _MovePosition11;
            half _Scale;
            half _CycleSec;

頂点シェーダー

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);

                // texcoordとTimeから膨らませる位置を決定
                half cycle = abs(sin(((_Time / _CycleSec) / _CycleSec) * 360));
                float4 target =
                  (_MovePosition00 * (saturate(1 - v.texcoord.x) * saturate(1 - v.texcoord.y))) +
                  (_MovePosition01 * (saturate(1 - v.texcoord.x) * saturate(v.texcoord.y))) +
                  (_MovePosition10 * (saturate(v.texcoord.x) * saturate(1 - v.texcoord.y))) +
                  (_MovePosition11 * (saturate(v.texcoord.x) * saturate(v.texcoord.y)));
                v.vertex.xy = v.vertex.xy + (target * cycle * _Scale).xy;

                OUT.worldPosition = v.vertex;

                ...

「もっとやりかたあったやろ」と言われそうですが、とりあえず動いたのでよしとしましょう。
重要なのは下で

                float4 target =
                  (_MovePosition00 * (saturate(1 - v.texcoord.x) * saturate(1 - v.texcoord.y))) +
                  (_MovePosition01 * (saturate(1 - v.texcoord.x) * saturate(v.texcoord.y))) +
                  (_MovePosition10 * (saturate(v.texcoord.x) * saturate(1 - v.texcoord.y))) +
                  (_MovePosition11 * (saturate(v.texcoord.x) * saturate(v.texcoord.y)));

入力点を四隅のどれか一つと判定して、プロパティで指定した方向にふくらませます。

マテリアルの設定

シェーダーが問題なくコンパイルできたら、マテリアルに設定します。

f:id:sugar_affordance:20190109173926p:plain
マテリアル設定

変数を微調整してあそびましょ。

ロゴに使ってみる

こんな感じにしてみました

ロゴ

*1:ストリートファイターシリーズのキャラクター。投げ技が得意

*2:いわゆる超必殺技。クリティカルアーツはストリートファイター5シリーズでの名前で、ゲージを全て消費して発動するすごく強い技

*3:相手に当たっていないのでゲージ全損になる、いわゆる"やっちゃった"行動

*4:ストリートファイター5の略称。最近の格ゲーに多い有料DLCキャラクターまとめてシーズンとして売り出す方法をとっているが、これも時流ってやつですかねえ


“Unity” and Unity logos are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere, and are used under license.


免責事項

当サイトの広告バナー、リンクによって提供される情報、サービス内容について、当サイトは一切の責任を負いません。

また、当サイトの情報を元にユーザ様が不利益を被った場合にも、当サイトは一切の責任を負いません。

すべて自己責任でお願いします。