Edit (2016): new script with undo functionality, and editor script for inspector buttons.
Disclaimer : use at your own risk. Back up your terrain before using. I won’t be held responsible for lost terrain ! (though this version does have undo which should hopefully cover any accidents)
STEP ONE : save your terrain! Export it by right-clicking on it in the Project Window, then select Export Package…
#How to use this :#
This script can be run in the Editor without playing. Attach the script to the terrain or an empty gameobject.
Assign the terrain to be modified in the inspector.
Set the amount in units to raise or lower the terrain by. If the height of the tallest peak is greater than the max height of the terrain, these peaks will become flattened. Same if the height is lower than the min height.
Click the ‘Raise/Lower Terrain’ button.
If there are previous heights stored, an Undo button will show.
please note: delete the script and add a new script for editing a different terrain. I havn’t included anything for checking if a different terrain is assigned in the inspector, so please delete this script and add a new script when editing a different terrain, otherwise the undo will write the previous terrains stored undo heights, and that would be very bad.
TerrainRaiseLowerHeightmap.cs :
//--------------------------------//
// TerrainRaiseLowerHeightmap.cs //
// Written by Jay Kay //
// 2016/4/8 //
//--------------------------------//
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TerrainRaiseLowerHeightmap : MonoBehaviour
{
public Terrain terrain;
public float changeHeightInUnits = 0f;
[HideInInspector] public List< float[,] > undoHeights = new List< float[,] >();
// ------------------------------------------------------- Modify Heightmap Functions
public void ModifyTerrainHeightmap()
{
// check if terrain is assigned in inspector
if ( !terrain )
{
Debug.LogError( gameObject.name + " has no terrain assigned in the inspector" );
return;
}
// get reference variables
TerrainData terrainData = terrain.terrainData;
int heightmapWidth = terrainData.heightmapWidth;
int heightmapHeight = terrainData.heightmapHeight;
// copy current heights
float[,] currentHeights = terrainData.GetHeights( 0, 0, heightmapWidth, heightmapHeight );
undoHeights.Add( currentHeights );
// make new height array
float[,] newHeights = new float[ heightmapWidth, heightmapHeight ];
float terrainHeight = terrainData.size.y;
for ( int y = 0; y < heightmapWidth; y++ )
{
for ( int x = 0; x < heightmapHeight; x++ )
{
newHeights[ y, x ] = Mathf.Clamp01( currentHeights[ y, x ] + ( changeHeightInUnits / terrainHeight ) );
}
}
// apply to terrain
terrainData.SetHeights( 0, 0, newHeights );
Debug.Log( "Raise/Lower Heights completed" );
}
// ------------------------------------------------------- Undo Functions
public void UndoModifyTerrainHeightmap()
{
// check if terrain is assigned in inspector
if ( !terrain )
{
Debug.LogError( gameObject.name + " has no terrain assigned in the inspector" );
return;
}
// get last heights
float[,] newHeights = undoHeights[ undoHeights.Count - 1 ];
// apply to terrain
terrain.terrainData.SetHeights( 0, 0, newHeights );
// remove from list
undoHeights.RemoveAt( undoHeights.Count - 1 );
Debug.Log( "Undo Heights completed" );
}
}
TerrainRaiseLowerHeightmapEditor.cs - IMPORTANT- place this script in an Editor folder :
//--------------------------------//
// TerrainRaiseLowerHeightmap.cs //
// Written by Jay Kay //
// 2016/4/8 //
//--------------------------------//
using UnityEditor;
using UnityEngine;
[ CustomEditor( typeof( TerrainRaiseLowerHeightmap ) ) ]
public class TerrainRaiseLowerHeightmapEditor : Editor
{
private GameObject obj;
private TerrainRaiseLowerHeightmap objScript;
void OnEnable()
{
obj = Selection.activeGameObject;
objScript = obj.GetComponent< TerrainRaiseLowerHeightmap >();
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
// spacing between buttons
EditorGUILayout.Space();
// check if there is a terrain
if ( objScript.terrain == null )
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label( "Assign a terrain to modify", GUILayout.MinWidth( 80 ), GUILayout.MaxWidth( 350 ) );
EditorGUILayout.EndHorizontal();
return;
}
// raise/lower button
EditorGUILayout.BeginHorizontal();
if ( GUILayout.Button( "Raise/Lower Terrain", GUILayout.MinWidth( 80 ), GUILayout.MaxWidth( 350 ) ) )
{
objScript.ModifyTerrainHeightmap();
}
EditorGUILayout.EndHorizontal();
// check if there is an undo array
if ( objScript.undoHeights.Count > 0 )
{
// spacing between buttons
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
if ( GUILayout.Button( "UNDO", GUILayout.MinWidth( 80 ), GUILayout.MaxWidth( 350 ) ) )
{
objScript.UndoModifyTerrainHeightmap();
}
EditorGUILayout.EndHorizontal();
}
}
}
This script is part of an upcoming Terrain Tools package I’m currently working on =]
Original Answer (2014):
While the OP has done an excellent job of answering their own question, I would like to provide another answer using scripting.
Disclaimer : use at your own risk. Back up your terrain before using. I won’t be held responsible for lost terrain !
STEP ONE : save your terrain! Export it by right-clicking on it in the Project Window, then select Export Package…
#How to use this :#
This script can be run in the Editor without playing. Attach the script to the terrain.
Right-click on the script component in the Inspector, then a drop-down selection box appears. Example :
There should be an option Raise Terrain Heightmap, click on this. Now the script will run and show a console message when complete.
The amount the height is raised by uses world-space values :
public var raiseHeightInUnits : float = 20.0;
so this would raise the terrain by 20 units.
Warning : if the height of the tallest peak is greater than the max height of the terrain, these peaks will become flattened. That’s why it’s important to backup the terrain first, in-case of any mistakes. I could write in some code to make sure this doesn’t happen, but with careful implementation and gradual increments to the raiseHeightInUnits, there should be no problems =]
#pragma strict
#if UNITY_EDITOR
@ContextMenu( "Raise Terrain Heightmap" )
function RaiseTerrainHeightmap()
{
RaiseHeights();
}
#endif
public var myTerrain : Terrain;
private var terrainData : TerrainData;
private var heightmapWidth : int;
private var heightmapHeight : int;
private var heightmapData : float[,];
public var raiseHeightInUnits : float = 20.0;
function RaiseHeights()
{
// - GetTerrainData -
if ( !myTerrain )
{
//myTerrain = Terrain.activeTerrain;
Debug.LogError( gameObject.name + " has no terrain assigned in the inspector" );
}
terrainData = myTerrain.terrainData;
heightmapWidth = myTerrain.terrainData.heightmapWidth;
heightmapHeight = myTerrain.terrainData.heightmapHeight;
// --
// store old heightmap data
heightmapData = terrainData.GetHeights( 0, 0, heightmapWidth, heightmapHeight );
var terrainHeight : float = terrainData.size.y;
// --
var y : int = 0;
var x : int = 0;
// raise heights
for ( y = 0; y < heightmapHeight; y ++ )
{
for ( x = 0; x < heightmapWidth; x ++ )
{
var newHeight : float = Mathf.Clamp01( heightmapData[ y, x ] + ( raiseHeightInUnits / terrainHeight ) );
heightmapData[ y, x ] = newHeight;
}
}
terrainData.SetHeights( 0, 0, heightmapData );
Debug.Log( "RaiseHeights() completed" );
}