Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Chopping down terrain's trees

Discussion in 'Scripting' started by rkaetano, Jun 24, 2013.

  1. rkaetano

    rkaetano

    Joined:
    Aug 27, 2012
    Posts:
    61
    Hi Guys, I've made a script to allow my "minions" chopping down some trees.
    Overall, the script is working nice.
    But since I need to re-assing the tree's array everytime I remove one tree, sometimes I have some boring problems, like a delay between the chopping action, and the tree falling down. And sometimes, the function which returns the nearest tree, pick a "already chopped" tree.

    I'm posting my script here, to see if someone has some better approach. Maybe, there's things that I could do better. :p

    Oh, and I would like to make the tree shakes, when getting hits, can someone give me any idea ? Thanks.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class EnvironmentManager : MonoBehaviour {  
    7.     public GameObject      woodPrefab;
    8.    
    9.     private Transform      _thisTransform;
    10.     private List<EnviTree> _terrainTrees;      
    11.     private Terrain        _curTerrain;
    12.     private float          _acumCleanTrees;
    13. //  private GameManager    _gameManager;
    14.     private TreeInstance[] _curTrees;  
    15.     private bool           _treeToClean;
    16.  
    17.     void Awake ()
    18.     {
    19.         _thisTransform = transform;
    20.     }
    21.    
    22.     void Start ()
    23.     {
    24. //      _gameManager = GameObject.Find ("_GameManager").GetComponent<GameManager> ();
    25.         _curTerrain = Terrain.activeTerrain;
    26.         if (woodPrefab == null) {
    27.             Debug.LogError ("Wood prefab not found!");
    28.         }
    29.         CreateTreeList ();
    30.        
    31.         foreach (string resourceName in System.Enum.GetNames( typeof( ResourceTypes))) {
    32.             GameObject resourceParent = new GameObject (resourceName);
    33.             resourceParent.transform.parent = _thisTransform;
    34.         }
    35.        
    36.     }
    37.    
    38.     void Update ()
    39.     {
    40.         if (_treeToClean) {
    41.             CleanEmptyTrees ();
    42.             _treeToClean = false;
    43.         }
    44.     }
    45.    
    46.     void CreateTreeList ()
    47.     {
    48.         List<TreeInstance> worldTrees = new List<TreeInstance> (_curTerrain.terrainData.treeInstances);
    49.         _curTrees = _curTerrain.terrainData.treeInstances;
    50.         _terrainTrees = new List<EnviTree> ();
    51.         int idTree = 0;
    52.         // TODO Give some difference between tree types, and wood amount
    53.         foreach (TreeInstance tree in worldTrees) {
    54.             _terrainTrees.Add (new EnviTree (idTree++, tree, Vector3.Scale (tree.position, _curTerrain.terrainData.size) + _curTerrain.transform.position, 30));           
    55.         }
    56.     }
    57.    
    58.     void CleanEmptyTrees ()
    59.     {
    60.         List<EnviTree> treesAlive = _terrainTrees.FindAll (i => i.hitPoints > 0);
    61.         if (treesAlive.Count != _terrainTrees.Count) {         
    62.             List<TreeInstance> newTreeList = new List<TreeInstance> ();
    63.             for (int itree = 0; itree < treesAlive.Count; itree++) {
    64.                 newTreeList.Add (treesAlive [itree].treeInstance);
    65.             }
    66.             _terrainTrees = treesAlive;
    67.             _curTerrain.terrainData.treeInstances = newTreeList.ToArray ();
    68.             float[,] heights = _curTerrain.terrainData.GetHeights (0, 0, 0, 0);
    69.             _curTerrain.terrainData.SetHeights (0, 0, heights);
    70.         }
    71.     }
    72.    
    73.     public int ReturnClosestTree (Vector3 relativeTo)
    74.     {
    75.         float closestDistance = Mathf.Infinity;
    76.         int closestTreeIndex = -1;
    77.  
    78.         for (int iTree = 0; iTree < _terrainTrees.Count; iTree++) {
    79.             if (_terrainTrees [iTree].hitPoints > 0) {
    80.                 float distanceToTree = Vector3.Distance (relativeTo, _terrainTrees [iTree].worldPos);              
    81.                 if (distanceToTree < closestDistance) {
    82.                     closestDistance = distanceToTree;
    83.                     closestTreeIndex = iTree;
    84.                 }
    85.             }
    86.         }
    87.         int returnValue = -1;
    88.         if (closestTreeIndex >= 0) {
    89.             returnValue = _terrainTrees [closestTreeIndex].treeId;
    90.         }
    91.         return returnValue;    
    92.     }
    93.    
    94.     public bool LumberHit (int ptreeId, int pamountOfHits)
    95.     {
    96.         bool treeChopped = false;
    97.         int treeIndex = _terrainTrees.FindIndex (i => i.treeId == ptreeId);
    98.         if (treeIndex >= 0) {          
    99.             treeChopped = _terrainTrees [treeIndex].LumberHit (pamountOfHits);
    100.             if (_terrainTrees [treeIndex].hitPoints <= 0) {
    101.                 int amountOfWood = _terrainTrees [treeIndex].woodAmount;
    102.                 Vector3 spawnPosition = new Vector3 (_terrainTrees [treeIndex].worldPos.x, _terrainTrees [treeIndex].worldPos.y + 5, _terrainTrees [treeIndex].worldPos.z);
    103.                 for (int iwood = 1; iwood <= amountOfWood; iwood++) {
    104.                     GameObject board = GameObject.Instantiate (woodPrefab, spawnPosition, Quaternion.identity) as GameObject;
    105.                     board.name = ResourceTypes.Wood.ToString ();
    106.                     Transform parent = _thisTransform.FindChild (ResourceTypes.Wood.ToString ());
    107.                     board.transform.parent = parent;       
    108.                 }
    109.                 _treeToClean = true;
    110.             }
    111.         } else {
    112.             Debug.Log ("LumberHit : Tree Not Found : " + ptreeId.ToString ());
    113.         }
    114.         return(treeChopped);
    115.     }
    116.    
    117.     public bool AnyResource (ResourceTypes presourceToFind)
    118.     {
    119.         return (_thisTransform.FindChild (presourceToFind.ToString ()).childCount > 0);
    120.     }
    121.    
    122.     public bool TreeExists (int ptreeId)
    123.     {
    124.         return(_terrainTrees.FindIndex (i => i.treeId == ptreeId) >= 0);
    125.     }
    126.    
    127.     public Vector3 TreePosition (int ptreeId)
    128.     {
    129.         Vector3 returnPos = Vector3.zero;
    130.         int treeIndex = _terrainTrees.FindIndex (i => i.treeId == ptreeId);
    131.         if (treeIndex >= 0) {
    132.             returnPos = _terrainTrees [treeIndex].worldPos;        
    133.         } else {
    134.             Debug.Log ("TreePosition : Tree Not Found : " + ptreeId.ToString ());
    135.         }
    136.         return(returnPos);
    137.        
    138.     }
    139.    
    140.     public int WoodAmount (int ptreeId)
    141.     {
    142.         int returnWoodAmount = 0;
    143.         int treeIndex = _terrainTrees.FindIndex (i => i.treeId == ptreeId);
    144.         if (treeIndex >= 0) {
    145.             returnWoodAmount = _terrainTrees [treeIndex].woodAmount;           
    146.         } else {
    147.             Debug.Log ("WoodAmount : Tree Not Found : " + ptreeId.ToString ());
    148.         }
    149.         return(returnWoodAmount);      
    150.     }
    151.        
    152.     void OnDisable ()
    153.     {
    154.         _curTerrain.terrainData.treeInstances = _curTrees;
    155.         float[,] heights = _curTerrain.terrainData.GetHeights (0, 0, 0, 0);
    156.         _curTerrain.terrainData.SetHeights (0, 0, heights);    
    157.     }
    158.    
    159. }
    160.  
    161. [System.Serializable]
    162. public class EnviTree {
    163.     public int          treeId;
    164.     public TreeInstance treeInstance;
    165.     public Vector3      worldPos;
    166.     public int          hitPoints;
    167.     public int          woodAmount;
    168.    
    169.     public EnviTree (int ptreeId, TreeInstance ptreeInstance, Vector3 pworldPos, int phitPoints)
    170.     {
    171.         this.treeId = ptreeId;
    172.         this.treeInstance = ptreeInstance;
    173.         this.worldPos = pworldPos;
    174.         this.hitPoints = phitPoints;
    175.         this.woodAmount = 4;
    176.     }
    177.    
    178.     public bool LumberHit (int amountOfHit)
    179.     {      
    180.         bool couldLumber = false;
    181.         if (this.hitPoints > 0) {
    182.             this.hitPoints -= amountOfHit;
    183.             this.hitPoints = Mathf.Max (this.hitPoints, 0);
    184.             couldLumber = true;
    185.         }
    186.         return (couldLumber);
    187.     }
    188. }
    189.  
     
  2. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Just eyeballing...the script looks fine. If you have a lot of trees, your problem is the time it takes Unity to remove the tree collider and update the terrain.

    For your ReturnClosestTree function...you basically finding the closest tree -center- to the relativeTo position, and that may not be the tree you want. For example, imagine if you have a tree 5 meters wide. Right beside it is a small tree .5 meters wide.

    OOOOOx o
    OOOOO
    OOOOO
    OOOOO

    Imagine the big block of O's represents the large tree, and the small 'o' represents the small one. The small 'x' is where you chopped on the big tree trunk.

    If you chop on the large tree, but your hitpoint is near the small tree...the center of the small tree is actually closer to the hitpoint than the center of the large tree. Thus, your search function will think you hit the small tree.

    Honestly...I would strongly suggest ditching Unity's trees for a situation like this, and just have tree gameobjects with their own colliders.

    You would know exactly which tree you hit by getting the collider gameobject. You could make it shake if it wasn't chopped completely. And then you could make it fall. And you wouldn't have to create a separate tree instance to make it fall.

    This is much easier than working with the built in system, where you have to find the correct tree (inaccurate), remove it from the terrainData.TreeInstances array. Then you have to update the terrain, which is slow. Then you have to create an instance of that tree, make it fall. Forget about shaking.


    Believe it or not, using instances of trees instead of the terrain tree system can be plenty fast.

    There is a big thread in this forum, about Unity's trees, which I had started a bit ago. It goes into details of using tree instances, the pros and cons, and also takes about using Graphics.DrawMesh to draw large amounts of trees that aren't interactive. That isn't as useful to you, but it's something you might find useful for distant trees. DrawMesh doesn't give it a collider, so nearby trees you want to interact with would be a tree gameobject with its collider. But more distant trees could be handled with DrawMesh.


    It looks like you are removing chopped trees correctly in CleanEmptyTrees(), so there may be a bug somewhere else. Are you sure you're reducing hitpoints properly?
     
    Last edited: Jun 24, 2013
  3. rkaetano

    rkaetano

    Joined:
    Aug 27, 2012
    Posts:
    61
    Hey @jc_lvngstn, thank you for your answer.

    About the ClosestTree(), I'm using this to return the position to my minion. So, he can move to the tree, and when are close enough, start to use the LumberHit(), but I understand what you mentioned, cause sometimes, I've this problems, like the minion having a tree just in front of him, and he pick one tree almost at the other side of map.

    About the reducing hitpoints, i can see that the correctly tree is chopped, they just take sometime to do this... :p

    Tonight, I will try with instances of trees. I just need to learn how to paint then... I will check the other topic further... ;)

    Thank you guy!
     
  4. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I believe somewhat early on in the topic there is a small script that shows how to create tree instances from the trees you have on your terrain. Based on the script you posted above, I think this shouldn't be difficult for you at all.
    One thing about creating tree instances...it is slower on startup. Just something to consider, maybe not a big deal in your situation. Also check out some pool manager, it can help a lot of creating and destroying gameobjects for your memory and speed.
     
  5. rkaetano

    rkaetano

    Joined:
    Aug 27, 2012
    Posts:
    61
    When I get home I will check this out, and I will come to post my results... ;)


    Thank you a lot.. ;)
     
  6. rkaetano

    rkaetano

    Joined:
    Aug 27, 2012
    Posts:
    61
    I didn't forget about it.
    I just didn't had time yesterday, to work with it.
    Today I will be doing these modifications.
     
  7. rkaetano

    rkaetano

    Joined:
    Aug 27, 2012
    Posts:
    61

    Hey @jc_lvngstn I've created a function to create the mesh trees. Look very nice, indeed. And no perfomance impacts at all. Since I'm using a top down camera, I don't need that bilboard thing, so... :p

    I'm just having problems with the color variation, like terrain's tree do.
    Could you help me with this ?

    Code (csharp):
    1.  
    2.     void ReplaceTerrainTrees() {
    3.         foreach( EnviTree treeEnvi in _terrainTrees) {
    4.             GameObject obj = Instantiate( _curTerrain.terrainData.treePrototypes[ treeEnvi.treeInstance.prototypeIndex ].prefab, treeEnvi.worldPos, Quaternion.identity ) as GameObject;
    5.             obj.renderer.materials[0].color -= treeEnvi.treeInstance.color;
    6.             obj.renderer.materials[1].color -= treeEnvi.treeInstance.color;
    7.         }      
    8.         _curTerrain.terrainData.treeInstances = new List<TreeInstance>().ToArray();
    9.         float[,] heights = _curTerrain.terrainData.GetHeights (0, 0, 0, 0);
    10.         _curTerrain.terrainData.SetHeights (0, 0, heights);    
    11.     }
    12.  
    Look at difference :
    Terrain
    $Captura de Tela 2013-06-25 às 23.10.56.png

    Mesh
    $Captura de Tela 2013-06-25 às 23.11.46.png
     
    Last edited: Jun 26, 2013
  8. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Is it because you are subtracting (reversing) the tree shade?

    obj.renderer.materials[0].color -= treeEnvi.treeInstance.color;
    obj.renderer.materials[1].color -= treeEnvi.treeInstance.color;

    Looks to me that they are reversed in the second screenshot. The trees in the first that are light are dark in the second shot, and the trees that are lighter are darker in the second.
     
  9. rkaetano

    rkaetano

    Joined:
    Aug 27, 2012
    Posts:
    61
    Hmmm.. don't think so... :p
    I've already tried with (+=), (-=) and of course (=).
    I think that this random color is not made with the color... :p
    If I use the (=) they keep the main color of the prefab.
    I think that should be another variable to get this kind of variation. :p
    But it's ok. I will not lost time with this, right now. It's not my main concern...

    Thank you anyway... ;)