Instantiate an Object at a random position avoiding colliders

Players can place items/buildings in my top-down 2d game, this means that there are no pre-defined areas in a scene that are empty of colliders/obsticles.

The problem is I need to Instantiate NPCs at a random position in the scene when the player enters that scene, but sometimes they spawn on top of a building or some other collider, which prevents them from wandering around (and obviously looks weird).

Right now I’m using a simple PolygonCollider2D to determine a spawn-area, which is more or less the entire scene, then I pick the spawn-position like this:

public Vector3 FindPointInArea()
    {
        var bounds = _collider.bounds;
        var center = bounds.center;

        float x = 0;
        float y = 0;
        int attempt = 0;
        do
        {
            x = Random.Range(bounds.min.x, bounds.max.x);
            y = Random.Range(bounds.min.y, bounds.max.y);
            attempt++;
        }
        while (!_collider.OverlapPoint(new Vector2(x, y)));

        return new Vector3(x, y, 0);
    }

Is there some smart solution that can make sure NPCs are never Instantiated on top of an obsticle?

How about something like this :

  • After the placing of all buildings, create a new List<Vector3> vAvailable.
  • Pick a granularity - let’s say 1.0 for simplicity.
  • Now loop over your entire play area stepping by ‘granularity’ along the X and Y axes.
  • If the position at that location is available, then add it into vAvailable.
  • Now - when you want a randomly valid position, just choose vAvailalbe[ Random.Range( 0, vAvailalbe.Count )]

I would have to create the vAvailable List every time the player makes a change to the scene (adding another building for example), but as long as that doesnt end up being too costly this seems like a very neat solution :slight_smile: Will try it

Either that, or, step through aAvailable (typically in reverse order) and if the point is no longer valid, just remove it from the list. :slight_smile:

1 Like

Ill give both a try and see which works best :slight_smile:

Would I then just do a raycast at every x,y, looking for a collider?

1 Like

You would certainly have to establish some method for working out whether the location is availalbe or not.

Raycasting may be one way of doing that. Another may be to create a simple GameObject with a trigger collider attached - place it at the location and see if the collider hits another collider or not.

1 Like

This, trigger mesh collider is my best solution to odd shaped buildings, otherwise I use raycasts and overlap sphere(not sure what the 2D counterpart is, overlap circle perhaps?)

This

Update: This works like a charm and does not seem at all costly :slight_smile: Although I did realize I’m basically just doing the same scan that A* pathfinding is doing, so I’m going to see if I can use that data and save on performance cost :slight_smile:

1 Like

That’s also an interesting option and makes sense. :slight_smile:

For people coming here in the future, this is how I ended up solving this using A* pathfinding data available (assuming you use that asset obviosly :slight_smile: )

public Vector3 FindWalkablePointInArea()
    {
        var bounds = _collider.bounds;
        var nodes = AstarPath.active.data.gridGraph.GetNodesInRegion(_collider.bounds);

        int randomNode = 0;

        do
        {
            randomNode = Random.Range(0, nodes.Count);
        }
        while (!_collider.OverlapPoint(new Vector2(nodes[randomNode].position.x, nodes[randomNode].position.y)) && nodes[randomNode].Walkable);
      
        return (Vector3)nodes[randomNode].position;
    }
1 Like

The way I would do it would be pick a random location and then use Physics.OverlapBox or Physics.OverlapSphere to check for any colliders you don’t want. If none you’re good to go, if you find any then pick a new random location and try again.

Thanks, but that is already what I am doing in the code above :slight_smile: Only I’m using a Polygon.