What is the best way to make a map (terrain) for my 2D game?

I have a 2D Unity project and I am running into something I don’t know how to do.

What is the best way to make a map?


  • It should look a bit like the map in settlers 2 golden edition only a newer design (maps that should also look like it: Stronghold, Anno, Banished and Age of Empires).
  • I want to have it as shown in the pictures.
  • There should be some small hills and the camera should not be straight down.

Any tips?





I already tried to make a map with isometric, only I didn’t get it nice and it’s blocky.

1 Like

I do mostly 3D stuff and recently dabbled in how 2D works. It seems like it is much more dependent on the art. I tried out tilemapping and it works really well, if a bit fiddly to setup, but the art needs to be absolutely perfect to get the look you want.

Are those pictures your isometric attempt? If so, they look pretty good to me.

Not a 2D expect here, but AFAICT by the look of the map and game’s texture data layout

(and I mean just by looking at this source file right here)

this is not a typical 2D sprite-based terrain but an actual triangular mesh that is rendered in such a way to look sprite-like and pixel crispy.

I can easily see generating an triangular grid mesh then using vertex color (or UV) to store both terrain height data + some texture ID/index and use local vertex pos as UV - to render decently similar-looking terrain with some shader magic here (even very basic one).

Here is a script that will generate you such a basic mesh:

using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent( typeof(MeshFilter) , typeof(MeshRenderer) )]
public class TriangularGridMesh : MonoBehaviour
{
    [Header("Grid Settings")]
    [SerializeField] float size = 1f;
    [SerializeField][Min(1)] int resolutionX = 10;
    [SerializeField][Min(1)] int resolutionY = 10;

    void OnEnable ()
    {
        Mesh mesh = new Mesh();
        mesh.name = "Equilateral Triangular Grid Mesh";
        GetComponent<MeshFilter>().sharedMesh = mesh;
        GenerateGrid( mesh );
    }

    void OnDisable ()
    {
        if( Application.isPlaying )
            Destroy( GetComponent<MeshFilter>().sharedMesh );
        else
            DestroyImmediate( GetComponent<MeshFilter>().sharedMesh );
    }

    void GenerateGrid ( Mesh mesh )
    {
        // Calculate vertex positions
        List<Vector3> vertices = new ();
        List<int> triangles = new ();
        float height = Mathf.Sqrt(3)/2*size;// Height of equilateral triangle
        for( int y=0 ; y<=resolutionY ; y++ )
        for( int x=0 ; x<=resolutionX ; x++ )
        {
            float xOffset = (y%2==0) ? 0 : size/2;
            vertices.Add( new Vector3( x*size+xOffset, y*height , 0) );
        }

        // Calculate triangles
        for( int y=0 ; y<resolutionY ; y++ )
        for( int x=0 ; x<resolutionX ; x++ )
        {
            int rowOffset = y * ( resolutionX + 1 );
            int bottomLeft = rowOffset + x;
            int bottomRight = bottomLeft + 1;
            int topLeft = bottomLeft + resolutionX + 1;
            int topRight = topLeft + 1;
            if( y%2==0 )
            {
                // First triangle (bottom-left)
                triangles.Add( bottomLeft );
                triangles.Add( topLeft );
                triangles.Add( bottomRight );

                // Second triangle (top-right)
                triangles.Add( bottomRight );
                triangles.Add( topLeft );
                triangles.Add( topRight );
            }
            else
            {
                // First triangle (top-left)
                triangles.Add( bottomLeft );
                triangles.Add( topLeft );
                triangles.Add( topRight );

                // Second triangle (bottom-right)
                triangles.Add( bottomLeft );
                triangles.Add( topRight );
                triangles.Add( bottomRight );
            }
        }

        // Assign data to mesh
        mesh.SetVertices( vertices );
        mesh.SetIndices( triangles , MeshTopology.Triangles , 0 );
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
    }

}

2 Likes

thank you very much for your reply, I really appreciate it!
I got in it in unity like in your picture,
but I still don’t quite understand how to get height difference.

You can generate a terrain-like height data for a mesh with perlin noise.

This code will do that:

using System;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent( typeof(MeshFilter) , typeof(MeshRenderer) )]
public class TriangularGridMesh : MonoBehaviour
{
    [Header("Grid Settings")]
    [SerializeField] float _gridTriangleLength = 1f;
    [SerializeField][Min(1)] Vector2Int _gridResolution = new (40,30);

    [Header("Noise Layer 0 Settings")]
    [SerializeField][Range(float.Epsilon,1)] float _noise0Scale = 0.15f;
    [SerializeField] Vector2 _noise0Offset = new ();
    [SerializeField] Vector2 _noise0HeightRange = new ( -0.5f , 1f );
    [Header("Noise Layer 1 Settings")]
    [SerializeField][Range(float.Epsilon,1)] float _noise1Scale = 0.7f;
    [SerializeField] Vector2 _noise1Offset = new ();
    [SerializeField] Vector2 _noise1HeightRange = new ( -0.2f , 0.1f );

    Mesh _mesh;

    #if UNITY_EDITOR
    void OnValidate ()
    {
        if( _mesh!=null )
            GenerateGrid( _mesh );
    }
    #endif

    void OnEnable ()
    {
        if( _mesh==null )
        {
            _mesh = new Mesh{ name = "Equilateral Triangular Grid Mesh" };
            GetComponent<MeshFilter>().sharedMesh = _mesh;
        }
        
        GenerateGrid( _mesh );
    }

    void OnDestroy ()
    {
        if( Application.isPlaying ) Destroy( _mesh );
        else DestroyImmediate( _mesh );
    }

    void GenerateGrid ( Mesh mesh )
    {
        int numVertices = ( _gridResolution.x + 1 ) * ( _gridResolution.y + 1 );
        List<Vector3> vertices = new List<Vector3>( numVertices );
        List<int> indices = new List<int>( _gridResolution.x * _gridResolution.y * 6 );

        float trih = Mathf.Sqrt(3) / 2 * _gridTriangleLength; // Height of equilateral triangle
        for( int y=0 ; y <= _gridResolution.y ; y++ )
        for( int x=0 ; x <= _gridResolution.x ; x++ )
        {
            float xOffset = ( y%2==0 ) ? 0 : _gridTriangleLength/2;
            Vector3 vert = new Vector3( x * _gridTriangleLength + xOffset , y * trih , 0 );
            float noiseValue;
            {
                // calculate raw noise value
                float layer0 = Mathf.PerlinNoise( ( vert.x + _noise0Offset.x ) * _noise0Scale , ( vert.y + _noise0Offset.y ) * _noise0Scale );
                float layer1 = Mathf.PerlinNoise( ( vert.x + _noise1Offset.x ) * _noise1Scale , ( vert.y + _noise1Offset.y ) * _noise1Scale );

                // apply height range settings
                layer0 = Mathf.Lerp( _noise0HeightRange.x , _noise0HeightRange.y , layer0 );
                layer1 = Mathf.Lerp( _noise1HeightRange.x , _noise1HeightRange.y , layer1 );

                // add up layers
                noiseValue = layer0 + layer1;
            }
            vert.z = -noiseValue;// negative so terrain grows in direction of the camera (default 2D facing Z+)
            vertices.Add( vert );
        }

        for( int y=0 ; y < _gridResolution.y ; y++ )
        for( int x=0 ; x < _gridResolution.x ; x++ )
        {
            int rowOffset = y * ( _gridResolution.x + 1 );
            int bottomLeft = rowOffset + x;
            int bottomRight = bottomLeft + 1;
            int topLeft = bottomLeft + _gridResolution.x + 1;
            int topRight = topLeft + 1;

            if( y%2==0 )
            {
                indices.Add( bottomLeft );
                indices.Add( topLeft );
                indices.Add( bottomRight );

                indices.Add( bottomRight );
                indices.Add( topLeft );
                indices.Add( topRight );
            }
            else
            {
                indices.Add( bottomLeft );
                indices.Add( topLeft );
                indices.Add( topRight );

                indices.Add( bottomLeft );
                indices.Add( topRight );
                indices.Add( bottomRight );
            }
        }

        mesh.SetVertices( vertices );
        mesh.SetIndices( indices , MeshTopology.Triangles , 0 );
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
    }

}

1 Like

To give you some more idea of how you can develop this further here is a script that will color vertices based on their terrain height value:

unlit vertex color shader graph here

create empty text file → paste this → rename file to vertex color.shadergraph

{
    "m_SGVersion": 3,
    "m_Type": "UnityEditor.ShaderGraph.GraphData",
    "m_ObjectId": "a1e3d7d9c1f04c97b4a1b64d078ac310",
    "m_Properties": [],
    "m_Keywords": [],
    "m_Dropdowns": [],
    "m_CategoryData": [
        {
            "m_Id": "36de4228b69e4553b14c19ee59542d46"
        }
    ],
    "m_Nodes": [
        {
            "m_Id": "053b9373654647b2812df8e434bc965a"
        },
        {
            "m_Id": "d84ff2256c694e229e658b48f3b6a194"
        },
        {
            "m_Id": "ba80706549094eaa86b276deb633b297"
        },
        {
            "m_Id": "d1e71dd17f9345aab0dcdca1044a2bf1"
        },
        {
            "m_Id": "69e6593924f1451c9da18a57102e9bc3"
        },
        {
            "m_Id": "1cc32cc85efc42fb95626028ac8e8613"
        },
        {
            "m_Id": "34031298249c4fd8849d6e60a7825876"
        },
        {
            "m_Id": "83385588fd51458d9e589092f1c44466"
        },
        {
            "m_Id": "645e9d945be941e5a3a30816b8db72b0"
        },
        {
            "m_Id": "f7aba0fa890d406b811c0052dbfdb6e6"
        }
    ],
    "m_GroupDatas": [],
    "m_StickyNoteDatas": [],
    "m_Edges": [
        {
            "m_OutputSlot": {
                "m_Node": {
                    "m_Id": "69e6593924f1451c9da18a57102e9bc3"
                },
                "m_SlotId": 0
            },
            "m_InputSlot": {
                "m_Node": {
                    "m_Id": "d1e71dd17f9345aab0dcdca1044a2bf1"
                },
                "m_SlotId": 0
            }
        }
    ],
    "m_VertexContext": {
        "m_Position": {
            "x": 0.0,
            "y": 0.0
        },
        "m_Blocks": [
            {
                "m_Id": "053b9373654647b2812df8e434bc965a"
            },
            {
                "m_Id": "d84ff2256c694e229e658b48f3b6a194"
            },
            {
                "m_Id": "ba80706549094eaa86b276deb633b297"
            }
        ]
    },
    "m_FragmentContext": {
        "m_Position": {
            "x": 0.0,
            "y": 200.0
        },
        "m_Blocks": [
            {
                "m_Id": "d1e71dd17f9345aab0dcdca1044a2bf1"
            },
            {
                "m_Id": "1cc32cc85efc42fb95626028ac8e8613"
            },
            {
                "m_Id": "34031298249c4fd8849d6e60a7825876"
            },
            {
                "m_Id": "83385588fd51458d9e589092f1c44466"
            },
            {
                "m_Id": "645e9d945be941e5a3a30816b8db72b0"
            },
            {
                "m_Id": "f7aba0fa890d406b811c0052dbfdb6e6"
            }
        ]
    },
    "m_PreviewData": {
        "serializedMesh": {
            "m_SerializedMesh": "{\"mesh\":{\"instanceID\":0}}",
            "m_Guid": ""
        },
        "preventRotation": false
    },
    "m_Path": "Shader Graphs",
    "m_GraphPrecision": 1,
    "m_PreviewMode": 2,
    "m_OutputNode": {
        "m_Id": ""
    },
    "m_SubDatas": [],
    "m_ActiveTargets": [
        {
            "m_Id": "4b04312bda4b47dc9ad53dd19a56063d"
        },
        {
            "m_Id": "7b1ca994a72f4f16b3ab390eba6cddad"
        }
    ]
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "053b9373654647b2812df8e434bc965a",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "VertexDescription.Position",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "d68c868cad6c4d23aceb364b3b24ea02"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "VertexDescription.Position"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.Vector1MaterialSlot",
    "m_ObjectId": "0f65c65eb2984de39b3d52156fa4a579",
    "m_Id": 0,
    "m_DisplayName": "Metallic",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "Metallic",
    "m_StageCapability": 2,
    "m_Value": 0.0,
    "m_DefaultValue": 0.0,
    "m_Labels": []
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "1cc32cc85efc42fb95626028ac8e8613",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "SurfaceDescription.Smoothness",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "e0140c855fa6496a9e8279e8627280e2"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "SurfaceDescription.Smoothness"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "34031298249c4fd8849d6e60a7825876",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "SurfaceDescription.NormalTS",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "c43e279702624005bdf6c69004d11319"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "SurfaceDescription.NormalTS"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.CategoryData",
    "m_ObjectId": "36de4228b69e4553b14c19ee59542d46",
    "m_Name": "",
    "m_ChildObjectList": []
}

{
    "m_SGVersion": 2,
    "m_Type": "UnityEditor.Rendering.BuiltIn.ShaderGraph.BuiltInTarget",
    "m_ObjectId": "4b04312bda4b47dc9ad53dd19a56063d",
    "m_Datas": [],
    "m_ActiveSubTarget": {
        "m_Id": "6fe7e04361334e7c8023ce26a75ea35f"
    },
    "m_AllowMaterialOverride": false,
    "m_SurfaceType": 0,
    "m_ZWriteControl": 0,
    "m_ZTestMode": 4,
    "m_AlphaMode": 0,
    "m_RenderFace": 2,
    "m_AlphaClip": false,
    "m_CustomEditorGUI": ""
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.ColorRGBMaterialSlot",
    "m_ObjectId": "62a8a603e4374eaca9adf0ecea4bd780",
    "m_Id": 0,
    "m_DisplayName": "Base Color",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "BaseColor",
    "m_StageCapability": 2,
    "m_Value": {
        "x": 0.5,
        "y": 0.5,
        "z": 0.5
    },
    "m_DefaultValue": {
        "x": 0.5,
        "y": 0.5,
        "z": 0.5
    },
    "m_Labels": [],
    "m_ColorMode": 0,
    "m_DefaultColor": {
        "r": 0.5,
        "g": 0.5,
        "b": 0.5,
        "a": 1.0
    }
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.Vector4MaterialSlot",
    "m_ObjectId": "63c9844608d64a72be85dacad8cb37c0",
    "m_Id": 0,
    "m_DisplayName": "Out",
    "m_SlotType": 1,
    "m_Hidden": false,
    "m_ShaderOutputName": "Out",
    "m_StageCapability": 3,
    "m_Value": {
        "x": 1.0,
        "y": 1.0,
        "z": 1.0,
        "w": 1.0
    },
    "m_DefaultValue": {
        "x": 1.0,
        "y": 1.0,
        "z": 1.0,
        "w": 1.0
    },
    "m_Labels": []
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "645e9d945be941e5a3a30816b8db72b0",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "SurfaceDescription.Occlusion",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "73c0e0b8eb77488b9d5650ba4d236eac"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "SurfaceDescription.Occlusion"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.VertexColorNode",
    "m_ObjectId": "69e6593924f1451c9da18a57102e9bc3",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "Vertex Color",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": -451.0,
            "y": 200.0,
            "width": 208.0,
            "height": 278.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "63c9844608d64a72be85dacad8cb37c0"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 2,
    "m_CustomColors": {
        "m_SerializableColors": []
    }
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.ColorRGBMaterialSlot",
    "m_ObjectId": "6a1a162760864f83a3bc66486ab95e03",
    "m_Id": 0,
    "m_DisplayName": "Emission",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "Emission",
    "m_StageCapability": 2,
    "m_Value": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_DefaultValue": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_Labels": [],
    "m_ColorMode": 1,
    "m_DefaultColor": {
        "r": 0.0,
        "g": 0.0,
        "b": 0.0,
        "a": 1.0
    }
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.Rendering.BuiltIn.ShaderGraph.BuiltInLitSubTarget",
    "m_ObjectId": "6fe7e04361334e7c8023ce26a75ea35f",
    "m_WorkflowMode": 1,
    "m_NormalDropOffSpace": 0
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.Vector1MaterialSlot",
    "m_ObjectId": "73c0e0b8eb77488b9d5650ba4d236eac",
    "m_Id": 0,
    "m_DisplayName": "Ambient Occlusion",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "Occlusion",
    "m_StageCapability": 2,
    "m_Value": 1.0,
    "m_DefaultValue": 1.0,
    "m_Labels": []
}

{
    "m_SGVersion": 1,
    "m_Type": "UnityEditor.Rendering.Universal.ShaderGraph.UniversalTarget",
    "m_ObjectId": "7b1ca994a72f4f16b3ab390eba6cddad",
    "m_Datas": [],
    "m_ActiveSubTarget": {
        "m_Id": "ca3149dcd1464e13801f5797985acb3b"
    },
    "m_AllowMaterialOverride": false,
    "m_SurfaceType": 0,
    "m_ZTestMode": 4,
    "m_ZWriteControl": 0,
    "m_AlphaMode": 0,
    "m_RenderFace": 2,
    "m_AlphaClip": false,
    "m_CastShadows": true,
    "m_ReceiveShadows": true,
    "m_DisableTint": false,
    "m_AdditionalMotionVectorMode": 0,
    "m_AlembicMotionVectors": false,
    "m_SupportsLODCrossFade": false,
    "m_CustomEditorGUI": "",
    "m_SupportVFX": false
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "83385588fd51458d9e589092f1c44466",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "SurfaceDescription.Emission",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "6a1a162760864f83a3bc66486ab95e03"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "SurfaceDescription.Emission"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.NormalMaterialSlot",
    "m_ObjectId": "a82d01cce4534c849fd91e5c2103c6a2",
    "m_Id": 0,
    "m_DisplayName": "Normal",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "Normal",
    "m_StageCapability": 1,
    "m_Value": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_DefaultValue": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_Labels": [],
    "m_Space": 0
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.TangentMaterialSlot",
    "m_ObjectId": "b7c3171f94f14381bb32523f2b7cd180",
    "m_Id": 0,
    "m_DisplayName": "Tangent",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "Tangent",
    "m_StageCapability": 1,
    "m_Value": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_DefaultValue": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_Labels": [],
    "m_Space": 0
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "ba80706549094eaa86b276deb633b297",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "VertexDescription.Tangent",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "b7c3171f94f14381bb32523f2b7cd180"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "VertexDescription.Tangent"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.NormalMaterialSlot",
    "m_ObjectId": "c43e279702624005bdf6c69004d11319",
    "m_Id": 0,
    "m_DisplayName": "Normal (Tangent Space)",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "NormalTS",
    "m_StageCapability": 2,
    "m_Value": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_DefaultValue": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_Labels": [],
    "m_Space": 3
}

{
    "m_SGVersion": 2,
    "m_Type": "UnityEditor.Rendering.Universal.ShaderGraph.UniversalUnlitSubTarget",
    "m_ObjectId": "ca3149dcd1464e13801f5797985acb3b"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "d1e71dd17f9345aab0dcdca1044a2bf1",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "SurfaceDescription.BaseColor",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "62a8a603e4374eaca9adf0ecea4bd780"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "SurfaceDescription.BaseColor"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.PositionMaterialSlot",
    "m_ObjectId": "d68c868cad6c4d23aceb364b3b24ea02",
    "m_Id": 0,
    "m_DisplayName": "Position",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "Position",
    "m_StageCapability": 1,
    "m_Value": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_DefaultValue": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
    },
    "m_Labels": [],
    "m_Space": 0
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "d84ff2256c694e229e658b48f3b6a194",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "VertexDescription.Normal",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "a82d01cce4534c849fd91e5c2103c6a2"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "VertexDescription.Normal"
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.Vector1MaterialSlot",
    "m_ObjectId": "e0140c855fa6496a9e8279e8627280e2",
    "m_Id": 0,
    "m_DisplayName": "Smoothness",
    "m_SlotType": 0,
    "m_Hidden": false,
    "m_ShaderOutputName": "Smoothness",
    "m_StageCapability": 2,
    "m_Value": 0.5,
    "m_DefaultValue": 0.5,
    "m_Labels": []
}

{
    "m_SGVersion": 0,
    "m_Type": "UnityEditor.ShaderGraph.BlockNode",
    "m_ObjectId": "f7aba0fa890d406b811c0052dbfdb6e6",
    "m_Group": {
        "m_Id": ""
    },
    "m_Name": "SurfaceDescription.Metallic",
    "m_DrawState": {
        "m_Expanded": true,
        "m_Position": {
            "serializedVersion": "2",
            "x": 0.0,
            "y": 0.0,
            "width": 0.0,
            "height": 0.0
        }
    },
    "m_Slots": [
        {
            "m_Id": "0f65c65eb2984de39b3d52156fa4a579"
        }
    ],
    "synonyms": [],
    "m_Precision": 0,
    "m_PreviewExpanded": true,
    "m_DismissedVersion": 0,
    "m_PreviewMode": 0,
    "m_CustomColors": {
        "m_SerializableColors": []
    },
    "m_SerializedDescriptor": "SurfaceDescription.Metallic"
}


  • blue - windy seas
  • yellow - sandy beaches
  • green - grassy meadows
  • grey - rocky mountains
  • white - snowy peaks
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent( typeof(MeshFilter) , typeof(MeshRenderer) )]
public class TriangularGridMesh : MonoBehaviour
{
    [Header("Grid Settings")]
    [SerializeField] float _gridTriangleLength = 1f;
    [SerializeField][Min(1)] Vector2Int _gridResolution = new (40,30);

    [Header("Noise Layer 0 Settings")]
    [SerializeField][Range(float.Epsilon,1)] float _noise0Scale = 0.15f;
    [SerializeField] Vector2 _noise0Offset = new ();
    [SerializeField] Vector2 _noise0HeightRange = new ( -0.5f , 1f );
    [Header("Noise Layer 1 Settings")]
    [SerializeField][Range(float.Epsilon,1)] float _noise1Scale = 0.7f;
    [SerializeField] Vector2 _noise1Offset = new ();
    [SerializeField] Vector2 _noise1HeightRange = new ( -0.2f , 0.1f );

    Mesh _mesh;

    #if UNITY_EDITOR
    void OnValidate ()
    {
        if( _mesh!=null )
            GenerateGrid( _mesh );
    }
    #endif

    void OnEnable ()
    {
        if( _mesh==null )
        {
            _mesh = new Mesh{ name = "Equilateral Triangular Grid Mesh" };
            GetComponent<MeshFilter>().sharedMesh = _mesh;
        }
        
        GenerateGrid( _mesh );
    }

    void OnDestroy ()
    {
        if( Application.isPlaying ) Destroy( _mesh );
        else DestroyImmediate( _mesh );
    }

    void GenerateGrid ( Mesh mesh )
    {
        int numVertices = ( _gridResolution.x + 1 ) * ( _gridResolution.y + 1 );
        List<Vector3> vertices = new ( numVertices );
        List<int> indices = new ( _gridResolution.x * _gridResolution.y * 6 );
        List<Color> colors = new ( numVertices );
        List<Vector2> uvs = new ( numVertices );

        float trih = Mathf.Sqrt(3) / 2 * _gridTriangleLength; // Height of equilateral triangle
        for( int y=0 ; y <= _gridResolution.y ; y++ )
        for( int x=0 ; x <= _gridResolution.x ; x++ )
        {
            float xOffset = ( y%2==0 ) ? 0 : _gridTriangleLength/2;
            Vector3 vert = new Vector3( x * _gridTriangleLength + xOffset , y * trih , 0 );
            float noiseValue;
            {
                // calculate raw noise value
                float layer0 = Mathf.PerlinNoise( ( vert.x + _noise0Offset.x ) * _noise0Scale , ( vert.y + _noise0Offset.y ) * _noise0Scale );
                float layer1 = Mathf.PerlinNoise( ( vert.x + _noise1Offset.x ) * _noise1Scale , ( vert.y + _noise1Offset.y ) * _noise1Scale );

                // apply height range settings
                layer0 = Mathf.Lerp( _noise0HeightRange.x , _noise0HeightRange.y , layer0 );
                layer1 = Mathf.Lerp( _noise1HeightRange.x , _noise1HeightRange.y , layer1 );

                // add up layers
                noiseValue = layer0 + layer1;
            }
            vert.z = -noiseValue;// negative so terrain grows in direction of the camera (default 2D facing Z+)
            if( noiseValue<0 ) vert.z = 0;// makes water flat
            vertices.Add( vert );

            uvs.Add( new Vector2( vert.x , vert.y ) );

            Color col;
            if( noiseValue>0.7f )
            {
                // snowy peaks
                col = Color.white;
            }
            else if( noiseValue>0.6f )
            {
                // rocky mountains
                col = Color.gray;
            }
            else if( noiseValue>0.1f )
            {
                // grassy meadows
                col = Color.green;
            }
            else if( noiseValue>0.05f )
            {
                // sandy beaches
                col = Color.yellow;
            }
            else
            {
                // windy seas
                col = Color.blue;
            }
            colors.Add( col );
        }

        for( int y=0 ; y < _gridResolution.y ; y++ )
        for( int x=0 ; x < _gridResolution.x ; x++ )
        {
            int rowOffset = y * ( _gridResolution.x + 1 );
            int bottomLeft = rowOffset + x;
            int bottomRight = bottomLeft + 1;
            int topLeft = bottomLeft + _gridResolution.x + 1;
            int topRight = topLeft + 1;

            if( y%2==0 )
            {
                indices.Add( bottomLeft );
                indices.Add( topLeft );
                indices.Add( bottomRight );

                indices.Add( bottomRight );
                indices.Add( topLeft );
                indices.Add( topRight );
            }
            else
            {
                indices.Add( bottomLeft );
                indices.Add( topLeft );
                indices.Add( topRight );

                indices.Add( bottomLeft );
                indices.Add( topRight );
                indices.Add( bottomRight );
            }
        }

        mesh.SetVertices( vertices );
        mesh.SetIndices( indices , MeshTopology.Triangles , 0 );
        mesh.SetUVs( 0 , uvs );
        mesh.SetColors( colors );
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
    }

}

2 Likes