Search Unity

Tiling and 9-Slice

Discussion in '2D' started by rstorm000, Nov 13, 2013.

  1. rstorm000

    rstorm000

    Joined:
    Aug 19, 2010
    Posts:
    212
    For anyone using the sprites, is there a known simple way for tiling or drawing with them?(Maybe with snapping) With tiling you could have a single collider for a set of tiles I guess.

    Also 9-Slice sprites would be useful for UI stuff.
     
  2. hermesdavidms

    hermesdavidms

    Joined:
    Jul 3, 2012
    Posts:
    181
    i dont think tiling can be done with the current feature set, and 9 slice can be possible now that scaling doesnt break batching but its kinda messy, have the 9 parts as separate objects and child them to a container object, and for every scale made to the container object, revert that scale on the corner objects and it would require some alignment calculations
     
  3. Storyteller

    Storyteller

    Joined:
    May 15, 2012
    Posts:
    23
    you can place by units using the CTRL key (on win) so if you make your sprites to match they will line up. you can the make your colliders however you want by setting their values.

    so, you can line up 10 boxes and use one collider to make a platform or wall.
     
  4. jonomf

    jonomf

    Joined:
    Nov 7, 2010
    Posts:
    7
    I made a pre-4.3 NineTile (9-Slice) script and it works nicely and is very handy. I would definitely like to see this functionality natively supported in Uni2D / the new GUI system.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class NineTile : MonoBehaviour {
    7.    
    8.     public static bool doNineTile = true;
    9.     const int TL = 0, T = 1, TR = 2, L = 3, M = 4, R = 5, BL = 6, B = 7, BR = 8;
    10.     public static Dictionary<int, Vector3> cornerSizes = new Dictionary<int, Vector3>();
    11.    
    12.     public float
    13.         overrideScale = 0,
    14.         zOffset = 0;
    15.     public bool nineTileOnStart = true;
    16.    
    17.     List<GameObject> tileGOs = new List<GameObject>();
    18.     Vector3 lockPos;   
    19.     Dictionary<int, Vector3> overrideCornerSizes = new Dictionary<int, Vector3>(); // for when scale != 0
    20.     Bounds bounds;
    21.    
    22.     bool OverridingScale { get { return overrideScale != 0; }}
    23.    
    24.    
    25.     void Start() {
    26.         if(nineTileOnStart)
    27.             Do9Tile();
    28.     }
    29.    
    30.     /// <summary> For manual calling when this object isn't active at start. </summary>
    31.     public void Do9Tile() {
    32.         if(!doNineTile) return;
    33.         bounds = collider.bounds;
    34.        
    35.         float holdScale = GUIController.scale;
    36.         Dictionary<int, Vector3> holdCornerSizes = cornerSizes;
    37.        
    38.         if(OverridingScale) {
    39.             foreach(int corner in GUIController.corners)  // Corners: public static int[] corners = {0, 2, 6, 8};  
    40.                 overrideCornerSizes.Add(corner, new Vector3(GUIController.me.tiles[corner].width * overrideScale, GUIController.me.tiles[corner].height * overrideScale, 1));
    41.            
    42.             holdScale = overrideScale;
    43.             holdCornerSizes = overrideCornerSizes;
    44.         }
    45.        
    46.         // Create all tiles
    47.         for(int i = 0; i < GUIController.me.tiles.Length; i++) { // GUIController.me.tiles is a Texture2D[] with each of the 9 slices, in order from upper-left (left then down)
    48.             Texture2D tile = GUIController.me.tiles[i];
    49.             GameObject tileGO = Instantiate(GUIController.me.ULPivotPlane) as GameObject;
    50.             // position the TL tile in the corner of the bounding box; everything else will be positioned according to this one.
    51.             if(i == 0)
    52.                 tileGO.transform.position = new Vector3(bounds.center.x - bounds.extents.x, bounds.center.y + bounds.extents.y, bounds.center.z);
    53.             tileGO.renderer.material.mainTexture = tile;
    54.             tileGO.name = tile.name;
    55. //          tileGO.renderer.material.color = color;
    56.             tileGOs.Add(tileGO);
    57.                
    58.             // Size current tile
    59.             switch(i) {
    60.             case TL: case TR: case BL: case BR:
    61.                 tileGO.transform.localScale = new Vector3(tile.width * holdScale, tile.height * holdScale, 1);
    62.                 break;
    63.             case T:
    64.                 tileGO.transform.localScale = new Vector3(bounds.size.x - holdCornerSizes[TL].x - holdCornerSizes[TR].x, holdCornerSizes[TL].y, 1);
    65.                 break;
    66.             case B:
    67.                 tileGO.transform.localScale = new Vector3(bounds.size.x - holdCornerSizes[BL].x - holdCornerSizes[BR].x, holdCornerSizes[BL].y, 1);
    68.                 break;
    69.             case L:
    70.                 float lHeight = bounds.size.y - holdCornerSizes[BL].y - holdCornerSizes[TL].y;
    71.                 if(lHeight < 0) lHeight = 0;
    72.                 tileGO.transform.localScale = new Vector3(holdCornerSizes[TL].x, lHeight, 1);
    73.                 break;
    74.             case R:
    75.                 float rHeight = bounds.size.y - holdCornerSizes[BL].y - holdCornerSizes[TR].y;
    76.                 if(rHeight < 0) rHeight = 0;
    77.                 tileGO.transform.localScale = new Vector3(holdCornerSizes[TR].x, rHeight, 1);
    78.                 break;
    79.             case M:
    80.                 float mHeight = bounds.size.y - holdCornerSizes[BL].y - holdCornerSizes[TL].y;
    81.                 if(mHeight < 0) mHeight = 0;
    82.                 tileGO.transform.localScale = new Vector3(bounds.size.x - holdCornerSizes[TL].x - holdCornerSizes[TR].x, mHeight, 1);
    83.                 break;
    84.             }
    85.            
    86.             // Position current tile
    87.             if(i != 0) {
    88.                 if((i % 3) != 0)
    89.                     lockPos = tileGOs[i - 1].transform.position + tileGOs[i - 1].renderer.bounds.size.x * Vector3.right;
    90.                 else
    91.                     lockPos = tileGOs[i - 3].transform.position + tileGOs[i - 3].renderer.bounds.size.y * Vector3.down;
    92.                
    93.                 tileGO.transform.position = lockPos;
    94.             }
    95.             tileGO.transform.parent = transform;
    96.         }
    97.        
    98.         if(zOffset != 0) {
    99.             foreach(GameObject go in tileGOs) {
    100.                 go.transform.position += Vector3.forward * zOffset;
    101.             }
    102.         }
    103.     }
    104.    
    105.     public void Reset() {
    106.         foreach(GameObject tile in tileGOs)
    107.             Destroy(tile);
    108.         tileGOs.Clear();
    109.         Do9Tile();
    110.     }
    111. }
    112.  
     
    Last edited: Nov 14, 2013
  5. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    2,975
  6. rstorm000

    rstorm000

    Joined:
    Aug 19, 2010
    Posts:
    212
    looks neat sfbay. Wish I could figure out where to get it. Jonomf, will try that code. :D
     
  7. TomasJ

    TomasJ

    Joined:
    Sep 26, 2010
    Posts:
    256
    We've not included any native solutions for tiling or slicing sprites in 4.3. They're planned for the same release GUI is coming out in. We feel it is important to match the workflow of 2D and GUI.
     
  8. Rocketballs

    Rocketballs

    Joined:
    Sep 13, 2012
    Posts:
    23
    I made a simple editor script to quickly duplicate a sprite in tiles, and flip it horizontally and vertically.
    It's quite rough but it does what I need, so I figured I should share it :)

    And obviously, feel free to add and improve it.

    All it needs is an empty gameobject to act as a container and inside the sprite you want to tile.
    The script goes in the empty gameobject.
    Make a prefab of it, and it's ready.

    $Untitled.png

    SpriteTiling.cs
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class SpriteTiling : MonoBehaviour {
    6.  
    7.     public Vector2 tileSize = new Vector2(1, 1);
    8.     public bool flipHorizontal = false;
    9.     public bool flipVertical = false;
    10. }
    11.  
    12.  
    SpriteTilingEditor.cs - This needs to be inside an "Editor" folder
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. [CustomEditor(typeof(SpriteTiling))]
    8.  
    9. public class SpriteTilingEditor : Editor {
    10.  
    11.     private SpriteTiling spriteTiling;
    12.     private GameObject originSprite;
    13.    
    14.     public override void OnInspectorGUI() {
    15.  
    16.         spriteTiling = target as SpriteTiling;
    17.  
    18.         GUILayout.BeginVertical("box");
    19.             EditorGUILayout.LabelField("TILING",EditorStyles.boldLabel);
    20.             spriteTiling.tileSize = EditorGUILayout.Vector2Field("Tile Size: ", spriteTiling.tileSize);
    21.             if(GUILayout.Button("Apply!"))
    22.             ApplyTileSettings();
    23.         GUILayout.EndHorizontal();
    24.  
    25.         GUILayout.BeginVertical("box");
    26.             EditorGUILayout.LabelField("ROTATION",EditorStyles.boldLabel);
    27.             if(GUILayout.Button("Flip Horizontal")) FlipHorizontal(!spriteTiling.flipHorizontal);
    28.             EditorGUILayout.LabelField("", spriteTiling.flipHorizontal.ToString(), EditorStyles.boldLabel);
    29.             if(GUILayout.Button("Flip Vertical")) FlipVertical(!spriteTiling.flipVertical);
    30.             EditorGUILayout.LabelField("", spriteTiling.flipVertical.ToString(), EditorStyles.boldLabel);
    31.         GUILayout.EndHorizontal();
    32.     }
    33.  
    34.     private void ApplyTileSettings() {
    35.  
    36.         originSprite = spriteTiling.transform.Find(spriteTiling.name).gameObject;
    37.  
    38.         List<GameObject> children = new List<GameObject>();
    39.         foreach(Transform child in spriteTiling.transform) {
    40.             if(child.gameObject == originSprite) continue;
    41.             children.Add(child.gameObject);
    42.         }
    43.         children.ForEach(child => DestroyImmediate(child));
    44.  
    45.  
    46.         if(spriteTiling.tileSize.x > 1  spriteTiling.tileSize.y > 1) {
    47.             for(int x = 0; x < spriteTiling.tileSize.x; x++) {
    48.                 for(int y = 0; y < spriteTiling.tileSize.y; y++) {
    49.                     if(x==0  y==0) continue;
    50.                     Object prefabRoot = PrefabUtility.GetPrefabParent(originSprite);
    51.                     GameObject newTiledSprite = (GameObject)PrefabUtility.InstantiatePrefab(prefabRoot);
    52.                     GameObject newTiledSpriteParent = newTiledSprite.transform.parent.gameObject;
    53.                     newTiledSprite.transform.position = new Vector3(originSprite.transform.position.x+x, originSprite.transform.position.y+y, 0);
    54.                     newTiledSprite.transform.parent = spriteTiling.transform;
    55.                     DestroyImmediate(newTiledSpriteParent);
    56.                 }
    57.             }
    58.         }
    59.         else if(spriteTiling.tileSize.x == 1) {
    60.             for(int y = 1; y < spriteTiling.tileSize.y; y++) {
    61.                 Object prefabRoot = PrefabUtility.GetPrefabParent(originSprite);
    62.                 GameObject newTiledSprite = (GameObject)PrefabUtility.InstantiatePrefab(prefabRoot);
    63.                 GameObject newTiledSpriteParent = newTiledSprite.transform.parent.gameObject;
    64.                 newTiledSprite.transform.position = new Vector3(originSprite.transform.position.x, originSprite.transform.position.y+y, 0);
    65.                 newTiledSprite.transform.parent = spriteTiling.transform;
    66.                 DestroyImmediate(newTiledSpriteParent);
    67.                
    68.             }
    69.         }
    70.         else if(spriteTiling.tileSize.y == 1) {
    71.             for(int x = 1; x < spriteTiling.tileSize.x; x++) {
    72.                 Object prefabRoot = PrefabUtility.GetPrefabParent(originSprite);
    73.                 GameObject newTiledSprite = (GameObject)PrefabUtility.InstantiatePrefab(prefabRoot);
    74.                 GameObject newTiledSpriteParent = newTiledSprite.transform.parent.gameObject;
    75.                 newTiledSprite.transform.position = new Vector3(originSprite.transform.position.x+x, originSprite.transform.position.y, 0);
    76.                 newTiledSprite.transform.parent = spriteTiling.transform;
    77.                 DestroyImmediate(newTiledSpriteParent);
    78.             }
    79.         }
    80.     }
    81.  
    82.     private void FlipHorizontal(bool b) {
    83.         if(spriteTiling.flipHorizontal) spriteTiling.transform.Rotate(0, -180, 0);
    84.         else if(!spriteTiling.flipHorizontal) spriteTiling.transform.Rotate(0, 180, 0);
    85.         spriteTiling.flipHorizontal = b;
    86.     }
    87.     private void FlipVertical(bool b) {
    88.         if(spriteTiling.flipHorizontal) spriteTiling.transform.Rotate(0, -180, -180);
    89.         else if(!spriteTiling.flipHorizontal) spriteTiling.transform.Rotate(0, 180, 180);
    90.         spriteTiling.flipVertical = b;
    91.     }
    92. }
    93.  
     
    Last edited: Nov 14, 2013
  9. sotirosn

    sotirosn

    Joined:
    Jan 5, 2013
    Posts:
    24
    I am doing the same as jonomf, but I manipulate each of the 16 vertexes of a sliced quad mesh directly instead of using 9 gameobjects. I think this is the way NGUI does it, and I think updating one vertex buffer is more efficient than updating 9 transform matrixes but I could be wrong. Then I use a simple SpriteTexture to convert the native Sprite to a quad texture. SpriteTexture can also be used on a primitive quad, plane or cube object.

    Code (csharp):
    1.  
    2. // SpriteTexture.cs
    3. public class SpriteTexture : MonoBehaviour {
    4.     public Sprite sprite;
    5.  
    6.     public void Awake() {
    7.         Apply();
    8.     }
    9.  
    10.     public void Apply() {
    11.         renderer.material.mainTexture = sprite.texture;
    12.         Debug.Log (sprite.texture.width + " " + sprite.texture.height);
    13.         Debug.Log (sprite.textureRect);
    14.  
    15.         renderer.material.mainTextureScale = new Vector2(
    16.             sprite.textureRect.width/sprite.texture.width,
    17.             sprite.textureRect.height/sprite.texture.height
    18.         );
    19.  
    20.         renderer.material.mainTextureOffset = new Vector2(
    21.             sprite.textureRect.x/sprite.texture.width,
    22.             sprite.textureRect.y/sprite.texture.height
    23.         );
    24.     }
    25. }
    26.  
    27. // BoarderedQuad.cs
    28. [System.Serializable]
    29. public class Boarder {
    30.     public float left, right, top, bottom;
    31. }
    32.  
    33. public class BoarderedQuad : Element {
    34.     // boader thickness
    35.     public Boarder boarder, uv;
    36.     public float width, height;
    37.  
    38.     MeshFilter meshFilter;
    39.  
    40.     public override void Awake() {
    41.         base.Awake();
    42.         Apply();
    43.     }
    44.  
    45.     // this method may be called from the editor inspector
    46.     public void Apply() {
    47.         meshFilter = GetComponent<MeshFilter>();
    48.         meshFilter.mesh.Clear();
    49.  
    50.         float w = width/2, h = height/2;
    51.  
    52.         // mesh is made up of 16 uvs
    53.         // ordered top to bottom, left to right
    54.         meshFilter.mesh.vertices = new Vector3[] {
    55.             // top left to right
    56.             new Vector3(-w, h, 0),
    57.             new Vector3(-w + boarder.left, h, 0),
    58.             new Vector3(w - boarder.right, h, 0),
    59.             new Vector3(w, h, 0),
    60.            
    61.             // inner-top left to right
    62.             new Vector3(-w, h - boarder.top, 0),
    63.             new Vector3(-w + boarder.left, h - boarder.top, 0),
    64.             new Vector3(w - boarder.right, h - boarder.top, 0),
    65.             new Vector3(w, h - boarder.top, 0),
    66.            
    67.             // inner-bottom left to right
    68.             new Vector3(-w, -h + boarder.bottom, 0),
    69.             new Vector3(-w + boarder.left, -h + boarder.bottom, 0),
    70.             new Vector3(w - boarder.right, -h + boarder.bottom, 0),
    71.             new Vector3(w, -h + boarder.bottom, 0),
    72.  
    73.             // bottom left to right
    74.             new Vector3(-w, -h, 0),
    75.             new Vector3(-w + boarder.left, -h, 0),
    76.             new Vector3(w - boarder.right, -h, 0),
    77.             new Vector3(w, -h, 0),
    78.         };
    79.  
    80.         // mesh is made up of 16 uvs
    81.         // ordered top to bottom, left to right
    82.         meshFilter.mesh.uv = new Vector2[] {
    83.             // top left to right
    84.             new Vector2(0, 1),
    85.             new Vector2(uv.left, 1),
    86.             new Vector2(uv.right, 1),
    87.             new Vector2(1, 1),
    88.  
    89.             // inner-top left to right
    90.             new Vector2(0, uv.top),
    91.             new Vector2(uv.left, uv.top),
    92.             new Vector2(uv.right, uv.top),
    93.             new Vector2(1, uv.top),
    94.  
    95.             // inner-bottom left to right
    96.             new Vector2(0, uv.bottom),
    97.             new Vector2(uv.left, uv.bottom),
    98.             new Vector2(uv.right, uv.bottom),
    99.             new Vector2(1, uv.bottom),
    100.  
    101.             // bottom left to right
    102.             new Vector2(0, 0),
    103.             new Vector2(uv.left, 0),
    104.             new Vector2(uv.right, 0),
    105.             new Vector2(1, 0),
    106.         };
    107.  
    108.         // 18 triangles, two for each of the 9 slices
    109.         // ordered in quads(6) top to bottom, left to right
    110.         meshFilter.mesh.triangles = new int[] {
    111.             // top left to right
    112.             0, 1, 4, 4, 1, 5,
    113.             1, 2, 5, 5, 2, 6,
    114.             2, 3, 6, 6, 3, 7,
    115.  
    116.             // middle left to right
    117.             4, 5, 8, 8, 5, 9,
    118.             5, 6, 9, 9, 6, 10,
    119.             6, 7, 10, 10, 7, 11,
    120.  
    121.             // bottom left to right
    122.             8, 9, 12, 12, 9, 13,
    123.             9, 10, 13, 13, 10, 14,
    124.             10, 11, 14, 14, 11, 15
    125.         };
    126.     }
    127.  
    128.     public override void Resize() {
    129.         base.Resize();
    130.  
    131.         float w = width/2, h = height/2;
    132.  
    133.         // mesh is made up of 16 uvs
    134.         // ordered top to bottom, left to right
    135.         meshFilter.mesh.vertices = new Vector3[] {
    136.             // top left to right
    137.             new Vector3(-w, h, 0),
    138.             new Vector3(-w + boarder.left, h, 0),
    139.             new Vector3(w - boarder.right, h, 0),
    140.             new Vector3(w, h, 0),
    141.  
    142.             // inner-top left to right
    143.             new Vector3(-w, h - boarder.top, 0),
    144.             new Vector3(-w + boarder.left, h - boarder.top, 0),
    145.             new Vector3(w - boarder.right, h - boarder.top, 0),
    146.             new Vector3(w, h - boarder.top, 0),
    147.  
    148.             // inner-bottom left to right
    149.             new Vector3(-w, -h + boarder.bottom, 0),
    150.             new Vector3(-w + boarder.left, -h + boarder.bottom, 0),
    151.             new Vector3(w - boarder.right, -h + boarder.bottom, 0),
    152.             new Vector3(w, -h + boarder.bottom, 0),
    153.  
    154.             // bottom left to right
    155.             new Vector3(-w, -h, 0),
    156.             new Vector3(-w + boarder.left, -h, 0),
    157.             new Vector3(w - boarder.right, -h, 0),
    158.             new Vector3(w, -h, 0),
    159.         };
    160.  
    161.         // no need to update mesh uvs or triangles that were applied in Apply()
    162.     }
    163. }
    164.  
    And lastly you can define a custom editor that will Apply() changes when the inspector changes.
     
    nicloay likes this.
  10. Chris_E

    Chris_E

    Joined:
    Jul 10, 2012
    Posts:
    31
    I'm looking to do this, but the line:

    public class BoarderedQuad : Element {

    is tripping me up. What is "Element"?

    Thanks!
     
  11. sotirosn

    sotirosn

    Joined:
    Jan 5, 2013
    Posts:
    24
    Sorry I copied and pasted the code from my own project. Element is just my custom 2D element that has layout features such as align left, or center, or fill. I think it should work still if you change it to a simple MonoBehaviour?
     
unityunity