Search Unity

Random 2D Shape Generation

Discussion in 'Scripting' started by techtide, Dec 30, 2015.

  1. techtide

    techtide

    Joined:
    Aug 22, 2015
    Posts:
    38
    Hi,

    I am working on a game and part of the game requires random 2D shape generation. The game is completely 2D. I would like to be able to change the border and fill of the shape, but that should be quite simple to work out. Also, is it possible to add a specific script to each random 2D shape?

    Thank you.

    -8Development
     
  2. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    Hey there, what kind of shapes would you like to generate?

    You may want to check out unity procedural mesh generation.



    Thing is you can't really go for "drawing" shapes, pixel by pixel. Depends on what you need, but that may be quite expensive performance-wise. So you'll need to generate a mesh and use a Sprite shader on it (this will control your fill color). Then, as you say your game is 2D and the mesh is technically a 3D object, you're gonna need to be able to change its layer/sorting order.

    Here's a script for that :

    (The Editor Script. Place it in a folder named "Editor")
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEditorInternal;
    4. using System;
    5. using System.Reflection;
    6. using System.Collections;
    7.  
    8.  
    9. [CustomEditor(typeof(SortingLayers3D))]
    10. public class SortingLayers3DEditor : Editor {
    11.  
    12.     string[] options = new string[]{};
    13.  
    14.  
    15.     void OnEnable(){
    16.         options = GetSortingLayerNames();
    17.     }
    18.  
    19.     public override void OnInspectorGUI()
    20.     {
    21.         base.OnInspectorGUI();
    22.      
    23.         SortingLayers3D S = (SortingLayers3D)target;
    24.         Transform T = (Transform)S.transform;
    25.      
    26.      
    27.         EditorGUILayout.BeginHorizontal();
    28.         S.index = EditorGUILayout.Popup("Sorting Layer", S.index, options, EditorStyles.popup);
    29.         EditorGUILayout.EndHorizontal();
    30.      
    31.         S.order = EditorGUILayout.IntField("Order in Layer", S.order);
    32.  
    33.         //Debug.Log(T.renderer.sortingLayerName);
    34.         T.renderer.sortingLayerName = options[S.index];
    35.         T.renderer.sortingOrder = S.order;
    36.     }
    37.  
    38.  
    39.     // Get the sorting layer names
    40.     public string[] GetSortingLayerNames() {
    41.         Type internalEditorUtilityType = typeof(InternalEditorUtility);
    42.         PropertyInfo sortingLayersProperty = internalEditorUtilityType.GetProperty("sortingLayerNames", BindingFlags.Static | BindingFlags.NonPublic);
    43.         return (string[])sortingLayersProperty.GetValue(null, new object[0]);
    44.     }
    45.  
    46.     // Get the unique sorting layer IDs
    47.     public int[] GetSortingLayerUniqueIDs() {
    48.         Type internalEditorUtilityType = typeof(InternalEditorUtility);
    49.         PropertyInfo sortingLayerUniqueIDsProperty = internalEditorUtilityType.GetProperty("sortingLayerUniqueIDs", BindingFlags.Static | BindingFlags.NonPublic);
    50.         return (int[])sortingLayerUniqueIDsProperty.GetValue(null, new object[0]);
    51.     }
    52. }
    53.  
    The actual script (Place it in a normal folder. You'll attach it to the 3D object you want to treat as a sprite)

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Reflection;
    4. using System.Collections;
    5.  
    6.  
    7. public class SortingLayers3D : MonoBehaviour{
    8.     [HideInInspector]
    9.     public int order = 0;
    10.     [HideInInspector]
    11.     public int index = 0;
    12. }
    13.  

    As for the border, you'll either need a shader or if you shape is convex you can duplicate it, place it one layer behind and assign it a different color (black for instance).


    And sure, it is possible to assign it a script at runtime. Once you have your GameObject created, use yourGameObject.AddComponent<YourScriptNameHere>().


    Code (CSharp):
    1. void Start(){
    2.  
    3. //This will create an empty GameObject with the name "GO_Generated_In_Code"
    4. GameObject newGameObject = new GameObject("GO_Generated_In_Code");
    5.  
    6. //This will attach the script named "YourScriptName" to your new GameObject
    7. YourScriptName script = newGameObject.AddComponent<YourScriptName>();
    8.  
    9. }
     
    Last edited: Dec 30, 2015
    techtide likes this.
  3. techtide

    techtide

    Joined:
    Aug 22, 2015
    Posts:
    38
    I'm looking to actually create random polygons. Oh, and thank you for your detailed response :) I really appreciate it!
     
  4. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    http://wiki.unity3d.com/index.php/ProceduralPrimitives

    Here i wrote a quick script which would create a new GameObject, generate a 2D plane procedurally and attach a script to it :

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PlaneGen : MonoBehaviour {
    5.  
    6.     public Material spriteMaterial;
    7.  
    8.     void Start(){
    9.  
    10.         //Create a new GameObject named "Spawned GameObject (Plane)"
    11.         GameObject spawnedGameObject = new GameObject("Spawned GameObject (Plane)");
    12.  
    13.         //Create a 2D plane procedurally on our new Object
    14.         GeneratePlane(spawnedGameObject);
    15.  
    16.     }
    17.    
    18.  
    19.     void GeneratePlane (GameObject spawnedGameObject) {
    20.         // You can change that line to provide another MeshFilter
    21.         MeshFilter filter = spawnedGameObject.AddComponent< MeshFilter >();
    22.         Mesh mesh = filter.mesh;
    23.         mesh.Clear();
    24.        
    25.         float length = 1f;
    26.         float width = 1f;
    27.         int resX = 2; // 2 minimum
    28.         int resY = 2;
    29.        
    30.         #region Vertices      
    31.         Vector3[] vertices = new Vector3[ resX * resY ];
    32.         for(int y = 0; y < resY; y++)
    33.         {
    34.             // [ -length / 2, length / 2 ]
    35.             float yPos = ((float)y / (resY - 1) - .5f) * length;
    36.             for(int x = 0; x < resX; x++)
    37.             {
    38.                 // [ -width / 2, width / 2 ]
    39.                 float xPos = ((float)x / (resX - 1) - .5f) * width;
    40.                 vertices[ x + y * resX ] = new Vector3( xPos, yPos, 0.0f );
    41.             }
    42.         }
    43.         #endregion
    44.        
    45.         #region Normales
    46.         Vector3[] normales = new Vector3[ vertices.Length ];
    47.         for( int n = 0; n < normales.Length; n++ )
    48.             normales[n] = -Vector3.forward;
    49.         #endregion
    50.        
    51.         #region UVs      
    52.         Vector2[] uvs = new Vector2[ vertices.Length ];
    53.         for(int v = 0; v < resY; v++)
    54.         {
    55.             for(int u = 0; u < resX; u++)
    56.             {
    57.                 uvs[ u + v * resX ] = new Vector2( (float)u / (resX - 1), (float)v / (resY - 1) );
    58.             }
    59.         }
    60.         #endregion
    61.        
    62.         #region Triangles
    63.         int nbFaces = (resX - 1) * (resY - 1);
    64.         int[] triangles = new int[ nbFaces * 6 ];
    65.         int t = 0;
    66.         for(int face = 0; face < nbFaces; face++ )
    67.         {
    68.             // Retrieve lower left corner from face ind
    69.             int i = face % (resX - 1) + (face / (resY - 1) * resX);
    70.            
    71.             triangles[t++] = i + resX;
    72.             triangles[t++] = i + 1;
    73.             triangles[t++] = i;
    74.            
    75.             triangles[t++] = i + resX;  
    76.             triangles[t++] = i + resX + 1;
    77.             triangles[t++] = i + 1;
    78.         }
    79.         #endregion
    80.        
    81.         mesh.vertices = vertices;
    82.         mesh.normals = normales;
    83.         mesh.uv = uvs;
    84.         mesh.triangles = triangles;
    85.        
    86.         mesh.RecalculateBounds();
    87.         mesh.Optimize();
    88.  
    89.  
    90.         //Attach material
    91.         MeshRenderer rend = spawnedGameObject.AddComponent<MeshRenderer>();
    92.  
    93.         if(spriteMaterial){
    94.             rend.material = spriteMaterial;
    95.         }
    96.     }
    97. }
    98.  
    Create a new C# script named PlaneGen, copy paste this in, drag this script to a GameObject (it could be anything, let's say the Main Camera), if you want add a material to the spriteMaterial variable and hit Play. You should see a small plane generated.
     
    techtide likes this.
  5. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    Ah, great then! It means this is what you're after... This is the core principle, however depending on how random your polygons will be, things may get trickier for triangulation. If you have a weird concave blob generated randomly, you'll also need to figure out a way to make the triangles properly...
     
  6. techtide

    techtide

    Joined:
    Aug 22, 2015
    Posts:
    38
    Thank you. This is interesting. So, does that mean that I could just create a random amount of triangles using Random.Range, and set that? That would create a random shape, right?
     
  7. techtide

    techtide

    Joined:
    Aug 22, 2015
    Posts:
    38
    I tired that, and it doesn't seem to work. Now, how do I make it a randomly generated shape?
    I tried to create a Random.Range(3, 36). How would you do this?
    Thanks for your help.
     
  8. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381

    Unfortunatelly, not exactly. You can tweak these variables

    Code (CSharp):
    1.         int resX = 2; // 2 minimum
    2.         int resY = 2;
    to add more divisions to the plane. However, if you start to change the positions of the vertices around, the shape may not look as you want.

    Say you have these random dots (these would be the vertices of your polygon) :





    There are mutliple ways to connect them...







    The way you arrange the connections between dots is determined by the triangles array. It is basically an array of indexes for each triangle. It is saying which vertex in the vertices array should be assigned in that triangle. Again, unfortunately it is a more complex subject and it depends exactly on how random your shapes will be. The worst will be triangulation. You can't just define the points on the border like when you draw a path in Photoshop for instance :(
     

    Attached Files:

  9. techtide

    techtide

    Joined:
    Aug 22, 2015
    Posts:
    38
    Darn :/ So I guess I can't really do that. Are there any other ways?
     
  10. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    at line 40 you can change this line

    vertices[ x + y * resX ]=new Vector3( xPos, yPos, 0.0f );

    to this :

    vertices[ x + y * resX ]=new Vector3( xPos + Random.Range(-0.5f, 0.5f), yPos + Random.Range(-0.5f, 0.5f), 0.0f );

    if you want to slightly deform the plane and get a feel for it. But it is probably not what you need. Again, it depends very much on what you need. If the shape is truly random, then it is not easy at all...
     
  11. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,735
    There are essentially an infinitely variable number of ways to procedurally generate mesh geometry. I suggest you try to understand the essential components in the script that @Ted Chirvasiu posted above, which is a fine example of simple-to-understand code. When you understand what it does, you can begin to modify it in order to experiment and learn how procedural geometry creation works, what the gotchas are, what the easy problems are, what the hard problems are, etc.
     
    Ted-Chirvasiu likes this.
  13. techtide

    techtide

    Joined:
    Aug 22, 2015
    Posts:
    38
    Okay. I think I know what to do. Due to this being only a prototype, I won't bother with this. I have an old Java program which creates shapes as PNGs. I can just run that and get some prototype sprites.

    Thanks for the help guys. In the real game, I will try this.
     
    Ted-Chirvasiu likes this.