0
\$\begingroup\$

With the script I instantiate random objects above the terrain. But the random was only on x and z. Now I want the height of the objects to be random as well.

The terrain position is: X = -250, Y = 0, Z = -250.

So I changed the line:

var y = Extents.y;

To:

var y = Random.Range(-Extents.y, Extents.y);

But now half of the objects above the terrain and half below the terrain, and I want them all to be above the terrain; thus the random height should be above the terrain.

Objects

My full code for reference:

using System;
using UnityEngine;
using Random = UnityEngine.Random;

[ExecuteInEditMode]
public class SphereBuilder : MonoBehaviour
{
    // for tracking properties change
    private Vector3 _extents;
    private int _sphereCount;
    private float _sphereSize;

    /// <summary>
    ///     How far to place spheres randomly.
    /// </summary>
    public Vector3 Extents;

    /// <summary>
    ///     How many spheres wanted.
    /// </summary>
    public int SphereCount;

    public float SphereSize;

    private GameObject parent;

    private void Start()
    {
        parent = GameObject.Find("Target Builder");
    }

    private void OnValidate()
    {
        // prevent wrong values to be entered
        Extents = new Vector3(Mathf.Max(0.0f, Extents.x), Mathf.Max(0.0f, Extents.y), Mathf.Max(0.0f, Extents.z));
        SphereCount = Mathf.Max(0, SphereCount);
        SphereSize = Mathf.Max(0.0f, SphereSize);
    }

    private void Reset()
    {
        Extents = new Vector3(250.0f, 20.0f, 250.0f);
        SphereCount = 100;
        SphereSize = 20.0f;
    }

    private void Update()
    {
        UpdateSpheres();
    }

    private void UpdateSpheres()
    {
        if (Extents == _extents && SphereCount == _sphereCount && Mathf.Approximately(SphereSize, _sphereSize))
            return;

        // cleanup
        var spheres = GameObject.FindGameObjectsWithTag("Target");
        foreach (var t in spheres)
        {
            if (Application.isEditor)
            {
                DestroyImmediate(t);
            }
            else
            {
                Destroy(t);
            }
        }

        var withTag = GameObject.FindWithTag("Terrain");
        if (withTag == null)
            throw new InvalidOperationException("Terrain not found");

        for (var i = 0; i < SphereCount; i++)
        {
            var o = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            o.tag = "Target";
            o.transform.localScale = new Vector3(SphereSize, SphereSize, SphereSize);
            o.transform.parent = parent.transform;

            // get random position
            var x = Random.Range(-Extents.x, Extents.x);
            //var y = Extents.y; // sphere altitude relative to terrain below
            var y = Random.Range(-Extents.y, Extents.y);
            var z = Random.Range(-Extents.z, Extents.z);

            // now send a ray down terrain to adjust Y according terrain below
            var height = 10000.0f; // should be higher than highest terrain altitude
            var origin = new Vector3(x, height, z);
            var ray = new Ray(origin, Vector3.down);
            RaycastHit hit;
            var maxDistance = 20000.0f;
            var nameToLayer = LayerMask.NameToLayer("Terrain");
            var layerMask = 1 << nameToLayer;
            if (Physics.Raycast(ray, out hit, maxDistance, layerMask))
            {
                var distance = hit.distance;
                y = height - distance + y; // adjust
            }
            else
            {
                Debug.LogWarning("Terrain not hit, using default height !");
            }

            // place !
            o.transform.position = new Vector3(x, y, z);
        }

        _extents = Extents;
        _sphereCount = SphereCount;
        _sphereSize = SphereSize;
    }
}
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

If terrain is at y = 0, you can make sure that the random y value you need is never smaller than zero by writing it like this:

var y = Random.Range(0, 2*Extents.y);

Although the range of values will be as wide as the previous ones, if a primitive gets a y value of 0 it will be split into two halves by the plane:

enter image description here

That's because the primitive origin is its geometric center. So, if we "push" the primitive upwards enough to let it out of being stuck in the plane, it will be just standing onto it:

enter image description here

If you're using spheres as primitives, and you know their radius is r, then you can improve the code like this:

var y = Random.Range(r, 2*Extents.y);

That means the minimum value will be the radius r of the sphere, making the sphere standing on the floor. I guess your radius value is stored by the variable SphereSize, so:

var y = Random.Range(SphereSize, 2*Extents.y);
\$\endgroup\$
2
  • \$\begingroup\$ It's working fine when I generate 10 or 100 objects but when I changed the Sphere Count for 1000 for testing some of the spheres half under the terrain. Maybe I'm wrong but it looks like that. Here is a link for a screenshot: imgur.com/a/vgbVrfO \$\endgroup\$ Jun 4, 2018 at 16:17
  • \$\begingroup\$ Oh snap, of course this happens! I updated my answer, you shall solve your problem now :) \$\endgroup\$
    – liggiorgio
    Jun 4, 2018 at 17:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .