質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.40%

  • Unity2D

    1347questions

Unityのスライスで外枠だけ広げたいです

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 799

melonplus

score 3

 前提・実現したいこと

UnityでSlicedを使ってこのようなspriteを外枠の青の濃いところの太さは変えず広げたいです
イメージ説明イメージ説明
こんな感じです

 発生している問題

普通はこうですが
イメージ説明イメージ説明
Sprite Editerで四角にボーダーを区切って
イメージ説明
Sizeを変えるとこのように四角になってしまいます
イメージ説明イメージ説明

色々試しましたが出来ませんでした。
どなたか方法を教えていただけますでしょうか。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

このような形状だと、9スライス方式では対応は困難そうですね...
まだうまくいくか試してはいないのですが、淡青色部分と濃青色部分にスプライトを分けて、2つを重ね描きするような方針でどうでしょうか?
拡縮の際は、内側の淡青色部分の大きさを「濃青色部分の大きさ - (境界幅 × 2)」にして、濃青色部分よりも境界の幅の分だけ小さくする感じで...

 [追記]

ご提示の画像のようなシンプルな外観のピンなら、先に申し上げた内部・外部の2枚スプライト方式で問題ないかと思いますが、外部に装飾を加えたりして派手にすると、拡大すると装飾が隠れてしまったり、縮小すると本来は見えない部分が見えてしまったりするかと思います。

別パターンとして、スプライトは当初の通り内部・外部を統合した1枚とし、Sprite Rendererの本来のSprites-Defaultsマテリアルの代わりにカスタムマテリアルを使って、拡縮率に応じてテクスチャのサンプリング位置をずらして外周部の太さを維持するという手も使えそうです。

勉強を兼ねて、下記のようなSprites-Defaultsを改造したマテリアルを作成してみました。

Shader "Sprites/Pin" // 名前を変更
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
        [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0

        // 下記追加プロパティの各値はUV座標系で指定する
        _HeadX ("Head X", Float) = 0.5 // ピン頭X
        _HeadY ("Head Y", Float) = 0.68 // ピン頭Y
        _InnerRadius ("Inner Radius", Float) = 0.23 // 淡色部半径
        _OuterRadius ("Outer Radius", Float) = 0.32 // 濃色部半径
        _TipDist ("Tip Distance", Float) = 0.68 // ピン頭中心からピン先端までの距離
    }

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

        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex SpriteVert
            #pragma fragment PinFrag // フラグメントシェーダーを差し替え
            #pragma target 2.0
            #pragma multi_compile_instancing
            #pragma multi_compile _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
            #include "UnitySprites.cginc"

            #define EPSILON 0.00001 // 分母に0が来る可能性のある場合にエラーを回避するための微小値(プロパティ等の入力値由来の除算部分は正常な値が入っているものとみなして対策を省略)

            float _HeadX; // ピン頭X
            float _HeadY; // ピン頭Y
            float _TipDist; // ピン頭中心からピン先端までの距離
            float _InnerRadius; // 淡色部半径
            float _OuterRadius; // 濃色部半径

            // SpriteFragに代わりこちらを使う
            fixed4 PinFrag(v2f IN) : SV_Target
            {
                // 以下、先端をT、頭の中心をO、Tから頭に接線を引いた時の接点をC、現在の注目点をPとして表す
                // 座標はOを原点とする...よってOは(0, 0)を示す
                float2 headPosition = float2(_HeadX, _HeadY); // 頭の中心のUV座標
                float cosTheta = clamp(_OuterRadius / _TipDist, -1.0, 1.0); // 線分OTと線分OCがなす角のコサイン
                float sinTheta = sqrt(1.0 - cosTheta * cosTheta); // 線分OTと線分OCがなす角のサイン
                float2 dBoundary = float2(sinTheta, -cosTheta); // 頭領域と先端領域を分ける半直線の向き
                float3x3 m = unity_ObjectToWorld; // モデル変換行列
                float2 scale = float2(length(float3(m[0][0], m[1][0], m[2][0])), length(float3(m[0][1], m[1][1], m[2][1]))); // モデル変換行列から求めた水平・垂直の拡縮率
                float2 pP = IN.texcoord - headPosition; // Pの座標
                float2 signPPX = float2(sign(pP.x), 1.0); // PのX成分の符号...float2に掛けるのを簡潔にするため、こちらもfloat2にした
                pP *= signPPX; // 左右対称なので、単純にするために右半分だけで計算し、最後に符号を合わせることにする
                float pDist = length(pP); // PのOからの距離
                float2 dP = pP / max(pDist, EPSILON); // Oから見たPの向き
                float cosPhi = -dP.y; // 線分OTと線分OPがなす角のコサイン
                float cosPsi = dot(dBoundary, dP); // 線分OCと線分OPがなす角のコサイン
                float scaleP = length(normalize(dBoundary / scale) * scale); // 線分OP方向における拡縮率
                float borderWidth = _OuterRadius - _InnerRadius; // 境界の幅
                float scaledBorderWidth = borderWidth / scaleP; // 拡縮率補正後の境界幅
                float scaledInnerRadius = _OuterRadius - scaledBorderWidth; // 拡縮率補正後の淡色部半径
                float radiusMultiplier = lerp(1.0, 1.0 / max(cosPsi, EPSILON), step(cosTheta, cosPhi)); // 半径の補正係数...Pが頭領域にあるなら1.0、先端領域なら1.0 / cosPsi
                float pInnerRadius = _InnerRadius * radiusMultiplier; // Pにおける淡色部半径
                float pScaledInnerRadius = scaledInnerRadius * radiusMultiplier; // Pにおける拡縮率補正後の淡色部半径
                float pOuterRadius = _OuterRadius * radiusMultiplier; // Pにおける濃色部半径
                float pIsInOuterRegionOrOutOfBounds = step(pScaledInnerRadius, pDist); // Pが補正後淡色部半径よりも外側か
                float pIsOutOfBounds = step(pOuterRadius, pDist); // Pが濃色部半径よりも外側か
                float pScaledDistForInnerRegion = pDist * pInnerRadius / pScaledInnerRadius; // 淡色部の場合のPのOからの拡縮率補正後距離
                float pScaledDistForOuterRegion = pInnerRadius + (pDist - pScaledInnerRadius) * (pOuterRadius - pInnerRadius) / (pOuterRadius - pScaledInnerRadius); // 濃色部の場合のPのOからの拡縮率補正後距離
                float pScaledDistForOutOfBounds = pDist; // 形状範囲外の場合のPのOからの拡縮率補正後距離
                float pScaledDist = lerp(lerp(pScaledDistForInnerRegion, pScaledDistForOuterRegion, pIsInOuterRegionOrOutOfBounds), pScaledDistForOutOfBounds, pIsOutOfBounds); // 合成したPのOからの拡縮率補正後距離
                float2 samplingPoint = dP * (pScaledDist * signPPX) + headPosition; // テクスチャ色のサンプリング位置...符号を戻して、頭中心の座標を足してUV座標とする

                fixed4 c = SampleSpriteTexture(samplingPoint) * IN.color;
                c.rgb *= c.a;
                return c;
            }
        ENDCG
        }
    }
}

伸縮のために拡縮率を調べる必要がありますが、これはオブジェクトのモデル変換行列を手がかりとしています。そのため、Sprite RendererのSizeではなく、TransformのScaleを操作して拡縮すると伸縮するという動作になっています。
十分調査していないのですが、Sprite RendererのSizeを操作すると、変換行列による拡縮ではなく、ポリゴンの頂点モデル座標が直接書き換えられるのかもしれません。そうだとすると、そこから拡縮率を求めるのは一筋縄ではいかなそうに思えました(ジオメトリーシェーダーを挟んで、そこで求めることができるかもしれませんが、コードが複雑になりそうです)。
モデル変換行列から拡縮率を調べている都合上、そのスプライトオブジェクトのScaleだけでなく、親以上の階層のScaleを操作しても同じく伸縮が起こります。場合によってはこの動作は都合が悪いかもしれませんが、その場合はやはり先に述べた2枚スプライト方式を使うということになるでしょうか。

拡縮してみると下図のようになります。左3つはカスタムマテリアル方式、中3つは2枚スプライト方式、右3つは特別な処理をしていない通常の拡縮です。
2枚スプライト方式だと、拡縮率が1未満では内側領域に隠れていた外側領域内部が見えるようになり、拡縮率が1より大きいと外側領域が内側領域によって隠れていきます。

プレビュー

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/17 15:52

    回答ありがとうございます。
    なるほど、やってみます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.40%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Unity2D

    1347questions