- Home /
Instatiating an object. Altering that new object causes prefab to be altered.
I am new to Unity3d and I'm playing around with a proof of concept project.
I'm currently stuck because I use a blank terrain prefab (easily instantiable) that I may want to include having its own scripts.
When my worldcreator object (blank object with script) starts instantiating the terrain pieces in a grid I'm noticing something very odd.
When I instantiate the terrain prefab, alter its height map and move on to instantiating the next piece, the next instantiating piece is carrying over heightmap terrain data from the previous instantiation. When I go to the unity3d editor and drag the prefab to the scene, it as well has the altered heightmap data from the last play.
I'm kind of lost as to why this would happen and I'm wondering if I'm not understanding what exactly instantiating really is or why the prefab in my project would be altered. Before today I thought prefabs were "read only" from a code perspective till they were instantiated and then altered based on in-game variables.
Here is my code.
#pragma strict
public var maxStrength: float = 1;
public var numOfTerrainBySide: int = 4;
public var terrainXsize = 500; //not used
public var terrainZsize = 500;
public var terrainYsize = 50;
public var terrainHeightMapSize = 512;
public var worldPiece: Terrain; //Prefab blank terrain linked from /prefabs/worldpiece
private var wploc : Vector3;
private var wprot : Quaternion;
function Start()
{
//get starting position of blank object "WorldCreator"
wploc = transform.position;
wprot = transform.rotation;
buildWorld();
}
function buildWorld()
{
//for each numOfTerrainBySide on X axis
for(var z = 1; z-1 < numOfTerrainBySide; z ++)
{
//for each numOfTerrainBySide on Z axis per X
for(var x = 1; x-1 < numOfTerrainBySide; x++)
{
var newloc = Vector3(terrainXsize * x - terrainXsize, wploc.y, terrainZsize * z - terrainZsize);
buildWorldPiece(newloc,wprot,x,z);
}
}
}
function buildWorldPiece(wploc,wprot,gridx,gridz)
{
//create terrain from prefab object
var newterrain : Terrain = new Instantiate(worldPiece, wploc, wprot);
//name object based on it's grid location
newterrain.name = "wp_" + gridx + "_" + gridz;
var xRes = newterrain.terrainData.heightmapWidth;
var yRes = newterrain.terrainData.heightmapHeight;
var heights = newterrain.terrainData.GetHeights(0, 0, xRes, yRes);
for (var x: int = 0; x < yRes; x++)
{
for (var z: int = 0; z < xRes; z++)
{
//proof of concept to put border around edges of multiple terrains
if (gridx == 1 && z == 0)
{
heights[x,z] = Random.Range(1.0f, terrainYsize);
}
if (gridz == 1 && x == 0)
{
heights[x,z] = Random.Range(1.0f, terrainYsize);
}
if (gridx == numOfTerrainBySide && z == terrainHeightMapSize)
{
heights[x,z] = Random.Range(1.0f, terrainYsize);
}
if (gridz == numOfTerrainBySide && x == terrainHeightMapSize)
{
heights[x,z] = Random.Range(1.0f, terrainYsize);
}
}
}
newterrain.terrainData.SetHeights(0, 0, heights);
}
Answer by Baste · Jan 13, 2015 at 11:59 AM
Instantiate is just a normal method on UnityEngine.Object. The new keyword in front of Instantiate makes no sense, and I'm surprised it even compiles.
I'm not quite sure what's happening, but I have an idea. Instantiate makes a copy of prefabs, but it doesn't make a deep copy. Which means that if the prefab references some other object, the reference will be copied, not the referenced object.
So I'd guess that your terrain is being copied, but the terrainData object being referenced is separate, so newterrain.terrainData references the same data as the prefab.
Try to do this in Instantiate:
var newterrain : Terrain = new Instantiate(worldPiece, wploc, wprot);
print("terrain is the same object: " + (newterrain == worldPiece));
print("terrainData is the same object: " + (newterrain.terrainData == worldPiece.terrainData));
I'm 99% sure you'll get true on the first one, and false on the second one.
To fix this, you'll have to create a copy of the terrain data, and assign that to the instantiated Terrain copy. That'll take some work on the Unity internals - post a reply if you can't figure it out on your own.
I was 99% sure your expected results would come out but I got the following.
terrain is the same object: False
terrainData is the same object: True
I think I'm getting the above results because terrain == worldpiece is doing an object comparison (are they the same object) when the newterrain.terrainData == worldpiece.terrainData is doing a terrainData comparison and is ignore object information. Since terrainData is being replicated between the objects it is the same between all pieces and the worldpiece.
The "new" in front of instantiate was a last $$anonymous$$ute compile to see if it would force a new object with new terrain data. I had it using just instantiate without the new.
The other solution I had was to apply new terraindata() to the object after its being instantiated (thats why I had terrainsize variables at the top of the script).
However, I had an issue where the terrain object was not applying the size.
newterrain.terrainData.size = Vector3(terrainXsize, terrainYsize, terrainZsize);
The new terrains however had a size of 8000x500x50. Blew my $$anonymous$$d why it wasn't applying an X size to the newterrain object when Y and Z were being applied.
EDIT: The below code keeps my terrain prefab intact and preserves its terrain data. Unfortunately size is not changing to 500x50x500 and it is creating each world piece 8000x50x8000.
//create terrain from prefab object
var newterrain : Terrain = Instantiate(worldPiece, wploc, wprot);
//create new terrainData to resolve problem with terrain data being copied to prefab
newterrain.terrainData = new TerrainData();
newterrain.terrainData.size = new Vector3(terrainXsize, terrainYsize, terrainZsize);
newterrain.terrainData.heightmapResolution = terrainHeight$$anonymous$$apResolution;
Edit: I figured out my issues with applying the size. I had to apply the heightmap resolution first before applying size.
The same thing happens if you use a terrain in 2 scenes and modify one of them. They both change, because Unity purposely doesn't make copies of terrain data.
Terrain is just funny that way. Lots of terrain-only optimizations and special-cases (for any game engine.) I'm a little surprised Instantiate works on terrain at all.
Answer by JMurray · Jan 16, 2015 at 05:40 PM
For the sake of the community I'm including that I also dropped the initiate on terrain and ended up going the route of creating a new object.
//create terrain out of thin air var newTerrainObject : GameObject = new GameObject(); newTerrainObject.transform.parent = transform; var newTerrain : Terrain = new newTerrainObject.gameObject.AddComponent("Terrain");
//move new terrain to location
newTerrainObject.transform.position= newloc;
//layer tag object
newTerrainObject.layer = terrainLayerMask;
//create new terrainData to resolve problem with terrain data being copied to prefab
newTerrain.terrainData = new TerrainData();
//set terraindata to minimum requirements. heightmapResolution must be set before size.
newTerrain.terrainData.heightmapResolution = terrainHeightMapResolution;
newTerrain.terrainData.size = new Vector3(terrainXsize, terrainYsize, terrainZsize);
This ended up resolving a bunch of issues in the end.
Your answer
Follow this Question
Related Questions
Cloned Object does not run Scripts 2 Answers
Instantiated clones arent behaving the same as the prefab 1 Answer
Deleting a GameObject 2 Answers
Cast Source Instantiating Error 1 Answer