How do you randomly spawn an object on the unused places of a procedurally generated tilemap?

Does anybody know how to randomly spawn the player (or anything else) on a procedurally generated platformer tilemap so that they don’t spawn inside the walls? I really need help about that. Sigh.

My idea is getting the unused places on the tilemap after the platforms have been generated, put them in a new list and then use that list to spawn the player but I don’t know how to do that. I’m not too knowledgeable at coding yet.

To make things a little more complicated, the code I use to generate the tilemaps involves using integers arrays and instantiating objects on a map, whatever the map, involves using Vector3.

Here is the code I use to generate the maps (thanks to Ethan Bruins on the Unity Blog and his articles about randomly generating tilemaps); several maps are generated on the top of each other. I collapsed the switch block because it’s just a list to choose from.

Maps generation code:

    public void GenerateMap()
    {
        levelMapsList = new List<int[,]>();

        //Work through the List of mapSettings
        for(int i = 0; i < mapSettings.Count; i++)
        {
            int[,] map = new int[width, height];
            float seed = mapSettings[i].randomSeed ? Time.time.GetHashCode() : mapSettings[i].seed.GetHashCode();

            //Generate the map depending on the algorithm selected
            switch(mapSettings[i].algorithm) ...

            //Add the map to the list
            levelMapsList.Add(map);
        }

        //Allows for all of the maps to be on the same tilemap without overlaying
        Vector2Int offset = new Vector2Int(-width / 2, (-height / 2) - 1);

        //Work through the list to generate all maps
        foreach(int[,] item in levelMapsList)
        {
            Algorithms.RenderMapWithOffset(item, tilemap, ruleTile, offset);
            offset.y += -height + 1;
        }
    }

    /// <summary>
    /// Renders a map using an offset provided, Useful for having multiple maps on one tilemap
    /// </summary>
    /// <param name="map">The map to draw</param>
    /// <param name="tilemap">The tilemap to draw on</param>
    /// <param name="tile">The tile to draw with</param>
    /// <param name="offset">The offset to apply</param>
    public static void RenderMapWithOffset(int[,] map, Tilemap tilemap, TileBase tile, Vector2Int offset)
    {
        for(int x = 0; x < map.GetUpperBound(0); x++)
        {
            for(int y = 0; y < map.GetUpperBound(1); y++)
            {
                if(map[x, y] == 1)
                {
                    tilemap.SetTile(new Vector3Int(x + offset.x, y + offset.y, 0), tile);
                }
            }
        }
    }

Player generation code:

    private void InstantiatePlayer()
    {
        int[,] map = new int[width, height];
        for(int x = 0; x < width; x++)
        {
            for(int y = 0; y < height; y++)
            {
                if(map[x, y] == 0)
                {
                    int spawnLocationX = x;
                    int spawnLocationY = y;
                    playerClone = Instantiate(playerPrefab, new Vector3(spawnLocationX, spawnLocationY, 0f), Quaternion.identity);
                    playerFollowCamera.m_Follow = playerClone.transform;
                    Debug.Log("instantiating player");
                    return;
                }
            }
        }
    }

Both work fine but the player spawns inside the walls of the tilemap on a regular basis. I need to replace:

        int[,] map = new int[width, height];

by an array that contains only the unused places on the tilemap that was procedurally generated so that the player doesn’t spawn inside the walls.

I could just use a way for the player to regenerate the maps in game but I need a reliable code that can also generate objects on a map, obstacles, traps, and so on.

Thank you for your help if you can help me. :slight_smile:

In game images where the player has spawned inside walls:

@

I didn’t yet read your other code block, but your player placement wouldn’t work as it is, as it doesn’t make any sense. And you didn’t address that either. See this, what is currently happening:

void InstantiatePlayer()
{
    // Why empty array 2d here?
    int[,] map = new int[width, height];

    for(int x = 0; x < width; x++)
    {
        for(int y = 0; y < height; y++)
        {
            // this will be zero always, from first array item
            if(map[x, y] == 0)
            {
                // place player
            }
        }
    }
}

Like I said, it does work, except that the player spawns inside the walls from time to time.

I don’t see the difference between the code I posted and yours.

@

Well there isn’t I just made it obvious that you are creating an empty array where each cell will be 0. When you start your for loop, the first tile at 0,0 will be 0, hence it will be selected.

Yeah, I’m aware of that, it’s the whole object of this thread. I need a way to generate an array that would contain only the unused places of the generated map. I can edit my first post to make it clearer, thank you for pointing this out. :slight_smile:

@

But to answer to your question…

“I need a way to generate an array that would contain only the unused places of the generated map”

To get tiles that are valid, you could do something like this;

Get the bounds for certain area or the whole tilemap.

Then iterate those bounds for each position. Note - you might have to iterate slightly more than bounds area, if you want to capture topmost positions for example.

Then you can have a pretty “blind” heuristic that decides if location is valid - check if position is empty and if there is floor below. You can extend / replace this rule by making it a delegate to be more specific to place some specific things.

If location fills the requirements of previous step, store it to list.

Do the same for all tiles and you have a list of your locations.

I’ve done something similar and it worked for me.

Well, thank you for that! I need a crash course in how to do that now. :slight_smile:

@

I’ll give you good link, which will show you how to iterate tiles of the map;
https://gamedev.stackexchange.com/questions/150917/how-to-get-all-tiles-from-a-tilemap

Note, they also mention (in the discussion) that you can also use foreach style .allpositionsWithin; it will only get the tiles in that area, not empty cells, so it might not be as easy to understand/use for this purpose, IMHO.

1 Like

Thank you! :slight_smile:

Why not create a Transform[ ] array and use FindObjectsOfType() to locate all of the areas the player can’t spawn. Then spawn the player to an area that != the transform.position of the elements within the array?

I still need to try this but I’d like to ask advice to @ChuanXin about that. More brains are always useful. :wink:

Each item should contain the information on whether a Tile exists (item[x, y] == 1) on the map or not (item[x, y] == 0).

For the InstantiatePlayer, perhaps the spawnLocation should have the same offset as applied in the map generation to get the right position?

The solutions in this would work as well, where you would check if the TileBase at the location is null. Using InstantiatePlayer as above would work.

As an extension of this, you could check if the position above an existing Tile is empty or not. This would give you the position of the top of an empty platform where you can place your Player or other obstacles.

I have a brute force example here (https://github.com/ChuanXin-Unity/ProceduralPatterns2D) where I place flowers at the top of empty platforms and mushrooms at the bottom of empty platforms. I suppose you could substitute them with other objects randomly? The main scene would be Assets/Foilage.unity and scripts in Assets/Other Assets/Scripts.

If you do not want to loop through and locate cells which are empty, and the Tilemap has a TilemapCollider2D, you could try randomly choosing positions in the Tilemap and running Physics2D.OverlapBox (https://docs.unity3d.com/ScriptReference/Physics2D.OverlapBox.html) and place player where no collision is detected? This is probably useful if you are only trying to spawn the player, and not multiple objects.

2 Likes

@ChuanXin

“As an extension of this, you could check if the position above an existing Tile is empty or not. This would give you the position of the top of an empty platform where you can place your Player or other obstacles.”

That is exactly what I already wrote and have done earlier. By scanning optionally several tiles around, it is more expensive but possible to get rule tile like way to place items.

“If you do not want to loop through and locate cells which are empty”

BTW - doesn’t allPositionsWithin do exactly this? It AFAIK it only gets the tiles from the bounds area, I might be wrong - edit - no it doesn’t.

The code I have works something like this. One tile gap is not a valid position.The sprite is just some random CC0 sprite.

5261396--526367--player_placement4.gif

1 Like

@ChuanXin @eses Thanks you very much for your contributions; I’ll take a look at all of them and see what I can do. :slight_smile:

Hi everyone!

I wish you a good new year! I cannot believe almost a month has passed since the last time I posted here! I had to fix some problems in my game but now I can come back to this. :slight_smile:

Thank you! This looks very promissing; I just downloaded your project, I’ll take a close look and see what I can do with it. :slight_smile:

@ChuanXin Nicely done! I have found the scripts and the rule tile with the mushroom and flower tiles with the offset set to y = -1 so that the mushrooms are upside down.

Now, I have to make it so that the rule tile draws prefabs. The player is already spawned when I generate the map; they’ll just have to be move to a position free of tile on the map and on a surface.

I also have objects I’ll have to spawn, like lights, that are also prefabs and I’d like these lights to be added in a place that would be at least 4 units high (two above the ground and one under the bottom of the upper platform.

Adding a game object to “Default game object” in the rule tile works somehow but the object is not created each time the platforms are created.

I’ve rewritten your FoilageAddTileLayer and PaintOnLayerRuleTile in order to understand how they work together and I’m using them in my game; I hope doing this is not a problem, even if one day I decide to sell this game?

My game starts on a scene where you have to choose between two players (for now, maybe more in the future).

So far, I’ve managed making it so that the Default Game Object in the Rule Tile is set to the selected player by code:

5339136--538788--Capture.JPG

public class LevelGenerator : MonoBehaviour
{
    public RuleTile ruleTile;

    public void GenerateMap()
    {
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        ruleTile.m_DefaultGameObject = player;
    }

and it works, the instantiated game object is the desired player. Disabling the supernumerary player wouldn’t be a problem but I’d rather moving the existing player instead of instantiating a new one.

That’s one thing, but then there is this recurring problem where the level is generated but the player is just not there; I have to regenerate the level several times so that it appears and most times there are several of them… I need to find a way so that the player is generated each time the level is and in only one instance.

I’ve noticed that the instantiated game object automatically comes with a tile under its feet:

5339136--538824--Capture.JPG

In my game, this makes my player take three units vertically and one horizontally. My guess is that the player is not instantiated if certain conditions are not met (too less room for example?); I’ll have to investigate this.

I’ll have good use for the plaint tiles list in the future but I’ll have to make it so that the tile are not painted everywhere on the scene as it is for now.

Thanks anyway for sharing your hard work @ChuanXin ! :slight_smile:

I’ve tried the first code on that page; it works, mostly, except that the player is generated far outside the bounds of the generated map. It’s a progress but I need to find a way to confine the player inside strict borders on the map, and make it spawn on a tile, not in the air…

I’m not done yet but thanks to you I’m going to make it! :slight_smile:

@

If you get your Tilemap bounds, it might contain empty area. Once you paint a tile, and then erase it, the bounds are already extended to cover this coordinate… so maybe this is the reason for your character spawning in empty area?

It might be inside your bounds, but there are no tiles anymore. You can try compressing Tilemap bounds from inspector (also from code), by right clicking Tilemap components menu icon, then select compress bounds.

Anyway, when you get the bounds like this:

BoundsInt bounds = tilemap.cellBounds;

You can then simply check that your are inside bounds, just like in that code snippet. Coordinate is inside bounds if its position is greater than bounds.min, and smaller than bounds.max values in both x and y axis. Then it is a matter of finding a cell inside this area, where you don’t have tile / have some specific tile.

Hi! :slight_smile:

It seems that the player is inside the bounds; I tried three times in a row and the player’s y position is always within the size of the map. Same thing, after compressing the map bounds.