Jump to content

Interacting with unity terrain trees


magic banana

Recommended Posts

Hey folks, So I thought I would touch base with you all, as I know some people would like to figure out this problem.
As many like myself, prefer to use the unity terrain system for trees. We face a problem with being able to interact with them like you would with a gameObject. The reason for this is because when they are used in the terrain system, they are technically not game objects. So, we are faced with a choice to either lose out on billboards and spawn them in as gameobjects or not interact with them at all.
So for all you canopy guys out there that would like to know how to interact with terrain trees here we go.

I will post an attachment with the full code. This is part 1: so keep the code handy for part 2

Obviously, you need to have a terrain set up with trees painted on.

Ok so firstly to your player you are going to want to create a script this can be named anything you want. But for mine im going to call it {Interaction}.

First, we are going to want to declare some variables for raycasting.

public class Interaction : MonoBehaviour
{

    //Raycast variables
    public string objectTag; // what we have hit {checks what objects tag it is}
    public int hitDistance; // distance the raycast checks
    RaycastHit hit;

Then we will add some variables for the terrain.

 //Terrain Variables
    public Terrain terrain; // refference to the terrain we have hit
    private TerrainData terrainData; // the terrain data we are trying to access
    public int surfaceIndex = 0; // this is the index of the terrains textures
    public SplatPrototype[] splatProto; // an array of the terrain textures

Then we will add some variables for the trees.
   

public float coolDown = 4;  // a cooldown to avoid spamming the chop function
    public float rayTimer = 4;   // as above
    public int ChopPower;     // this is used to determine how much power gets turned into damage
    public GameObject ChoppingWoodChips;  // if you want to instantiate wood chips to spawn when trees are hit
    public GameObject[] FallingTreePrefab;    // This array is for the exact gameobject of the trees **Note trees must be the same as in the editor
    private int m_ChopDamage;                      // how much damage the tree will take
    private int m_CurrentChoppingTreeIndex = -1; // the actual index of the tree we are hitting

Now create a class to store the switch statement in (you can name this what ever you want but make sure you call the same function in update() )

 

void Update()
    {
        SelectAction();

    }

    public void SelectAction()
    {

Ok so now we do a simple raycast and return the tag of the hit in the SelectAction() function..

 

 RaycastHit hit;
        if (Input.GetMouseButtonDown(1))
        {

            // This ray will see what is where we clicked er chopped
            var impactOnScreenPosition = Camera.main.WorldToScreenPoint(Input.mousePosition); //checks where we need to shot the ray

            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // raycasts to the position we have clicked

Now we check what the object we hit with the raycast is.

for demo purposes I have used stones and copper and what not. you can add functionality any item in game with this method. 
 

if (Physics.Raycast(ray, out hit, 10.0f))
            {
                objectTag = hit.collider.gameObject.tag; // checks what object we have hit by its tag
                switch (objectTag)
                {

                    case "Stone":
                        break;
                    case "copper":
                        break;
                    case "Clay":
                        break;
                    case "Terrain":

Now to interact with the trees we are going to add our functionality to the terrain block.

 

 case "Terrain": (put below code inside of this) 


                       

 terrain = hit.collider.gameObject.GetComponent<Terrain>(); // so this code assigns the terrain variable when raycast hits
                        terrainData = terrain.terrainData;  //gets a refernce to the hit terrains terrain data
                        float sampleHeight = terrain.SampleHeight(hit.point); // now this float check the terrains sample height at the raycasts hit point
                        if (hit.collider.gameObject.GetComponent<Terrain>() == null)
                        {
                            Debug.Log("Didnt hit a thing mate");
                            return;
                        }

Interaction.cs

Link to comment
Share on other sites

I have no idea how to get a tree out of the terrain to interact with it! But that would be interesting!

Link to comment
Share on other sites

Ok so part 2:

so in the switch just after our if statement we want to check if we hit the terrain and if the hit point is under the sample height or above the sample height. Unity terrain trees are actually part of the terrain collider. Its a good idea to have a few debugs to check if it is working.

if (hit.point.y <= sampleHeight)
{
   Debug.Log("Hit Terrain under the float point");

}
 if (hit.point.y > sampleHeight)
 {
  Debug.Log("Hit the tree");
 }

Now we create a function and reference it in the if the is > sampleHeight.  I will call mine ChopAction() but you can name it what ever you want. Put chopAction function into the if statement above, like this...

if (hit.point.y > sampleHeight)
{
     ChopAction();
  }

Now in the function for ChopAction we will add these.

public void chopAction()
    {

        int protoTypeIndex = ChopTree(hit);
        if (protoTypeIndex == -1)
        {
            // We haven't chopped enough for it to fall.
            return;
        }
        GameObject protoTypePrefab = terrain.terrainData.treePrototypes[protoTypeIndex].prefab;

    }

Now after that we create a function that returns a raycast hit. I will call mine ChopTree()

public int ChopTree(RaycastHit hit)
    {
        TerrainData hitTerrain = terrain.terrainData; // ref to the terrain data
        TreeInstance[] treeInstances = hitTerrain.treeInstances; // gets the instances of the terrain trees based on the terrain hit

        // Our current closest tree initializes to far away
        float maxDistance = float.MaxValue;
        // Track our closest tree's position
        var closestTreePosition = new Vector3();
        // Let's find the closest tree to the place we chopped and hit something
        int closestTreeIndex = 0;
        var closestTree = new TreeInstance();
        for (int i = 0; i < treeInstances.Length; i++)
        {
            TreeInstance currentTree = treeInstances[i];
            // The the actual world position of the current tree we are checking
            Vector3 currentTreeWorldPosition = Vector3.Scale(currentTree.position, hitTerrain.size) +
                terrain.transform.position;

            // Find the distance between the current tree and whatever we hit when chopping
            float distance = Vector3.Distance(currentTreeWorldPosition, this.gameObject.transform.position);

            // Is this tree even closer?
            if (distance < maxDistance)
            {
                maxDistance = distance;
                closestTreeIndex = i;
                closestTreePosition = currentTreeWorldPosition;
                closestTree = currentTree;

            }
        }

        // get the index of the closest tree..in the terrain tree slots, not the index of the tree in the whole terrain
        int prototypeIndex = closestTree.prototypeIndex;

        // Play our chop shound
        PlayChopSound();  // create a function to play a sound if you want


        if (m_CurrentChoppingTreeIndex != closestTreeIndex)
        {
            //This is a different tree we are chopping now, reset the damage!
            // This means we can only chop on one tree at a time, switching trees sets their
            // health back to full.
            m_ChopDamage = ChopPower;
            m_CurrentChoppingTreeIndex = closestTreeIndex;
        }
        else
        {
            // We are chopping on the same tree.
            m_ChopDamage += ChopPower;
        }


        if (m_ChopDamage >= 100)
        {
            var treeInstancesToRemove = new List<TreeInstance>(hitTerrain.treeInstances);
            // We have chopped down this tree!
            // Remove the tree from the terrain tree list
            treeInstancesToRemove.RemoveAt(closestTreeIndex);
            hitTerrain.treeInstances = treeInstancesToRemove.ToArray();

            // Now refresh the terrain, getting rid of the darn collider
            float[,] heights = hitTerrain.GetHeights(0,0,0,0);
            hitTerrain.SetHeights(0, 0, heights);
            treeInstancesToRemove.RemoveAt(closestTreeIndex);
            hitTerrain.treeInstances = treeInstancesToRemove.ToArray();

            // Put a falling tree in its place, tilted slightly away from the player
              var fallingTree =(GameObject) Instantiate(FallingTreePrefab[prototypeIndex], closestTreePosition,
                     Quaternion.AngleAxis(2, Vector3.right));

           
            fallingTree.transform.localScale = new Vector3(closestTree.widthScale * fallingTree.transform.localScale.x,
                closestTree.heightScale * fallingTree.transform.localScale.y,
                closestTree.widthScale * fallingTree.transform.localScale.z);
        }
        return prototypeIndex;
    }

Now all you need to do is create the same trees you have as prefabs and add colliders and a rigidbody to them and place them in the array FallingTreePrefab[].make sure you add them in at the same position they are in the terrain tree spot.

Obviously this is bear bones and can use some work to sync animations and check if you have an axe equiped and such.

Add the chop damage to the interaction script and get chopping.

Here is a quick video of it in action I only added 1 tree type into the prefabs so its the same type that falls.

attached is the complete code.

 

Interaction.cs

Link to comment
Share on other sites

3 hours ago, DucaDiMonteSberna said:

ah ok so basically you are hiding the tree in the terrain and instantiating your own! Clever!

Yeah it finds what tree is closest to the position hit. Delete it from the terraindata file and instantiates a prefab.

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Tell a friend

    Love Canopy - Procedural Worlds? Tell a friend!
  • Need help?

    We work with some of the biggest brands in global gaming, automotive, technology, and government to create environments, games, simulations, and product launches for desktop, mobile, and VR.

    Our unique expertise and technology enable us to deliver solutions that look and run better at a fraction of the time and cost of a typical project.

    Check out some of our non-NDA work in the Gallery, and then Contact Us to accelerate your next project!

×
×
  • Create New...