6
\$\begingroup\$

I've been searching internet to find an efficient way to create a procedural tilemap without creating a GameObject per tile. All the TileMap tutorials I've found are creating tiles by creating hundreds of GameObjects. Thus the hierarchy in Unity is expanding unexpectedly.

I'm pretty sure this is not the "right" way to do it. Especially after seeing the new Unity 2D tools that supports tilemaps. Unity made it so, you can create tiles in "one" gameobject, not for each tile.

So, how do I do it in the right and effiecent way?

\$\endgroup\$
  • 1
    \$\begingroup\$ You've cross posted this to two SE sites. Pick one. \$\endgroup\$ – Almo Jun 18 '15 at 18:56
  • 1
    \$\begingroup\$ What's your specific concern with creating large numbers of GameObjects? If it's just hierarchy clutter, there are easy ways to fix that. Unity's batching should combine similar tiles into a single batch anyway, so there's unlikely to be a major draw call penalty to doing it this way. Minimizing offscreen tiles and time spent in tile update functions are both solvable problems too. Have you profiled your game and identified a specific bottleneck you're trying to solve? \$\endgroup\$ – DMGregory Jun 18 '15 at 19:27
  • \$\begingroup\$ @DMGregory Well if we think about 100*100 map in screen (if we assume we zoomed out to see whole map), it will make 10.000 gameobjects in the scene. It also means 10.000 unncesary transform component. I don't even know if unity can support 10.000 object in hieararchy window. I know it freezes after couple hundred. \$\endgroup\$ – BerkayD Jun 18 '15 at 20:02
6
\$\begingroup\$

Here's what I'm aware of:

Option 1. GameObject per tile. It's not completely horrible in certain cases. Depending on your needs, it could work.. well enough.

Option 2. A single quad or plane referencing a texture you create at run-time. You would essentially use your tile atlas texture to "paint" your map as one new texture. Depending on the size of your map of course, you might want to have multiple quads/planes each representing portions of your map.

Option 3. Create your own mesh. This would more than likely be the method you'll like most once implemented. It'll give you tons of flexibility and probably the highest performance. You would essentially create a quad per tile and set each vertex UV to map to the tiles in your tile atlas.

For Option 2, I'd suggest watching this video series by quill18creates: 3D TileMap tutorial series by quill18creates

For Option 3, this is my code, with tweaks, so it may not be perfect:

//For this, your GameObject this script is attached to would have a
//Transform Component, a Mesh Filter Component, and a Mesh Renderer
//component. You will also need to assign your texture atlas / material
//to it. 

void Start() {
    meshFilter = GetComponent<MeshFilter>();

    BuildMesh();
}

public void BuildMesh() {
    int numTiles = mapSizeX * mapSizeY;
    int numTriangles = numTiles * 6;
    int numVerts = numTiles * 4;

    Vector3[] vertices = new Vector3[numVerts];
    UVArray = new Vector2[numVerts];

    int x, y, iVertCount = 0;
    for (x = 0; x < mapSizeX; x++) {
        for (y = 0; y < mapSizeY; y++) {
            vertices[iVertCount + 0] = new Vector3(x, y, 0);
            vertices[iVertCount + 1] = new Vector3(x + 1, y, 0);
            vertices[iVertCount + 2] = new Vector3(x + 1, y + 1, 0);
            vertices[iVertCount + 3] = new Vector3(x, y + 1, 0);
            iVertCount += 4;
        }
    }

    int[] triangles = new int[numTriangles];

    int iIndexCount = 0; iVertCount = 0;
    for (int i = 0; i < numTiles; i++) {
        triangles[iIndexCount + 0] += (iVertCount + 0);
        triangles[iIndexCount + 1] += (iVertCount + 1);
        triangles[iIndexCount + 2] += (iVertCount + 2);
        triangles[iIndexCount + 3] += (iVertCount + 0);
        triangles[iIndexCount + 4] += (iVertCount + 2);
        triangles[iIndexCount + 5] += (iVertCount + 3);

        iVertCount += 4; iIndexCount += 6;
    }

    mesh = new Mesh();
    //mesh.MarkDynamic(); if you intend to change the vertices a lot, this will help.
    mesh.vertices = vertices;
    mesh.triangles = triangles;
    meshFilter.mesh = mesh;

    UpdateMesh(); //I put this in a separate method for my own purposes.
}


//Note, the example UV entries I have are assuming a tile atlas 
//with 16 total tiles in a 4x4 grid.

public void UpdateMesh() {
    int iVertCount = 0;

    for (int x = 0; x < mapSizeX; x++) {
        for (int y = 0; y < mapSizeY; y++) {
            UVArray[iVertCount + 0] = new Vector2(0, 0); //Top left of tile in atlas
            UVArray[iVertCount + 1] = new Vector2(.25f, 0); //Top right of tile in atlas
            UVArray[iVertCount + 2] = new Vector2(.25f, .25f); //Bottom right of tile in atlas
            UVArray[iVertCount + 3] = new Vector2(0, .25f); //Bottom left of tile in atlas
            iVertCount += 4;
        }
    }

    meshFilter.mesh.uv = UVArray;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Your answers did help me back in the days. Now, I am looking through my old questions, and I've realized I've never checked this answer as accepted. Sorry for an one eternity late approval ^^ Thanks for the answer. \$\endgroup\$ – BerkayD Dec 9 '16 at 21:29
0
\$\begingroup\$

As you (I think) alluded to, Unity's roadmap has plans for a tilemap editor. I'm looking forward to it, because right now it's a little confusing how to proceed.

In the meantime, the best approach I'm seeing is to make your map in Tiled and then use X-UniTMX to import it into Unity. Obviously that's not procedural generation though; I'd imagine you would use the same technique as X-UniTMX is using to create the mesh.

\$\endgroup\$
0
\$\begingroup\$

You could just have the tiles created deeper in the hierarchy and close that folder and don't be bothered. What you can do as well is hide your game objects from the hierarchy `myTile.hideFlags = HideFlags.HideInHierarchy" not sure about how much performance would be gained.

Showing 100x100 textures on the screen is never a good idea drawing textures is more heavy then drawing polygons anyway. If you need to zoom out that far you are better off with bigger textures. Same goes for being zoomed in, you should not draw the textures that do not need to be drawn.

What I usually do (I'm not a Unity guy) is creating a Class for a map and use a 2 dimensional array for holding all your tiles. This array could be simply of ints or a Tile class with a couple of ints that refer to your textures, tile type, objects, etc. Then one would draw it as follows:

for (int y = 0; y < mapHeight; y++)
{
    for(int x = 0; x < mapHeight; x++)
    {
        Graphics.DrawTexture(new Rect(x * tileWidth, y * tileHeight, tileWidth, tileHeigt), tileTexture);
    }
}

The draw method I use seems to be for drawing a GUI but might be perfectly suitable for the task. Otherwise you have to dig into the sprite manager and find a way that works with this method.

You could use a dictionary for texture reference like int 1 in your tile class refers to some texture and int 2 refers to another. In the line you are drawing your texture you would use your dictionary to give the right texture object. This way it should not create a GameObject and thus not create all unnecessary overhead.

You can do the same for pretty much everything that does not need the level of control of a GameObject. Mind though, this way it will draw every tile of your map it most probably does not auto cull the objects like I think unity does with game objects. But just drawing what needs to be on screen is very easy. You know the screen/viewport size, camera position and the tile size. Some simple math should draw exactly what's needed.

Still, unity is a 3D engine. It just supports 2D in a 3D way. But you can basically do the same thing for quads/meshes with textures and a Orthographic camera. Just drawing those meshes needed is pretty cheap.

\$\endgroup\$
0
\$\begingroup\$

As someone working on voxel/block-based project, I cannot help but think about just using the same approach, only in 2D. You can build a grid-like mesh procedurally and assign right coordinates of tile visuals in your texture atlas to UV channel. In 3D you have to do much more job than this to have high performance, but in 2D very likely it's really enough.

\$\endgroup\$
0
\$\begingroup\$

You could always use LeanPool. Its a free asset on the asset store. It does object pooling for you. Then you can do a simple cull system around the cameras view that spawns/de spawns tile gameobjects as they enter/exit the cameras view. Im not sure on how big your tilemap is going to be but Unity can handle quite a bit of gameobjects. LeanPool has benchmark scenes in Unity where you can spawn/ de spawn a thousand objects with box collision components and I think possibly a render component as well on each one and it does it very very quickly.

https://gamedevacademy.org/how-to-script-a-2d-tile-map-in-unity3d/

Here is a link that will show you an example of using LeanPool with a tilemap.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.