Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Create terrain from code

Discussion in 'Scripting' started by Garrett-Lynch, Mar 16, 2013.

  1. Garrett-Lynch

    Garrett-Lynch

    Joined:
    Mar 13, 2013
    Posts:
    14
    Hi

    I'm attempting to build a terrain tiler and I've already looked at most of the posts and examples here but ether they are not quite what I want or I don't understand how to start reworking parts of the scripts that seem useful.

    I'm not trying to make an infinite tiler, I want a finite looped tiler as if building a world. I have several questions about the best way to do this.

    1) Do I build everything from code and store each tiles information i.e. position, texture, heightmap etc. in a database of some sorts? Or do I prebuild the tiles and simply place/stitch them from the code? Which is best/more efficent?

    2) What is the maximum number of terrains I can use in Unity3D? Is there a limit?

    3) Is it best to create (ether from code or manually) small and lots of terrains or big and fewer terrains?

    I'm more interested in the theory behind this at the moment and I'm gradually working up a basic script. Here is what I have and I'm stuck on adding ether a colour or texture, I keep getting renderer missing problems or the colour does not appear. What am I doing wrong?

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using Utility;
    5.  
    6. public class test : MonoBehaviour
    7. {
    8.     //use this for initialization
    9.     void Start ()
    10.     {
    11.         //create a new terrain data
    12.         TerrainData _terrainData = new TerrainData();
    13.  
    14.         //set terrain width, height, length
    15.         _terrainData.size = new Vector3(20, 1, 20);
    16.  
    17. //this is where I'm stuck, how do I change the terrain colour?
    18. _terrain.AddComponent("Renderer");
    19. _terrain.renderer.enabled = true;
    20. _terrain.renderer.material.color = Color.red;
    21.  
    22.  
    23. //to test size
    24. //print("Size = " + _terrainData.size);
    25.  
    26. //to test position
    27. //print("X = " + _terrain.transform.position.x.ToString());
    28. //print("Y = " + _terrain.transform.position.y.ToString());
    29. //print("Z = " + _terrain.transform.position.z.ToString());
    30.  
    31.     }
    32.    
    33.     // Update is called once per frame
    34.     void Update () {
    35.  
    36.     }
    37.    
    38. }
    39.  
    thanks
    Garrett
     
  2. Deleted User

    Deleted User

    Guest

    for most of your questions I dont have an answer, sorry but Terrains doesnt use "normal" shaders (where you can provide a main color). The Terrain uses a splatmap to apply different Textures.
    Anyways a few month ago I started somthing simmilar, which I scratched early on. I give you my script, maybe it will help you out with some thinks. Its an Editor script but most parts should apply to an runtime solution

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.IO;
    5.  
    6. public class CreateTiledTerrain : EditorWindow {
    7.    
    8.     private static EditorWindow window;
    9.    
    10.     private static Vector2 tileAmount = Vector2.one;
    11.    
    12.     private float width  = 1000;
    13.     private float lenght = 1000;
    14.     private float height = 600;
    15.    
    16.     private int heightmapResoltion          = 513;
    17.     private int detailResolution            = 1024;
    18.     private int detailResolutionPerPatch    = 8;
    19.     private int controlTextureResolution    = 512;
    20.     private int baseTextureReolution        = 1024;
    21.    
    22.     private string path = string.Empty;
    23.    
    24.     private enum Alphabet{
    25.         None,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z
    26.     }
    27.     private Alphabet alphabet = Alphabet.None;
    28.  
    29.    
    30.  
    31.     [MenuItem("Terrain/Create Tiled Terrain")]
    32.     public static void CreateWindow(){
    33.         window = EditorWindow.GetWindow(typeof(CreateTiledTerrain));
    34.         window.title = "Tiled Terrain";
    35.         window.minSize = new Vector2(500f, 700f);
    36.     }
    37.    
    38.     private void OnGUI(){
    39.         EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(false));
    40.         tileAmount = EditorGUILayout.Vector2Field("Amount", tileAmount);
    41.         EditorGUILayout.EndHorizontal();
    42.        
    43.         width = EditorGUILayout.FloatField("Terrain Width", width);
    44.         lenght = EditorGUILayout.FloatField("Terrain Lenght", lenght);
    45.         height = EditorGUILayout.FloatField("Terrain Height", height);
    46.        
    47.         EditorGUILayout.Space();
    48.  
    49.         heightmapResoltion = EditorGUILayout.IntField("Heightmap Resoltion", heightmapResoltion);
    50.         heightmapResoltion = Mathf.ClosestPowerOfTwo(heightmapResoltion) + 1;
    51.         heightmapResoltion = Mathf.Clamp(heightmapResoltion, 33, 4097);
    52.        
    53.         detailResolution = EditorGUILayout.IntField("Detail Resolution", detailResolution);
    54.         detailResolution = Mathf.ClosestPowerOfTwo(detailResolution);
    55.         detailResolution = Mathf.Clamp(detailResolution, 0, 4096);
    56.        
    57.         detailResolutionPerPatch = EditorGUILayout.IntField("Detail Resolution Per Patch", detailResolutionPerPatch);
    58.         detailResolutionPerPatch = Mathf.ClosestPowerOfTwo(detailResolutionPerPatch);
    59.         detailResolutionPerPatch = Mathf.Clamp(detailResolutionPerPatch, 8, 128);
    60.        
    61.         controlTextureResolution = EditorGUILayout.IntField("Control Texture Resolution", controlTextureResolution);
    62.         controlTextureResolution = Mathf.ClosestPowerOfTwo(controlTextureResolution);
    63.         controlTextureResolution = Mathf.Clamp(controlTextureResolution, 16, 1024);
    64.        
    65.         baseTextureReolution = EditorGUILayout.IntField("Base Texture Reolution", baseTextureReolution);
    66.         baseTextureReolution = Mathf.ClosestPowerOfTwo(baseTextureReolution);
    67.         baseTextureReolution = Mathf.Clamp(baseTextureReolution, 16, 2048);
    68.        
    69.         EditorGUILayout.Space();
    70.         GUILayout.Label("Path were to save TerrainDate:");
    71.         path = EditorGUILayout.TextField("Assets/", path);
    72.        
    73.         if(GUILayout.Button("Create")){
    74.             ValidatePath();
    75.             CreateTerrain();
    76.            
    77.             path = string.Empty;
    78.         }
    79.     }
    80.    
    81.     private void ValidatePath(){
    82.         if(path == string.Empty) path = "TiledTerrain/TerrainData/";
    83.        
    84.         string pathToCheck = Application.dataPath + "/" + path;
    85.         if(Directory.Exists(pathToCheck) == false){
    86.             Directory.CreateDirectory(pathToCheck);
    87.         }
    88.     }
    89.    
    90.     private void CreateTerrain(){
    91.         GameObject parent = (GameObject)Instantiate(new GameObject("Terrain"));
    92.         parent.transform.position = new Vector3(0, 0, 0);
    93.  
    94.        
    95.         for(int x = 1; x <= tileAmount.x; x++){
    96.             for(int y = 1; y <= tileAmount.y; y++){
    97.                
    98.                 TerrainData terrainData = new TerrainData();
    99.                
    100.                 alphabet = (Alphabet)x;
    101.                 string name = alphabet + "-" + y;
    102.        
    103.                 terrainData.size = new Vector3( width / 16f,
    104.                                                 height,
    105.                                                 lenght / 16f);
    106.                
    107.                 terrainData.baseMapResolution = baseTextureReolution;
    108.                 terrainData.heightmapResolution = heightmapResoltion;
    109.                 terrainData.alphamapResolution = controlTextureResolution;
    110.                 terrainData.SetDetailResolution(detailResolution, detailResolutionPerPatch);
    111.  
    112.                 terrainData.name = name;
    113.                 GameObject terrain = (GameObject)Terrain.CreateTerrainGameObject(terrainData);
    114.                
    115.                 terrain.name = name;
    116.                 terrain.transform.parent = parent.transform;
    117.                 terrain.transform.position = new Vector3(lenght * (x - 1), 0, width * (y - 1));
    118.  
    119.                 AssetDatabase.CreateAsset(terrainData, "Assets/" + path + name + ".asset");
    120.  
    121.                
    122.             }
    123.         }
    124.  
    125.  
    126.        
    127.     }
    128.    
    129. }
    130.  
    131.  
     
  3. Garrett-Lynch

    Garrett-Lynch

    Joined:
    Mar 13, 2013
    Posts:
    14
    Hi

    Thanks for your reply, I thought it was simply texture I should be using, didn't know about splatmap. This is what I have now to try and texture the terrain but stil nothing working - I keep getting an error "Terrain splat 0 is null". Any ideas anyone?


    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using Utility;
    4.  
    5. public class test : MonoBehaviour
    6. {
    7.     //use this for initialization
    8.     void Start ()
    9.     {
    10.         //create a new terrain data
    11.         TerrainData _terrainData = new TerrainData();
    12.  
    13.         //set terrain width, height, length
    14.         _terrainData.size = new Vector3(20, 1, 20);
    15.  
    16. SplatPrototype[] terrainTexture = new SplatPrototype[1];
    17. terrainTexture[0] = new SplatPrototype();
    18. terrainTexture[0].texture = (Texture2D)Resources.Load("Terrain Assets/Terrain Textures/Grass (Hill)");
    19.  
    20. _terrainData.splatPrototypes = terrainTexture;
    21.  
    22.         //Create a terrain with the set terrain data
    23.         GameObject _terrain = Terrain.CreateTerrainGameObject(_terrainData);
    24.     }
    25.    
    26.     // Update is called once per frame
    27.     void Update () {
    28.  
    29.     }
    30.    
    31. }
     
  4. Deleted User

    Deleted User

    Guest

    dont know why you get an error. But you will not see the Splatmap anyways, it doesnt get renderd! A splatmap describes where to render wich Texture (eg. Green is a Grass Texture, Blue a stone, red a sand and the Alpha channel a mud one)

    I would guess you need to parse these textures to the shader but Im not shure

    EDIT: here you can download the default shaders to look into them http://unity3d.com/unity/download/archive

    I guess you need to use
    function SetTexture (propertyName : String, texture : Texture) : void and the propertyNames should be:
    "_Control"
    "_Splat3"
    "_Splat2"
    "_Splat1"
    "_Splat0"
     
    Last edited by a moderator: Mar 18, 2013
  5. dariannikookar

    dariannikookar

    Joined:
    Feb 1, 2017
    Posts:
    3
    to: element_wsc
    (this is not a reply to fix the splats)

    I'm just familiarizing myself with coding and Unity and find your code to be very useful! I've finished a script to fix seams (Terrain.SetNeighbors) with a simple 1 index width height fix on all of the sides of each Terrain (TerrainData.SetHeights) and now I can combine your script with mine and create a terrain with a x * y grid-size with an automated seam fix. I'll be cleaning up my code a bit and post it as well. (later times :) ).
    If someone would like to use your code now, that someone just needs to fix a simple line in the CreateWindow() (line 34).
    the total function would look like this:

    public static void CreateWindow()
    {
    window = EditorWindow.GetWindow(typeof(CreateTiledTerrain));
    window.titleContent = new GUIContent("Tiled Terrain"); // this is the line that has been changed
    window.minSize = new Vector2(500f, 700f);
    }
    Some 'resolution' typos, but the code works :)
    So, two thumbs up, thank you very much. Even more than 4 years later :)
     
  6. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    I've added this method to load Raw files as heightmap, but still not working as expected, but if you load by hand everything is fine. Any advice?

    to made it works, you'll need the Raw files named exactly as the terrain will be.
    Indeed I've changed this line:
    Code (CSharp):
    1. string name = (x - 1) + "_" + (y - 1);

    add this line to the very bottom of "CreateTerrain"
    Code (CSharp):
    1. // Apply Height Map
    2.                 string file = Application.dataPath + "/" + rawFile + "/" + name + ".raw";
    3.                 //Debug.Log(file);
    4.                 LoadTerrainWithBytesArray(terrainData, file);
    5.                 terrain.GetComponent<Terrain>().Flush();

    And this is the method used to load and apply the heightmap.
    Code (CSharp):
    1. private void LoadTerrainWithBytesArray(TerrainData terrainData, string rawFile)
    2.     {
    3.         //Debug.Log(terrainData.name + ":" + rawFile);
    4.  
    5.         int heightmapWidth = terrainData.heightmapWidth;
    6.         int heightmapHeight = terrainData.heightmapHeight;
    7.  
    8.         byte[] heightmapBytes = File.ReadAllBytes(rawFile); // Loads the whole heightmap file into this array
    9.         float[,] heightmap = new float[heightmapWidth, heightmapHeight];
    10.         int byteNumber = 0; // Will be used to iterate through the 'bytes' array
    11.  
    12.         for(int width = 0; width < heightmapBytes.Length / (heightmapHeight * 2); width++)
    13.         {
    14.             for(int height = 0; height < heightmapBytes.Length / (heightmapHeight * 2); height++)
    15.             {
    16.                 //heightmap[width, height] = ((bytes[byteNumber] << 0x08) | bytes[byteNumber + 1]) / 65536f; // MAC Sets value in 'bytes' array to matrix
    17.                 heightmap[width, height] = ((ushort)(heightmapBytes[byteNumber] << 0x08) | heightmapBytes[byteNumber]) / 65536f; // PC IBM Sets value in 'bytes' array to matrix
    18.                 byteNumber += 2;
    19.             }
    20.         }
    21.  
    22.         terrainData.SetHeights(0, 0, heightmap);
    23.         heightmapBytes = null;
    24.     }