Problem of placing game objects on the terrain surface

I’m trying to spawn a series of game objects, which I call obstacles, on Terrain surface when starting the game. The terrain has some elevations and its settings are the terrain’s default:
Set Height in Paint Terrain section is default 0
Pixel Error: 1
Terrain Width/Length: 1000
Terrain Height: 600
Detail Resolution Per Patch: 32
Detail Resolution: 1024
.
Suppose I spawn 10-20 objects. From this amount, some of them are spawned correctly on the terrain’s surface(where surface is flat or little rough) which is ok. but Where the ground is slightly sloping, objects are spawned above the height of the terrain, or below(inside of) the terrain’s surface. And my problem is exactly here that I want all the objects to spawn on the terrain’s surface more precisely, whether the point to be spawned is a mountain or a valley.
my script:

    public class LevelBasedObjectSpawner : MonoBehaviour
    {
        GameplayManager _gameplayManager;
        PathPointsSpawner _pathPointsSpawner;

        [SerializeField] List<LevelConfig> levelConfigs;
        int _levelIndex;
        [SerializeField] Transform _parent_PhysicalDangerous;


        void On_SpawnPathPoints_Completed()
        {
            LevelConfig config = levelConfigs[_levelIndex];
            SpawnObjects(config.dangerousPhysicalObjects, _parent_PhysicalDangerous);
        }

        Vector3 GetRandomPositionBetweenPoints(Vector3 start, Vector3 end)
        {
            // Find a random point along the line between start and end
            float t = Random.Range(0f, 1f);  // Random value between 0 and 1
            Vector3 randomPoint = Vector3.Lerp(start, end, t);

            // Adjust height based on terrain (using SampleHeight solution)
            float terrainHeight = _gameplayManager.terrain.SampleHeight(randomPoint);
            randomPoint.y = terrainHeight;

            return randomPoint;
        }

        void SpawnObjects(List<ObjectSpawnConfig> objectConfigs, Transform parent)
        {
            var spawnedPoints = _pathPointsSpawner._spawnedPoints;
            for (int i = 0; i < spawnedPoints.Count - 1; i++)
            {
                Transform start = spawnedPoints[i];
                Transform end = spawnedPoints[i + 1];

                Vector3 segmentDirection = (end.position - start.position).normalized;
                Vector3 perpendicularDirection = Vector3.Cross(segmentDirection, Vector3.up).normalized; // Perpendicular vector for "left-right" offset

                foreach (var objConfig in objectConfigs)
                {
                    int _spawnCount = Random.Range(0, objConfig.spawnNumber);
                    for (int j = 0; j < _spawnCount; j++)
                    {
                        Vector3 spawnPos = GetRandomPositionBetweenPoints(start.position, end.position);

                        // Apply random offset in the perpendicular "left-right" direction
                        float lateralOffset = Random.Range(-objConfig.horizontalOffset, objConfig.horizontalOffset);
                        spawnPos += perpendicularDirection * lateralOffset + new Vector3(0, objConfig.yOffset, 0); // Adding yOffset

                        Instantiate(objConfig.prefab, spawnPos, Quaternion.identity, parent);
                    }
                }
            }
        }

    }

No matter how much I tried and changed my code, I could not get the result.
.
I also used another method to check from above to bottom with ray casting to calculate the surface of terrain, but the result was the same and again the objects are below or above the height of the terrain.
code:

Vector3 GetRandomPositionBetweenPoints(Vector3 start, Vector3 end)
    {
        // Find a random point along the line between start and end
        float t = Random.Range(0f, 1f);
        Vector3 randomPoint = Vector3.Lerp(start, end, t);

        // Cast a ray downward to find the terrain surface
        RaycastHit hit;
        Vector3 rayOrigin = randomPoint + Vector3.up * 100f;  // Starting the ray above the random point
        if (Physics.Raycast(rayOrigin, Vector3.down, out hit, Mathf.Infinity, terrainLayerMask))
        {
            randomPoint.y = hit.point.y;  // Set the y-coordinate to the hit point's y position
        }
        else
        {
            // If no terrain hit, keep the original y value or set a default height if necessary
            randomPoint.y = _gameplayManager.terrain.SampleHeight(randomPoint);
        }

        return randomPoint;
    }

If you have a flat object (house, etc.) and not-flat ground, you must choose what you want to happen.

Here’s some possibilities:

  • make an “adaptor” (like a foundation) that you place, then put the house on that
  • have a single reference point on the object and put that on the ground
  • have more than one point (the center and the corners perhaps?) that you check (many raycasts), then use the lowest contact point, burying some of the object in the ground
  • flatten the ground in a large enough area to fit the flat object flush to ground
  • tilt the spawned object to align (this is also hard, opening another huge can of worms: how do you fit to an unevenly-sloping surface, a pinnacle, etc.)

Alternately design the object itself to have its own foundation, sort of like the root of a tooth, that you don’t mind burying as necessary.

Any and all of these things will have wildly different outcomes and require wildly different engineering solutions, so think about the complete problem space (which includes all forseeable combinations you want to address) before moving forward.

That just means you have a bug… and that means… time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

ALSO: this might be very useful to you to understand the math and coordinate conversions (and there are MANY!!) necessary to solve your problem:

Matching terrain heights to a collider:

I also have a ton of terrain procgen examples here:

MakeGeo is presently hosted at these locations:

https://bitbucket.org/kurtdekker/makegeo

1 Like