I am trying to make a script that will automatically add grass and tress to my terrain. I am finding little that is helpful on google. Any advice would would great thanks!
I found this
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GrassCreator : ScriptableWizard {
public Terrain terrain;
public int detailIndexToMassPlace;
public int[] splatTextureIndicesToAffect;
public int detailCountPerDetailPixel = 0;
[MenuItem ("Terrain/Mass Grass Placement")]
static void createWizard() {
ScriptableWizard.DisplayWizard("Select terrain to put grass on", typeof (GrassCreator), "Place Grass on Terrain");
}
void OnWizardCreate () {
if (!terrain) {
Debug.Log("You have not selected a terrain object");
return;
}
if (detailIndexToMassPlace >= terrain.terrainData.detailPrototypes.Length) {
Debug.Log("You have chosen a detail index which is higher than the number of detail prototypes in your detail libary. Indices starts at 0");
return;
}
if (splatTextureIndicesToAffect.Length > terrain.terrainData.splatPrototypes.Length) {
Debug.Log("You have selected more splat textures to paint on, than there are in your libary.");
return;
}
for (int i = 0; i < splatTextureIndicesToAffect.Length; i ++) {
if (splatTextureIndicesToAffect[i] >= terrain.terrainData.splatPrototypes.Length) {
Debug.Log("You have chosen a splat texture index which is higher than the number of splat prototypes in your splat libary. Indices starts at 0");
return;
}
}
if (detailCountPerDetailPixel > 16) {
Debug.Log("You have selected a non supported amount of details per detail pixel. Range is 0 to 16");
return;
}
int alphamapWidth = terrain.terrainData.alphamapWidth;
int alphamapHeight = terrain.terrainData.alphamapHeight;
int detailWidth = terrain.terrainData.detailResolution;
int detailHeight = detailWidth;
float resolutionDiffFactor = (float)alphamapWidth/detailWidth;
float[,,] splatmap = terrain.terrainData.GetAlphamaps(0,0,alphamapWidth,alphamapHeight);
int[,] newDetailLayer = new int[detailWidth,detailHeight];
//loop through splatTextures
for (int i = 0; i < splatTextureIndicesToAffect.Length; i++) {
//find where the texture is present
for (int j = 0; j < detailWidth; j++) {
for (int k = 0; k < detailHeight; k++) {
float alphaValue = splatmap[(int)(resolutionDiffFactor*j),(int)(resolutionDiffFactor*k),splatTextureIndicesToAffect[i]];
newDetailLayer[j,k] = (int)Mathf.Round(alphaValue * ((float)detailCountPerDetailPixel)) + newDetailLayer[j,k];
}
}
}
terrain.terrainData.SetDetailLayer(0,0,detailIndexToMassPlace,newDetailLayer);
}
void OnWizardUpdate ()
{
helpString = "Ready";
}
}
And it works great for grass! I am just tweaking it to be more what I need. While this will help I still don’t know how to make trees in this way.
Does anyone know?
This is what I managed to get so far
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class TreeCreator : ScriptableWizard {
public Terrain terrain;
public int treeIndexToMassPlace;
public int[] splatTextureIndicesToAffect;
public int treeDensity = 0;
private int treeCount = 0;
[MenuItem ("Terrain/Mass Tree Placement")]
static void createWizard() {
ScriptableWizard.DisplayWizard("Select terrain to put trees on", typeof (TreeCreator), "Place trees on Terrain");
}
void OnWizardCreate () {
if (!terrain) {
Debug.Log("You have not selected a terrain object");
return;
}
if (treeIndexToMassPlace >= terrain.terrainData.treePrototypes.Length) {
Debug.Log("You have chosen a detail index which is higher than the number of detail prototypes in your detail libary. Indices starts at 0");
return;
}
if (splatTextureIndicesToAffect.Length > terrain.terrainData.splatPrototypes.Length) {
Debug.Log("You have selected more splat textures to paint on, than there are in your libary.");
return;
}
for (int i = 0; i < splatTextureIndicesToAffect.Length; i ++) {
if (splatTextureIndicesToAffect[i] >= terrain.terrainData.splatPrototypes.Length) {
Debug.Log("You have chosen a splat texture index which is higher than the number of splat prototypes in your splat libary. Indices starts at 0");
return;
}
}
if (treeDensity > 16) {
Debug.Log("You have selected a non supported amount of details per detail pixel. Range is 0 to 16");
return;
}
int alphamapWidth = terrain.terrainData.alphamapWidth;
int alphamapHeight = terrain.terrainData.alphamapHeight;
int detailWidth = terrain.terrainData.detailResolution;
int detailHeight = detailWidth;
int treeDamper = 50000;
float resolutionDiffFactor = (float)alphamapWidth/detailWidth;
float[,,] splatmap = terrain.terrainData.GetAlphamaps(0,0,alphamapWidth,alphamapHeight);
int[,] newDetailLayer = new int[detailWidth,detailHeight];
detailHeight = 25;
//loop through splatTextures
for (int i = 0; i < splatTextureIndicesToAffect.Length; i++)
{
//find where the texture is present
for (int j = 0; j < detailWidth; j++)
{
for (int k = 0; k < detailHeight; k++)
{
float alphaValue = splatmap[(int)(resolutionDiffFactor*j),(int)(resolutionDiffFactor*k),splatTextureIndicesToAffect[i]];
alphaValue = 255;
// newDetailLayer[j,k] = (int)Mathf.Round(alphaValue * ((float)treeDensity)) + newDetailLayer[j,k];
for(int x = 0; x < detailWidth; x++)
{
for(int z = 0; z < detailHeight; z++)
{
//Debug.Log(newDetailLayer.ToString());
if(alphaValue > 1)
{
if(treeCount == 0)
{
TreeInstance t = new TreeInstance();
t.color = Color.white;
t.heightScale = 1;
t.widthScale = 1;
t.lightmapColor = Color.white;
t.prototypeIndex = treeIndexToMassPlace;
t.position = new Vector3(x, 0, z);
terrain.AddTreeInstance(t);
treeCount++;
}
else
{
treeCount++;
}
}
if(treeCount> treeDamper)
{
treeCount = 0;
}
}
}
}
}
}
}
void OnWizardUpdate ()
{
helpString = "Ready";
}
}
It takes a REALLY long time to run and it only seems to make 4 trees, one at each corner. Can anyone tell me why, how to make this better, or anything else that can be improved?
I think TreeInstance.position is a normalized Vector3 and you’re trying to set it as a world position. The x , z values get clamped between 0 and 1 so only the corners get trees although some corners get A LOT of trees.
That’s me setting the position.
t.position = new Vector3(x, 0, z);
That’s the position for tree instance
public Vector3 position
{
get
{
return this.m_Position;
}
set
{
this.m_Position = value;
}
}
I don’t see where/how it can be normalizing, although that does sound right seeings its only in the corners.
This might help.
t.position = new Vector3(x / terrain.TerrainData.size.x, 0, z / terrain.TerrainData.size.z);
Sweet! its working, just one more thing to iron out and its more or less done.
Just got to make it not take so long and not check EVERY vert to speed it up.
Cheers for all your help.
Going through the terrain object interface while checking for trees, adding new ones as you go might be a performance issue but don’t quote me on this.
Right now you’re doing multiple read and writes to this data structure which you could cache easily enough with a pair of built in arrays that you merge at the end. I assume you’ll be able to calculate some amount of trees that you’ll be adding if you knew how many trees there were to get a total for a single buffer allocation. terrain.TerrainData.treeInstances.Length will provide you with the current tree count I think. Once you merge your new trees with the existing trees in your cached array you should be able to set terrain.TerrainData.treeInstances to your tree cache array in one shot.