- Home /
Modifying terrain height under a gameobject at runtime
I've checked this and the Terrain and TerrainData reference.
But I tried and cannot figure out how to modify the terrain by scripting at runtime. What I actually want is to be able to raise the terrain under a certain gameobject.
Any help or examples will be appreaciated. Thanks
Answer by pirulo · Feb 05, 2010 at 03:42 AM
thank for yor answer Duck, I finally figured it out. I paste the code I used so that anyone who has the same problem can check it out. It is not that good but I hope it helps.
Unluckily if you modify the terrain at runtime it doesnt reset to the original heights it had before you played the scene.
Also, I havent thought of an algorithm to raise the terrain in a smooth way (this is plain blocky), but it would be good to raise it smoothly as if a mountain was raising.
Terrain terr; // terrain to modify
int hmWidth; // heightmap width
int hmHeight; // heightmap height
int posXInTerrain; // position of the game object in terrain width (x axis)
int posYInTerrain; // position of the game object in terrain height (z axis)
int size = 50; // the diameter of terrain portion that will raise under the game object
float desiredHeight = 0; // the height we want that portion of terrain to be
void Start () {
terr = Terrain.activeTerrain;
hmWidth = terr.terrainData.heightmapWidth;
hmHeight = terr.terrainData.heightmapHeight;
}
void Update () {
// get the normalized position of this game object relative to the terrain
Vector3 tempCoord = (transform.position - terr.gameObject.transform.position);
Vector3 coord;
coord.x = tempCoord.x / terr.terrainData.size.x;
coord.y = tempCoord.y / terr.terrainData.size.y;
coord.z = tempCoord.z / terr.terrainData.size.z;
// get the position of the terrain heightmap where this game object is
posXInTerrain = (int) (coord.x * hmWidth);
posYInTerrain = (int) (coord.z * hmHeight);
// we set an offset so that all the raising terrain is under this game object
int offset = size / 2;
// get the heights of the terrain under this game object
float[,] heights = terr.terrainData.GetHeights(posXInTerrain-offset,posYInTerrain-offset,size,size);
// we set each sample of the terrain in the size to the desired height
for (int i=0; i < size; i++)
for (int j=0; j < size; j++)
heights[i,j] = desiredHeight;
// go raising the terrain slowly
desiredHeight += Time.deltaTime;
// set the new height
terr.terrainData.SetHeights(posXInTerrain-offset,posYInTerrain-offset,heights);
}
What is the code format? if I save is as javascript, cSharp or boo I get lots of compile errors.
It looks like C#, but it appears to be missing the Unity engine and System collections namespaces and it does not derive from $$anonymous$$onoBehavior. You probably need to add the 'using' and 'extends' parts at the beginning like any other C# code. If you are getting errors, they may be because the compiler doesn't know what those functions, variables, and class variables mean because it doesn't know where to find that information. Like trying to define an unknown word without a dictionary. It just doesn't work.
Works like a charm. Good work.
I've added a small script for resetting the terrain after the game ends:
using UnityEngine;
public class ResetTerrainOnDestroy : $$anonymous$$onoBehaviour
{
#region Fields
public Terrain Terrain;
private float[,] originalHeights;
#endregion
#region $$anonymous$$ethods
private void OnDestroy()
{
this.Terrain.terrainData.SetHeights(0, 0, this.originalHeights);
}
private void Start()
{
this.originalHeights = this.Terrain.terrainData.GetHeights(
0, 0, this.Terrain.terrainData.heightmapWidth, this.Terrain.terrainData.heightmapHeight);
}
#endregion
}
6 years later I still found this useful, thanks! I changed it a bit - heightmapwidth is now deprecated and swapped from OnDestroy to OnApplicationQuit, since (for me at least) the ingame terrain GameObject also gets destroyed and OnDestroy order is not guaranteed and thus terrain resetting was not guaranteed to work. OnApplicationQuit is called before OnDestroy tho so in any case terrain reset is guaranteed.
Full working example:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapReset : MonoBehaviour
{
public Terrain terrain;
private float[,] originalHeights;
private void OnApplicationQuit() {
this.terrain.terrainData.SetHeights(0, 0, this.originalHeights);
}
private void Start()
{
this.originalHeights = this.terrain.terrainData.GetHeights(
0, 0, this.terrain.terrainData.heightmapResolution, this.terrain.terrainData.heightmapResolution);
}
}
Ok so I know I am abit late to this post, but I am getting a few error with Pirulo's script aswell. I am receiving errors for the lines:
for (int i=0; i<size; i++)
for (int j=0; j<size; j++)
I am receiving the following errors in Unity.:
error CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statement
and
error CS1525: Unexpected symbol
;', expecting)', or `,'
Is anyone able to help me out and explain what is causing these errors?
Thanks a lot!
@TubooBokBok I think that there was a small problem when the code was copied. The problematic lines:
for (int i=0; i<size; i++)
for (int j=0; j<size; j++)
should be this:
for (int i=0; i<size; i++)
for (int j=0; j<size; j++)
If there are still problems, try changing < to ,=, etc. get turned into <, >, &eq, etc. I hope this solves your error.
Answer by duck · Feb 04, 2010 at 07:53 PM
You need to use GetHeights and SetHeights. Specifically:
- convert your GameObject's position to normalized terrain coordinates (i.e. in the range 0-1)
- use terrainData.GetHeights(x,y,width,height) to get an array containing the current height at that location (in your case, width and height would probably be 1,1)
- Modify the values in the array, to increase the height
- use terrainData.SetHeights(x,y,yourHeightArray) to write the new heights back to that location on the terrain.
Answer by JOKaija · Feb 10, 2015 at 08:33 PM
I made a little modification for code. Took couple of hours, so there maid be a fine adjusting....
This is, what I'm using, when settling buildings over terrain, which is not flat.
Before inserting a building to the terrain, first look terrain height with raycast at wanted position.
Then call the routine with raycasted height + xxx (extra height). You can set width & height & terrain texture with size also.
Routine can fine adjust raised terrain edges within given distance from newly levelled area.
If you want to test this piece of fixed code, just create a new scene. Add terrain to it. Add two textures to it. Add camera pointing to terrain. Add this script to your terrain inspector. Left mouse click raises and right lovers pointed terrain. Also, enable "Test with mouse" from scipt's inspector.
using UnityEngine;
using System.Collections;
public class RaiseLowerTerrain : MonoBehaviour
{
public bool TestWithMouse = false;
public Terrain myTerrain;
public int SmoothArea;
private int xResolution;
private int zResolution;
private float[,] heights;
private float[,] heightMapBackup;
protected const float DEPTH_METER_CONVERT=0.05f;
protected const float TEXTURE_SIZE_MULTIPLIER = 1.25f;
public int DeformationTextureNum = 1;
protected int alphaMapWidth;
protected int alphaMapHeight;
protected int numOfAlphaLayers;
private float[, ,] alphaMapBackup;
void Start()
{
xResolution = myTerrain.terrainData.heightmapWidth;
zResolution = myTerrain.terrainData.heightmapHeight;
alphaMapWidth = myTerrain.terrainData.alphamapWidth;
alphaMapHeight = myTerrain.terrainData.alphamapHeight;
numOfAlphaLayers = myTerrain.terrainData.alphamapLayers;
if (Debug.isDebugBuild)
{
heights = myTerrain.terrainData.GetHeights (0, 0, xResolution, zResolution);
heightMapBackup = myTerrain.terrainData.GetHeights(0, 0, xResolution, zResolution);
alphaMapBackup = myTerrain.terrainData.GetAlphamaps(0, 0, alphaMapWidth, alphaMapHeight);
}
}
void OnApplicationQuit()
{
if (Debug.isDebugBuild)
{
myTerrain.terrainData.SetHeights(0, 0, heightMapBackup);
myTerrain.terrainData.SetAlphamaps(0, 0, alphaMapBackup);
}
}
void Update()
{
// This is just for testing with mouse!
// Point mouse to the Terrain. Left mouse button
// raises and right mouse button lowers terrain.
if (TestWithMouse == true)
{
if (Input.GetMouseButtonDown (0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, out hit))
{
// area middle point x and z, area width, area height, smoothing distance, area height adjust
raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, 0.01f);
// area middle point x and z, area size, texture ID from terrain textures
TextureDeformation (hit.point, 10 * 2f, DeformationTextureNum);
}
}
if (Input.GetMouseButtonDown (1))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, out hit))
{
// area middle point x and z, area width, area height, smoothing distance, area height adjust
raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, -0.01f);
// area middle point x and z, area size, texture ID from terrain textures
TextureDeformation (hit.point, 10 * 2f, 0);
}
}
}
}
private void raiselowerTerrainArea(Vector3 point, int lenx, int lenz, int smooth, float incdec)
{
int areax;
int areaz;
smooth += 1;
float smoothing;
int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
lenx += smooth;
lenz += smooth;
terX -= (lenx / 2);
terZ -= (lenz / 2);
if (terX < 0) terX = 0;
if (terX > xResolution) terX = xResolution;
if (terZ < 0) terZ = 0;
if (terZ > zResolution) terZ = zResolution;
float[,] heights = myTerrain.terrainData.GetHeights(terX, terZ, lenx, lenz);
float y = heights[lenx/2,lenz/2];
y += incdec;
for (smoothing=1; smoothing < smooth+1; smoothing++)
{
float multiplier = smoothing / smooth;
for (areax = (int)(smoothing/2); areax < lenx-(smoothing/2); areax++)
{
for (areaz = (int)(smoothing/2); areaz < lenz-(smoothing/2); areaz++)
{
if ((areax > -1) && (areaz > -1) && (areax < xResolution) && (areaz < zResolution))
{
heights [areax, areaz] = Mathf.Clamp((float)y*multiplier,0,1);
}
}
}
}
myTerrain.terrainData.SetHeights (terX, terZ, heights);
}
private void raiselowerTerrainPoint(Vector3 point, float incdec)
{
int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
float y = heights[terX,terZ];
y += incdec;
float[,] height = new float[1,1];
height[0,0] = Mathf.Clamp(y,0,1);
heights[terX,terZ] = Mathf.Clamp(y,0,1);
myTerrain.terrainData.SetHeights(terX, terZ, height);
}
protected void TextureDeformation(Vector3 pos, float craterSizeInMeters,int textureIDnum)
{
Vector3 alphaMapTerrainPos = GetRelativeTerrainPositionFromPos(pos, myTerrain, alphaMapWidth, alphaMapHeight);
int alphaMapCraterWidth = (int)(craterSizeInMeters * (alphaMapWidth / myTerrain.terrainData.size.x));
int alphaMapCraterLength = (int)(craterSizeInMeters * (alphaMapHeight / myTerrain.terrainData.size.z));
int alphaMapStartPosX = (int)(alphaMapTerrainPos.x - (alphaMapCraterWidth / 2));
int alphaMapStartPosZ = (int)(alphaMapTerrainPos.z - (alphaMapCraterLength/2));
float[, ,] alphas = myTerrain.terrainData.GetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphaMapCraterWidth, alphaMapCraterLength);
float circlePosX;
float circlePosY;
float distanceFromCenter;
for (int i = 0; i < alphaMapCraterLength; i++) //width
{
for (int j = 0; j < alphaMapCraterWidth; j++) //height
{
circlePosX = (j - (alphaMapCraterWidth / 2)) / (alphaMapWidth / myTerrain.terrainData.size.x);
circlePosY = (i - (alphaMapCraterLength / 2)) / (alphaMapHeight / myTerrain.terrainData.size.z);
distanceFromCenter = Mathf.Abs(Mathf.Sqrt(circlePosX * circlePosX + circlePosY * circlePosY));
if (distanceFromCenter < (craterSizeInMeters / 2.0f))
{
for (int layerCount = 0; layerCount < numOfAlphaLayers; layerCount++)
{
//could add blending here in the future
if (layerCount == textureIDnum)
{
alphas[i, j, layerCount] = 1;
}
else
{
alphas[i, j, layerCount] = 0;
}
}
}
}
}
myTerrain.terrainData.SetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphas);
}
protected Vector3 GetNormalizedPositionRelativeToTerrain(Vector3 pos, Terrain terrain)
{
Vector3 tempCoord = (pos - terrain.gameObject.transform.position);
Vector3 coord;
coord.x = tempCoord.x / myTerrain.terrainData.size.x;
coord.y = tempCoord.y / myTerrain.terrainData.size.y;
coord.z = tempCoord.z / myTerrain.terrainData.size.z;
return coord;
}
protected Vector3 GetRelativeTerrainPositionFromPos(Vector3 pos,Terrain terrain, int mapWidth, int mapHeight)
{
Vector3 coord = GetNormalizedPositionRelativeToTerrain(pos, terrain);
return new Vector3((coord.x * mapWidth), 0, (coord.z * mapHeight));
}
}
rgds: Jari Kaija jari.kaija@live.com
Hi guys Script worked when attached to terrain object but when I pressed play and while I had a first person controller on I only got it to change from one terrain texture or the other. That is all that happened no height change I wanted to use the script to say attach it to a shovel so the player can change the terrain that way. Sorry for being a noob but as they say if you don't ask you will never know;)
The textures change at the right position (but with "test with mouse"), where the object is placed, but for some reason the terrain elevate on the bottom-right corner, I guess at coords 0,0 and it just elevate. While I would flat it at 0 (on Y) any advice ?
Sorry for necromancing this post. ^_^
Had to do this for it to work for me. Seems code was expecting terrain to be placed at the origin.
private void raiselowerTerrainArea(Vector3 point, int lenx, int lenz, int smooth, float incdec)
{
var pointLocalised = myTerrain.transform.InverseTransformPoint(point);
int areax;
int areaz;
smooth += 1;
float smoothing;
int terX = (int)((pointLocalised.x / myTerrain.terrainData.size.x) * xResolution);
int terZ = (int)((pointLocalised.z / myTerrain.terrainData.size.z) * zResolution);
Answer by Simon Wittber · Feb 08, 2012 at 04:26 AM
Terrain Destruction does all this for you. It's cheap and simple, and fast.
Answer by Victor_cross · Jul 27, 2017 at 01:11 AM
That script above worked great. Could you tell me what part of the script I should be looking at to say deform the terrain less. And how would go attaching the script to a object like a shovel or a pick axe so the players could flatten at a slow rate. Script works great :) But It would be nice to punch a hole in terrain and stitch it internally to make a cave.
To deform less,smooth the terrain, to do this in runtime apply gausian blurr on the heightmap, to apply terrain modification to a shovel use linecasting, then calculate the Terrain Hit into Heightmap coordinates and modify the terrain accordingly.
I made the suggested changes and it worked well. Hey guys is there a way to modify the script in question to utilize the middle mouse scroll button and allow the player to punch a hole in the terrain and create internal mesh e.g to create a basic cave.
Unity engine on its own does not support holes in its terrain system, there are plugins which solved this, i have no idea how tho, only one which is they they copy the terrain area and where the hole needs to be created they stitch it
Your answer
Follow this Question
Related Questions
Make a simple tree 1 Answer
Tree Billboarding Settings 1 Answer
Messed up terrain texture on Android 5 device 0 Answers
need trigger + collide 2 Answers
How to increase Distribution, area spread of trees over the limit ? 0 Answers