- Home /
How Do I Interact With Terrain Trees?
How do I interact with Terrain Trees?
A terrain tree: painted via the Editor / Place Trees brush, or via use of the Mass Place Trees function, or via modification of terrainData from script. A terrain tree is not present in the project Hierarchy.
A placed tree: is visible in the Hierarchy and typically positioned manually or via scripts.
There is a common misconception that a developer, especially one working on a Sandbox, Build, RTS, Survival or Crafting system game, can build a functionality set on a placed tree (harvest, gather wood, lumberjack, chop, etc.) that might include scripts, triggers or components, turn that into a prefab then paint that tree onto the terrain and all the custom behaviours will follow. They do not. Nothing of the sort works.
This is a system shock to many developers (myself included) when they realize their whole concept needs to be reconsidered.
Can you be more specific as for why this functionality won't work? Isn't the tree prefab cloned as-is and behaving as it would if it were placed directly in the game world as a gameobject?
Is this proven not to work too?
I'm quite interested in your thinking here since I might have to deal with this kind of functionality in my project.
Sorry to reply so late, but... 1) The tree prefab does not behave as-is when turned into a Terrain Tree. 2) Yes, proven. A simple test is to create a GObj Tree, add a script that prints 'you clicked me' on click, turn that into a prefab and paint it on your terrain as a Terrain Tree. You won't see anything when you click the Terrain tree - that's the crux of the issue really, scripts and such on GObj trees do not work when painted on as Terrain Trees.
Answer by getyour411 · Feb 26, 2014 at 08:57 AM
A sample solution is provided via two scripts.
Script #1 is attached to your Player. Script #2 is attached to a GameManager singleton-like object that is tagged as "GameManager", adapt to your needs.
Script #1 - attached to Player, will look for GameManager tagged object so change that line if you do something different.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
// Purpose: demonstrate script interface to interact with terrain trees
// Steps: Attach this to main(parent/top) Player gameObject, or adjust myTransform to your hierarchy,
// define Inspector values for harvestTreeDistance, respawnTimer
// Setup a prefab tree with CAPSULE collider, how to at bottom of
// http://docs.unity3d.com/Documentation/Components/terrain-Trees.html
// paint terrain tree
// Assign the non-collider prefab version of the tree to felledTree
// press Play, left click on terrain tree
// Harvested terrain tree info is passed to a manager object QM_ResourceManager for respawn management,
// you'll need that too or you could comment out any functionality related to manager
// Note: this is not a demo of modifying terrainData permanently - there's enough risk involved with that
// such that you shouldn't try it unless you know what you are doing.
public class QM_HarvestTerrainTree : MonoBehaviour {
// Player, Range
public int harvestTreeDistance; // Set [Inspector] min. distance from Player to Tree for your scale?
public bool rotatePlayer = true; // Should we rotate the player to face the Tree?
private Transform myTransform; // Player transform for cache
// Terrains, Hit
private Terrain terrain; // Derived from hit...GetComponent<Terrain>
private RaycastHit hit; // For hit. methods
private string lastTerrain; // To avoid reassignment/GetComponent on every Terrain click
// Tree, GameManager
public GameObject felledTree; // Prefab to spawn at terrain tree loc for TIIIIIIMBER!
private QM_ResourceManager rMgr; // Resource manager script
public float respawnTimer; // Duration of terrain tree respawn timer
void Start () {
if (harvestTreeDistance <= 0) {
Debug.Log ("harvestTreeDistance unset in Inspector, using value: 6");
harvestTreeDistance = 6;
}
if (respawnTimer <= 0) {
Debug.Log ("respawnTimer unset in Inspector, using quick test value: 15");
respawnTimer = 15;
}
myTransform = transform;
lastTerrain = null;
rMgr = GameObject.FindGameObjectWithTag ("GameManager").GetComponent<QM_ResourceManager> ();
}
void Update () {
if (Input.GetMouseButtonUp (0)) {
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, out hit, 30f)) {
// Did we click a Terrain?
if(hit.collider.gameObject.GetComponent<Terrain>() == null)
return;
// Did we click on the same Terrain as last time (or very first time?)
if(lastTerrain == null || lastTerrain != hit.collider.name) {
terrain = hit.collider.gameObject.GetComponent<Terrain>();
lastTerrain = terrain.name;
}
// Was it the terrain or a terrain tree, based on SampleHeight()
float groundHeight = terrain.SampleHeight(hit.point);
if(hit.point.y - .2f > groundHeight) {
// It's a terrain tree, check Proximity and Harvest
if(CheckProximity())
HarvestWood();
}
}
}
}
private bool CheckProximity() {
bool inRange = true;
float clickDist = Vector3.Distance (myTransform.position, hit.point);
if (clickDist > harvestTreeDistance) {
Debug.Log ("Out of Range");
inRange = false;
}
return inRange;
}
private bool CheckRecentUsage(string _terrainName, int _treeINDEX) {
bool beenUsed = false;
for (int cnt=0; cnt < rMgr.managedTrees.Count; cnt++) {
if (rMgr.managedTrees[cnt].terrainName == _terrainName && rMgr.managedTrees [cnt].treeINDEX == _treeINDEX) {
Debug.Log ("Tree has been used recently");
beenUsed = true;
}
}
return beenUsed;
}
private void HarvestWood() {
int treeIDX = -1;
int treeCount = terrain.terrainData.treeInstances.Length;
float treeDist = harvestTreeDistance;
Vector3 treePos = new Vector3 (0, 0, 0);
// Notice we are looping through every terrain tree,
// which is a caution against a 15,000 tree terrain
for (int cnt=0; cnt < treeCount; cnt++) {
Vector3 thisTreePos = Vector3.Scale (terrain.terrainData.treeInstances [cnt].position, terrain.terrainData.size) + terrain.transform.position;
float thisTreeDist = Vector3.Distance (thisTreePos, hit.point);
if (thisTreeDist < treeDist) {
treeIDX = cnt;
treeDist = thisTreeDist;
treePos = thisTreePos;
}
}
if (treeIDX == -1) {
Debug.Log ("Out of Range");
return;
}
if(!CheckRecentUsage(terrain.name, treeIDX)) {
// Success - all tests passed
// Place a cube to show the tree, the ResourceManager will remove it after a time
// Obviously tweak to your liking, just a visual aid to show it worked
GameObject marker = GameObject.CreatePrimitive (PrimitiveType.Cube);
marker.transform.position = treePos;
// Example of spawning a placed tree at this location, just for demo purposes
// it will slide through terrain and disappear in 4 seconds
GameObject fellTree = Instantiate(felledTree,treePos,Quaternion.identity) as GameObject;
fellTree.gameObject.AddComponent<Rigidbody>();
Destroy(fellTree,4);
// Add this terrain tree and cube to our Resource Manager for demo purposes
rMgr.AddTerrainTree(terrain.name, treeIDX, Time.time+respawnTimer, marker.transform);
if (rotatePlayer) {
Vector3 lookRot = new Vector3 (hit.point.x, myTransform.position.y, hit.point.z);
myTransform.LookAt (lookRot);
}
// There are too many guesses to be made here about your game mechanics to code the rest
// For example, you might..
// Start Animation
// Play Sound Clip
// Give player an Inventory item
// Bump lumberjacking skill
// Random.Range[] spawn a Forest Protector Spirit of Woe
// etc.
}
}
}
Script #2 is attached to a GameManager singleton-like object that is tagged as "GameManager", adapt to your needs.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
// Attach this to a Singleton-like GameObject of which there is only and will ever only be one,
// ala classic GameManager object.
public class QM_ResourceManager : MonoBehaviour {
// Define List component
public class QM_Tree {
public string terrainName { get; set; }
public int treeINDEX { get; set; }
public float respawnTime { get; set; }
public Transform marker { get; set; }
// Constructor
public QM_Tree(string _terrainName, int _treeINDEX, float _respawnTime, Transform _marker) {
terrainName = _terrainName;
treeINDEX = _treeINDEX;
respawnTime = _respawnTime;
marker = _marker;
}
}
// Tree Harvest script access
public List<QM_Tree> managedTrees = new List<QM_Tree>();
void Start() {
// Scan for tree to "respawn" (remove cube, make available again) every 15 seconds
// Adjust to your needs using a fast spawn here for demo
InvokeRepeating ("RespawnTree", 15, 15);
}
private void RespawnTree() {
if (managedTrees.Count == 0)
return;
// Removing the demo cube and allowing tree to be used again
for (int cnt=0; cnt < managedTrees.Count; cnt++) {
if(managedTrees[cnt].respawnTime < Time.time) {
Destroy(managedTrees[cnt].marker.gameObject);
managedTrees.RemoveAt(cnt);
return;
}
}
}
public void AddTerrainTree(string _terrainName, int _treeIDX, float _respawnTime, Transform _marker) {
managedTrees.Add (new QM_Tree(_terrainName, _treeIDX, _respawnTime, _marker));
}
}
Additional methods, posts, ideas & approaches
http://answers.unity3d.com/questions/420154/modify-existing-mass-place-trees-function.html
http://forum.unity3d.com/threads/110354-Finally-Removing-trees-AND-the-colliders
http://forum.unity3d.com/threads/28855-Destroyable-Trees
A collider-less solution: http://answers.unity3d.com/questions/685405/mass-place-trees-tagging.html
I have a freeze when i click with this script, can you help me ?
I have put Q$$anonymous$$_HarvestTerrainTree.cs in a empty game object, and Q$$anonymous$$_HarvestTerrainTree to my firstpersoncontroller.
I think it's because their is lot of tree in my maps, but if you have another idea ?
I have a problem. The tree i've harvested doesn't disappear, the fell tree just spawnes and get pushed to the side because of the original tree's collider.
For Q$$anonymous$$_HarvestTerrainTree,
On line 151 it states: Vector3 thisTreePos = Vector3.Scale(terrain.terrainData.treeInstances[cnt].position, terrain.terrainData.size) + terrain.transform.position;
Change this to: Vector3 thisTreePos = Vector3.Scale(terrain.terrainData.GetTreeInstance(cnt).position, terrain.terrainData.size) + terrain.transform.position;
using the array "treeInstances" is costly to performance and causes a pause mid game. using "GetTreeInstance()" is seemless. I used this with 10,000 trees and there was no issue!
Answer by JayCrossler · Sep 08, 2017 at 03:00 AM
Is there a way to remove that tree (treeidx) from rendering? Or, because it's static, is it always going to be in the scene?