How to get the ground point of a Vector3?

What I’m trying to do is spawn monsters/powerups on the ground. Here’s how I’m doing it at the moment.

  • Find a starting point before randomizing, in this case it would be the player’s position.
  • Randomize the z and x axis whilst using the player’s Y position. Since I know that the Y is walkable/jumpable.
  • Raycast upward from that randomized point to find the ceiling point using the obstacle mask
  • From the ceiling point raycast downward to find the ground point
  • Overlap sphere to make sure there aren’t any others of it’s kind in that position.

I’m open to using NavMeshHit as well if it’s easier.

Find Ground Point:

    private static Vector3 FindGroundPoint(Vector3 pos, LayerMask obstacleMask)
    {
        //FindCeiling then
        float rayLength = 100;
        float castPosY = rayLength;
        if (Physics.Raycast(pos, Vector3.up, out RaycastHit ceilingHit, rayLength, obstacleMask))
        {
            castPosY = ceilingHit.point.y;
        }
        if (Physics.Raycast(new Vector3(pos.x, castPosY, pos.z), Vector3.down, out RaycastHit groundHit, 1000, obstacleMask))
        {
            return groundHit.point;
        }
        else
        {
            Debug.LogError("ERROR: Ground Not Found");
            return Vector3.zero;
        }
    }

Find Random Position:

   public static bool GetRandomPosition(Vector3 startPos, float minDistance, float maxDistance, LayerMask spawnObjectMask, LayerMask obstacleMask, float checkRadius, out Vector3 randomPos)
    {
        float x = Random.Range(minDistance, maxDistance);
        float z = Random.Range(minDistance, maxDistance);
        int xModifier = Random.Range(0, 100) > 50 ? 1 : -1;
        int zModifier = Random.Range(0, 100) > 50 ? 1 : -1;
        randomPos = FindGroundPoint(new Vector3((startPos.x + x * xModifier), startPos.y, startPos.z + (z * zModifier)), obstacleMask);
        Collider[] results = Physics.OverlapSphere(randomPos, checkRadius, spawnObjectMask);
        if (results == null || results.Length == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

I don’t understand the point of raycasting upwards then downwards. Couldn’t you just raycast downwards only?
Is your code not working?

2 Likes

Yeah what’s the actual question here?

You can probably just pick some arbitrarily high point and raycast infinitely downwards to find a point on the ground.

2 Likes

Right, the issue is probably that he thinks when his initial point is below the surface we should cast upwards. However that would not work because a raycast can only detect the surface of a collider from the outside. A raycast from the inside will not hit the collider.

2 Likes

Well in my case I would need to find a ceiling too, so two raycasts just to get a ground point. I was wondering if there was something built in navmesh or unity to do this with less code.

In order to make sure it doesn’t spawn on top of surfaces instead of under. Think of something like a doorway, i want it to spawn under the doorway

It still doesn’t make sense to do an upward raycast. Do what spiney199 suggested above. Make sure the raycast can pass through everything except spawnable surfaces.

using UnityEngine;
public class Spawner : MonoBehaviour // Spawn monsters somewhere off in the distance
{
    public GameObject monster;

    void Start()
    {
        Vector3 pos=Quaternion.Euler(0,Random.Range(0,360),0)*Vector3.right*30; // Get a random distant point
        if (!Physics.SphereCast(transform.position,0.5f,pos,out RaycastHit hit,30)) // Nothing between us and the point?
            if (Physics.Raycast(transform.position+pos,Vector3.down,out hit,2)) // Ground below the point?
                Instantiate(monster,hit.point+Vector3.up,Quaternion.identity); // RAWR!!
        Invoke("Start",0.5f); // keep doing it
    }
}
1 Like

Well It does make sense when you understand the reason behind the two raycasts. Imagine a 3d level with several different hallways and levels. Choosing a new random location on the height of your player, that potential new location could be in a hallway at a higher elevation. So casting downwards wouldn’t get you a proper position. So he first searches for the ceiling and then does another raycast from the ceiling down to find the floor. Though what doesn’t really make sense to me is this default value:

float castPosY = rayLength;

So when the potential position is below your current height, he would start the downward ray at an arbitrary absolute position that is equal to the ray length, so relative from absolute 0 rayLength upwards. Technically, depending on the level layout, that point at a height of 100 could still be below a potential valid location as this is not relative to the current player position but an absolute height in worldspace.

I would generally start with a single raycast downwards from the player center height to find the floor. If there is no floor below, do a raycast upwards to find the closest ceiling at this location and then do another raycast down to find the floor below that ceiling point. Just going up a fix amount and casting down would cause many issues if your level has multi layers / levels stacked on top of each other as you would always just hit the top floor.

Though just picking a random point in a rectangle around the player also isn’t a great way to navigate anyways :slight_smile: However this all depends on the level layout.

1 Like

Thanks for the detailed explanation. Getting the center position of the capsule collider seems like a way to approach this thanks!