Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice
  2. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  3. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

9 Slice code for non-Experimental.

Discussion in '2D Experimental Preview' started by SevenPointRed, Dec 7, 2016.

  1. SevenPointRed

    SevenPointRed

    Joined:
    Feb 3, 2016
    Posts:
    218
    I got fed up of waiting for Unity to release 9 Slice. So here is a simple class to do it yourself.
    Not heavily tested, might have some issues as I only wrote it quickly to do some simple UI ( e.g. wont work with tight packing or rotation). Allows you to 9 Slice existing Sprites. Just assign the Sprite and the default Sprite material.
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using UnityEditor.Callbacks;
    3. #endif
    4.  
    5. namespace UnityEngine.Sprites
    6. {
    7.     [ExecuteInEditMode]
    8.     [RequireComponent(typeof(MeshRenderer))]
    9.     [RequireComponent(typeof(MeshFilter))]
    10.     public class SlicedSprite : MonoBehaviour
    11.     {
    12.         [Header("Target")]
    13.         public Sprite sprite;
    14.         public Material material;
    15.  
    16.         [Header("Info")]
    17.         public Color colour = Color.white;
    18.         public Mode mode = Mode.NineSlice;
    19.  
    20.         private MeshFilter meshFilter { get { return GetComponent<MeshFilter>(); } }
    21.         private MeshRenderer meshRenderer { get { return GetComponent<MeshRenderer>(); } }
    22.  
    23.         public enum BoundsMode
    24.         {
    25.             None = 0,
    26.             Fit = 1
    27.         }
    28.  
    29.         public enum Mode
    30.         {
    31.             Single = 0,
    32.             NineSlice = 1
    33.         }
    34.  
    35.         [Header("9-Slice")]
    36.         public float sliceWidth = 1f;
    37.         public float sliceHeight = 1f;
    38.  
    39.         [Header("Bounds")]
    40.         public float boundsWidth = 1f;
    41.         public float boundsHeight = 1f;
    42.  
    43.         [Header("Scaling")]
    44.         public BoundsMode boundsMode = BoundsMode.None;
    45.  
    46.         [Header("Debugging")]
    47.         public Rect textureRect;
    48.         public Vector2 textureRectOffset;
    49.         public Rect rect;
    50.         public Texture2D texture;
    51.  
    52.         private Mesh mesh
    53.         {
    54.             get
    55.             {
    56.                 return Application.isPlaying ? meshFilter.mesh : meshFilter.sharedMesh;
    57.             }
    58.             set
    59.             {
    60.                 if (Application.isPlaying) meshFilter.mesh = value;
    61.                 if (!Application.isPlaying) meshFilter.sharedMesh = value;
    62.             }
    63.         }
    64.  
    65. #if UNITY_EDITOR
    66.         [SerializeField]
    67.         private int insCheckId;
    68. #endif
    69.  
    70.         void Awake()
    71.         {
    72.             ApplyRendererProps();
    73.             Reload();
    74.         }
    75.  
    76. #if UNITY_EDITOR
    77.         private void OnEnable()
    78.         {
    79.             meshFilter.hideFlags = HideFlags.HideInInspector;
    80.             meshRenderer.hideFlags = HideFlags.HideInInspector;
    81.         }
    82.  
    83.         private void OnDisable()
    84.         {
    85.             meshFilter.hideFlags = HideFlags.None;
    86.             meshRenderer.hideFlags = HideFlags.None;
    87.         }
    88.  
    89.         void OnDrawGizmosSelected()
    90.         {
    91.             //Duplicate/Copy paste fix.
    92.             if (insCheckId != GetInstanceID())
    93.             {
    94.                 Debug.Log("Instance id changed, reloading mesh");
    95.                 insCheckId = GetInstanceID();
    96.                 mesh = null;
    97.                 Reload();
    98.             }
    99.             if ( !Application.isPlaying && mode == Mode.NineSlice)
    100.             {
    101.                 Reload();
    102.             }
    103.             if (boundsMode != BoundsMode.None)
    104.             {
    105.                 Gizmos.DrawWireCube(transform.position, new Vector3(boundsWidth * transform.localScale.x, boundsHeight * transform.localScale.y, 0.1f * transform.localScale.z));
    106.             }
    107.         }
    108.  
    109.         /// <summary>
    110.         /// This is used to apply the atlas texture to the sprite on play/build.
    111.         /// </summary>
    112.         [PostProcessScene(2)]
    113.         public static void OnPostprocessScene()
    114.         {
    115.             Debug.Log("SlicedSprite::OnPostprocessScene()");
    116.             var all = FindObjectsOfType<SlicedSprite>();
    117.             foreach (var item in all)
    118.             {
    119.                 item.Reload();
    120.             }
    121.         }
    122. #endif
    123.  
    124.         public void ApplyRendererProps()
    125.         {
    126.             MaterialPropertyBlock props = new MaterialPropertyBlock();
    127.             //Not needed? Seems pointless.
    128.             GetComponent<Renderer>().GetPropertyBlock(props);
    129.             //props.Clear();
    130.             props.SetColor("_Color", colour);
    131.             props.SetTexture("_MainTex", sprite.texture);
    132.             GetComponent<Renderer>().SetPropertyBlock(props);
    133.         }
    134.  
    135.         [ContextMenu("Reload")]
    136.         public void Reload()
    137.         {
    138.             if (sprite == null)return;
    139.  
    140.             meshRenderer.sharedMaterial = material;
    141.             //Create mesh
    142.             Mesh newMesh = null;
    143.             if (mode == Mode.Single)
    144.             {
    145.                 //Load from sprite? TODO
    146.                 newMesh = null;
    147.             }
    148.             else
    149.             {
    150.                 //Create.
    151.                 newMesh = CreateSliced();
    152.             }
    153.  
    154.             if ( newMesh != null )
    155.             {
    156.                 //Set alignment. Yes this should be part of the CreateSliced function, I just haven't bothered.
    157.                 newMesh = ScaleAndAlign(newMesh);
    158.                 //Generate bounds.
    159.                 newMesh.RecalculateBounds();
    160.                 //Set mesh
    161.                 newMesh.name = name;
    162.                 mesh = newMesh;
    163.                 //Update the colour.
    164.                 ApplyRendererProps();
    165.             }
    166.  
    167.             if (Application.isPlaying)
    168.             {
    169.                 Debug.Log("SlicedSprite::Reload() " + gameObject.name + " " + sprite.name + " " + sprite.texture.name);
    170.             }
    171.         }
    172.  
    173.         Rect GetBounds(Vector3[] verts)
    174.         {
    175.             Rect bounds = new Rect();
    176.             for (int i = 0; i < verts.Length; i++)
    177.             {
    178.                 //Get bounds.
    179.                 Vector3 vert = verts[i];
    180.                 bounds.yMin = Mathf.Min(bounds.yMin, vert.y);
    181.                 bounds.xMin = Mathf.Min(bounds.xMin, vert.x);
    182.                 bounds.yMax = Mathf.Max(bounds.yMax, vert.y);
    183.                 bounds.xMax = Mathf.Max(bounds.xMax, vert.x);
    184.             }
    185.             return bounds;
    186.         }
    187.  
    188.         Mesh ScaleAndAlign(Mesh target)
    189.         {
    190.             var verts = target.vertices;
    191.             Rect bounds = GetBounds(verts);
    192.             //Set sizing.
    193.             var worldHeight = bounds.height;
    194.             var  worldWidth = bounds.width;
    195.             //Do scaling..
    196.             var finalScale = 1f;
    197.             if (boundsMode == BoundsMode.Fit)
    198.             {
    199.                 var scaleY = Mathf.Min(worldHeight, boundsHeight) / worldHeight;
    200.                 var scaleX = Mathf.Min(worldWidth, boundsWidth) / worldWidth;
    201.                 finalScale = Mathf.Min(scaleX, scaleY);
    202.             }
    203.             worldWidth *= finalScale;
    204.             worldHeight *= finalScale;
    205.             //Scale
    206.             for (int i = 0; i < verts.Length; i++)
    207.             {
    208.                 var v = verts[i];
    209.                 verts[i] = new Vector3(v.x  * finalScale, v.y * finalScale, v.z);
    210.             }
    211.             //Set.
    212.             target.vertices = verts;
    213.             return target;
    214.         }
    215.  
    216.         Mesh CreateSliced()
    217.         {
    218.             textureRect = sprite.textureRect;
    219.             texture = sprite.texture;
    220.             rect = sprite.rect;
    221.             textureRectOffset = sprite.textureRectOffset;
    222.  
    223.             var verts = new Vector3[4 * 9];
    224.             var uv = new Vector2[4 * 9];
    225.             var triangles = new int[6 * 9];
    226.  
    227.             var Width = sprite.rect.width * sliceWidth;
    228.             var Height = sprite.rect.height * sliceHeight;
    229.             var Left = sprite.border.x;
    230.             var Right = Width - sprite.border.z;
    231.             var Top = sprite.border.y;
    232.             var Bottom = Height - sprite.border.w;
    233.  
    234.             var offset = -new Vector3( sprite.pivot.x *sliceWidth, sprite.pivot.y * sliceHeight );
    235.  
    236.             //Verts.
    237.             var x1 = (offset.x + 0f) / sprite.pixelsPerUnit;
    238.             var x2 = (offset.x + Left) / sprite.pixelsPerUnit;
    239.             var x3 = (offset.x + Right) / sprite.pixelsPerUnit;
    240.             var x4 = (offset.x + Width) / sprite.pixelsPerUnit;
    241.             var y1 = (offset.y + 0f) / sprite.pixelsPerUnit;
    242.             var y2 = (offset.y + Top) / sprite.pixelsPerUnit;
    243.             var y3 = (offset.y + Bottom) / sprite.pixelsPerUnit;
    244.             var y4 = (offset.y + Height) / sprite.pixelsPerUnit;
    245.  
    246.             //UVs.
    247.             var u1 = sprite.textureRect.xMin / sprite.texture.width;
    248.             var u2 = (sprite.textureRect.xMin + sprite.border.x ) / sprite.texture.width;
    249.             var u3 = (sprite.textureRect.xMax - sprite.border.z ) / sprite.texture.width;
    250.             var u4 = sprite.textureRect.xMax / sprite.texture.width;
    251.             var v1 = sprite.textureRect.yMin / sprite.texture.height;
    252.             var v2 = (sprite.textureRect.yMin + sprite.border.y) / sprite.texture.height;
    253.             var v3 = (sprite.textureRect.yMax - sprite.border.w) / sprite.texture.height;
    254.             var v4 = sprite.textureRect.yMax / sprite.texture.height;
    255.  
    256.             int index = -4;
    257.  
    258.             //Top row.
    259.             Set(verts, uv, triangles, index += 4,
    260.                 new Vector3(x1, y1), new Vector3(x2, y1), new Vector3(x2, y2), new Vector3(x1, y2),
    261.                 new Vector2(u1, v1), new Vector2(u2, v1), new Vector2(u2, v2), new Vector2(u1, v2));
    262.  
    263.             Set(verts, uv, triangles, index += 4,
    264.                 new Vector3(x2, y1), new Vector3(x3, y1), new Vector3(x3, y2), new Vector3(x2, y2),
    265.                 new Vector2(u2, v1), new Vector2(u3, v1), new Vector2(u3, v2), new Vector2(u2, v2));
    266.  
    267.             Set(verts, uv, triangles, index += 4,
    268.                 new Vector3(x3, y1), new Vector3(x4, y1), new Vector3(x4, y2), new Vector3(x3, y2),
    269.                 new Vector2(u3, v1), new Vector2(u4, v1), new Vector2(u4, v2), new Vector2(u3, v2));
    270.  
    271.             //Middle row.
    272.             Set(verts, uv, triangles, index += 4,
    273.                new Vector3(x1, y2), new Vector3(x2, y2), new Vector3(x2, y3), new Vector3(x1, y3),
    274.                new Vector2(u1, v2), new Vector2(u2, v2), new Vector2(u2, v3), new Vector2(u1, v3));
    275.  
    276.             Set(verts, uv, triangles, index += 4,
    277.                 new Vector3(x2, y2), new Vector3(x3, y2), new Vector3(x3, y3), new Vector3(x2, y3),
    278.                 new Vector2(u2, v2), new Vector2(u3, v2), new Vector2(u3, v3), new Vector2(u2, v3));
    279.  
    280.             Set(verts, uv, triangles, index += 4,
    281.                 new Vector3(x3, y2), new Vector3(x4, y2), new Vector3(x4, y3), new Vector3(x3, y3),
    282.                 new Vector2(u3, v2), new Vector2(u4, v2), new Vector2(u4, v3), new Vector2(u3, v3));
    283.  
    284.             //Bottom row.
    285.             Set(verts, uv, triangles, index += 4,
    286.                new Vector3(x1, y3), new Vector3(x2, y3), new Vector3(x2, y4), new Vector3(x1, y4),
    287.                new Vector2(u1, v3), new Vector2(u2, v3), new Vector2(u2, v4), new Vector2(u1, v4));
    288.  
    289.             Set(verts, uv, triangles, index += 4,
    290.                 new Vector3(x2, y3), new Vector3(x3, y3), new Vector3(x3, y4), new Vector3(x2, y4),
    291.                 new Vector2(u2, v3), new Vector2(u3, v3), new Vector2(u3, v4), new Vector2(u2, v4));
    292.  
    293.             Set(verts, uv, triangles, index += 4,
    294.                 new Vector3(x3, y3), new Vector3(x4, y3), new Vector3(x4, y4), new Vector3(x3, y4),
    295.                 new Vector2(u3, v3), new Vector2(u4, v3), new Vector2(u4, v4), new Vector2(u3, v4));
    296.  
    297.             //Use existing mesh if it fits.
    298.             var d = mesh != null && mesh.vertices.Length == verts.Length ? mesh : new Mesh();
    299.          
    300.             //Set mesh propertys.
    301.             d.vertices = verts;
    302.             d.uv = uv;
    303.             d.triangles = triangles;
    304.        
    305.             //Colors could be used, mostly pointless as we have tint.
    306.             var c = new Color[verts.Length];
    307.             for ( int i = 0; i < c.Length; i ++ )
    308.             {
    309.                 c[i] = Color.white;
    310.             }
    311.             d.colors = c;
    312.  
    313.             //Done.
    314.             return d;
    315.         }
    316.  
    317.         void Set(Vector3[] verts, Vector2[] uvs, int[] triangles, int index, Vector3 tl, Vector3 tr, Vector3 br, Vector3 bl, Vector2 utl, Vector2 utr, Vector2 ubr, Vector2 ubl)
    318.         {
    319.             verts[index] = tl;
    320.             verts[index + 1] = bl;
    321.             verts[index + 2] = br;
    322.             verts[index + 3] = tr;
    323.  
    324.             uvs[index] = utl;
    325.             uvs[index + 1] = ubl;
    326.             uvs[index + 2] = ubr;
    327.             uvs[index + 3] = utr;
    328.  
    329.             int triindex = index / 4 * 6;
    330.  
    331.             triangles[triindex] = index;
    332.             triangles[triindex + 1] = index + 1;
    333.             triangles[triindex + 2] = index + 2;
    334.             triangles[triindex + 3] = index;
    335.             triangles[triindex + 4] = index + 2;
    336.             triangles[triindex + 5] = index + 3;
    337.         }
    338.     }
    339. }
     
    Last edited: Dec 7, 2016
  2. Rickshao

    Rickshao

    Joined:
    Jul 7, 2015
    Posts:
    4
    I worte one too. Attach C# Script to the one who needs 9-sliced and don't forget to add shader file to your project:
    Code (CSharp):
    1. /*
    2. *Author: 邵志恒
    3. *Blog: http://blog.csdn.net/rickshaozhiheng/article/details/53608168
    4. *Email: zhiheng.rick@gmail.com
    5. *Shader修改自unity5.5.0内置shader
    6. * set Image Import Setting ->MeshType to Full Rect
    7. */
    8. using System.Collections;
    9. using System.Collections.Generic;
    10. using UnityEngine;
    11.  
    12. [DisallowMultipleComponent]
    13. [ExecuteInEditMode]
    14. [RequireComponent(typeof(SpriteRenderer))]
    15. public class NineSlicedProvider : MonoBehaviour {
    16.     public bool refreshEveryFrame = false;
    17.  
    18.     SpriteRenderer m_SpriteRenderer;
    19.     SpriteRenderer spriteRenderer {
    20.         get{
    21.             if (m_SpriteRenderer == null)
    22.             {
    23.                 m_SpriteRenderer = this.GetComponent<SpriteRenderer> ();
    24.             }
    25.             return m_SpriteRenderer;
    26.         }
    27.     }
    28.  
    29.     Material m_Material;
    30.     Material material
    31.     {
    32.         get{
    33.             if (spriteRenderer == null)
    34.             {
    35.                 m_Material = null;
    36.                 return null;
    37.             }
    38.  
    39.             if (m_Material == null)
    40.             {
    41.                 spriteRenderer.sharedMaterial = new Material(Shader.Find("Rickshao/NineSlicedShader"));
    42.                 m_Material = spriteRenderer.sharedMaterial;
    43.             }
    44.             return m_Material;
    45.         }
    46.     }
    47.  
    48.     void OnEnable()
    49.     {
    50.         SetShaderVaries ();
    51.     }
    52.    
    53.     // Update is called once per frame
    54.     void Update () {
    55.         if (refreshEveryFrame)
    56.         {
    57.             SetShaderVaries();
    58.         }
    59.     }
    60.  
    61.     void SetShaderVaries()
    62.     {
    63.         if (spriteRenderer == null || spriteRenderer.sprite == null)
    64.             return;
    65.  
    66.         float width = spriteRenderer.sprite.rect.width;
    67.         float borderLeft = spriteRenderer.sprite.border.x;
    68.         float borderBottom = spriteRenderer.sprite.border.y;
    69.         float borderRight = spriteRenderer.sprite.border.z;
    70.         float borderTop = spriteRenderer.sprite.border.w;
    71.  
    72.         float left = borderLeft / width;
    73.         float bottom = borderBottom / width;
    74.         float right = borderRight / width;
    75.         float top = borderTop / width;
    76.  
    77.         //Debug.Log ("top " + top + " bottom " + bottom + " left " + left + " right " + right);
    78.  
    79.         material.SetFloat("top", top);
    80.         material.SetFloat("bottom", bottom);
    81.         material.SetFloat("right", right);
    82.         material.SetFloat("left", left);
    83.  
    84.         material.SetFloat("sx", this.transform.localScale.x);
    85.         material.SetFloat ("sy", this.transform.localScale.y);
    86.     }
    87. }
    88.  
    Code (Shader):
    1. /*
    2. *Author: 邵志恒
    3. *Blog: http://blog.csdn.net/rickshaozhiheng/article/details/53608168
    4. *Email: zhiheng.rick@gmail.com
    5. *Shader修改自unity5.5.0内置shader
    6. * set Image Import Setting ->MeshType to Full Rect
    7. */
    8. Shader "Rickshao/NineSlicedShader"
    9. {
    10.     Properties
    11.     {
    12.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    13.         [HideInInspector]_Color ("Tint", Color) = (1,1,1,1)
    14.         [HideInInspector][MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    15.     }
    16.  
    17.     SubShader
    18.     {
    19.         Tags
    20.         {
    21.             "Queue"="Transparent"
    22.             "IgnoreProjector"="True"
    23.             "RenderType"="Transparent"
    24.             "PreviewType"="Plane"
    25.             "CanUseSpriteAtlas"="True"
    26.         }
    27.  
    28.         Cull Off
    29.         Lighting Off
    30.         ZWrite Off
    31.         Blend One OneMinusSrcAlpha
    32.  
    33.         Pass
    34.         {
    35.         CGPROGRAM
    36.             #pragma vertex vert
    37.             #pragma fragment frag
    38.             #pragma target 2.0
    39.             #pragma multi_compile _ PIXELSNAP_ON
    40.             #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
    41.             #include "UnityCG.cginc"
    42.  
    43.             float top;
    44.             float bottom;
    45.             float left;
    46.             float right;
    47.             float sx;
    48.             float sy;
    49.  
    50.             struct appdata_t
    51.             {
    52.                 float4 vertex   : POSITION;
    53.                 float4 color    : COLOR;
    54.                 float2 texcoord : TEXCOORD0;
    55.                 //UNITY_VERTEX_INPUT_INSTANCE_ID //copied from 5.5, not work in 5.4
    56.             };
    57.  
    58.             struct v2f
    59.             {
    60.                 float4 vertex   : SV_POSITION;
    61.                 fixed4 color    : COLOR;
    62.                 float2 texcoord  : TEXCOORD0;
    63.                 //UNITY_VERTEX_OUTPUT_STEREO
    64.             };
    65.  
    66.             fixed4 _Color;
    67.  
    68.             float2 UVTransform(float2 origin)
    69.             {
    70.                 float2 result = origin;
    71.  
    72.                 if(origin.x * sx < left)
    73.                 {
    74.                     result.x = origin.x * sx;
    75.                 }else
    76.                 {
    77.                     if((1 - origin.x) * sx < right)
    78.                     {
    79.                         result.x = 1 - (1 - origin.x) * sx ;
    80.                     }else
    81.                     {
    82.                         result.x = (origin.x * sx - left)/(sx -left - right)*(1 - left - right) + left;
    83.                     }
    84.                 }
    85.  
    86.                 if(origin.y * sy < top)
    87.                 {
    88.                     result.y = origin.y * sy;
    89.                 }else
    90.                 {
    91.                     if((1 - origin.y) * sy < bottom)
    92.                     {
    93.                         result.y = 1 - (1 - origin.y) * sy ;
    94.                     }else
    95.                     {
    96.                         result.y = (origin.y * sy - top)/(sy - top - bottom)*(1 - top - bottom) + top;
    97.                     }
    98.                 }
    99.  
    100.                 return result;
    101.             }
    102.  
    103.             v2f vert(appdata_t IN)
    104.             {
    105.                 v2f OUT;
    106.                 UNITY_SETUP_INSTANCE_ID(IN);
    107.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
    108.                 OUT.vertex = UnityObjectToClipPos(IN.vertex);
    109.                 OUT.texcoord = IN.texcoord;
    110.                 OUT.color = IN.color * _Color;
    111.                 #ifdef PIXELSNAP_ON
    112.                 OUT.vertex = UnityPixelSnap (OUT.vertex);
    113.                 #endif
    114.  
    115.                 return OUT;
    116.             }
    117.  
    118.             sampler2D _MainTex;
    119.             sampler2D _AlphaTex;
    120.  
    121.             fixed4 SampleSpriteTexture (float2 uv)
    122.             {
    123.                 fixed4 color = tex2D (_MainTex, uv);
    124.  
    125. #if ETC1_EXTERNAL_ALPHA
    126.                 // get the color from an external texture (usecase: Alpha support for ETC1 on android)
    127.                 color.a = tex2D (_AlphaTex, uv).r;
    128. #endif //ETC1_EXTERNAL_ALPHA
    129.  
    130.                 return color;
    131.             }
    132.  
    133.             fixed4 frag(v2f IN) : SV_Target
    134.             {
    135.                 fixed4 c = SampleSpriteTexture (UVTransform(IN.texcoord)) * IN.color;
    136.                 c.rgb *= c.a;
    137.                 return c;
    138.             }
    139.         ENDCG
    140.         }
    141.     }
    142. }
    143.  
     
    unity_IpxdANggCs1roQ likes this.
  3. Rickshao

    Rickshao

    Joined:
    Jul 7, 2015
    Posts:
    4
    This is what it looks:
    nineSliced.png spriteEditor.png
     
    unity_IpxdANggCs1roQ likes this.