- Home /
How do I optimize my usage of many GameObjects?
I'm currently prototyping a game using Unity's 2D tools, and I want the game to vaguely resemble Terraria. To this end, I want the scene to consist of a huge grid of tiles, much like Terraria.
Since the world needs to be destructible, and the player needs to collide with individual blocks, I'm making every tile a separate GameObject with a rigidbody2D and a box collider attached to it. Obviously this is very bad for performance, and the most I've managed at 60FPS was about 128x128 blocks.
Is there something I should be doing differently to get better performance? Is this sort of thing even possible in Unity? This isn't my first Unity project, but it is the first where I've attempted something on a scale as large as this.
Answer by Statement · Dec 22, 2013 at 07:47 PM
Treat a chunk of tiles as one game object.
It would mean that you would be generating a mesh that cover multiple tiles.
If you generate both the graphical mesh and the collider mesh, then you get away with much less game objects (and you should do). To interact with the tiles, you will now have to rethink your code design to allow a different communication method since you no longer have a game object per tile and thus can't have scripts per tile.
This approach is very feasible and I've used it in various 3d voxel implementations, one of which is an experimental hobby project called Clayful (video 1) (video 2). A lot of people ask for source, but unfortunately I've binned them so please don't ask for source :) Just included the videos to reinforce your belief that it's doable and feasible.
If you have any concerns about this approach, post a comment onto my answer and I'll get back to you later.
Conceptually, think of a chunk as some managable sized grid of tiles. Like 8x8 or 16x16. I dont have a "best" value, get a feeling for it yourself. Basically you want to have as big of a chunk as you can, but not so big that the act of recreating the meshes becomes a bottleneck.
You would create a new Mesh and update it when you need to rebuild the map. Therefore, knowing the Mesh API is a must. After the mesh is created, you need to set the objects MeshFilter.sharedMesh property.
Note: You may be required to set sharedMesh to null and back if physics doesn't seem to update!
To support multiple textures and keep drawcalls down, I would suggest using an atlas so you can use a single material for every tile. This is a good technique but forces you to use a single material. If you want to support multiple types of materials, thats possible too but its extra work and I'll leave it to you to solve that problem.
I would recommend that you have a manager game object that you use to interact with the tiles and not communicate with the chunks directly. So you could think of having one game object for your map, and children to it are your chunks.
The "Map" (Manager) has a script on it that you use whenever you want to work with tiles, so you can do stuff like:
map.SetTile(x, y, TileInfo.Brick);
map.Rebuild();
Introduce Proxy classes if you want to be able to get a "Tile" object to work with so you don't have to mess around with the map all of the time. Here you could also define any behaviour you want to support. Alternatively you could make a higher level API for your Map if you prefer that but I think Tile proxy is cleaner personally.
Note: I don't know if "Proxy" is the correct word for the pattern here, but basically the class is intended to change the Map when you use a Tile, without ever having to know about the Map from your scripts!
Let's see how a Tile class could be made.
// does not extend from MonoBehaviour
public class Tile
{
// These are intentionally set as read only
public readonly Map map;
public readonly int x;
public readonly int y;
public Tile(Map map, int x, int y)
{
this.map = map;
this.x = x;
this.y = y;
}
// Just an example and everyone love explosions
public void Explode()
{
// alternatively also destroy neighbor tiles
map.SetTile(x, y, TileInfo.Empty);
map.Rebuild();
// play audio
// spawn particles at tile location
}
}
This object should be created by the Map like:
public class Map : MonoBehaviour
{
// ...
// Creates a proxy object, for convenience
public Tile GetTile(int x, int y)
{
return new Tile(this, x, y);
}
// ...
}
If you really want to, you could also create a little system for adding components to your tiles too, using a howngrown system.
In the end, you could do stuff like:
Tile foo = map.GetTile(4, 19);
foo.Explode();
And my examples are very basic, you should probably cache Tile proxies you've created for instance so you don't keep creating new ones with every call to GetTile. Just giving you a skeleton idea to work with.
The idea is though that you have:
One Map, which is the entry point to working with tiles.
Several MapChunks, which the Map manages and are hidden from the rest of the code.
Possibly has utility functions to regenerate the mesh.
Each MapChunk also has a MeshCollider, a Renderer and a MeshFilter and represents n by n tiles.A Tile class, which is an accessible and simple to use class for doing stuff with a Tile.
As you saw, this is just for convenience and you may choose not to have it.
That seems feasible, thanks! I thought the solution would involve 'chunks' of some sort.
Could you give me some pointers on how to implement this? If I'm generating the world at runtime, how do I consolidate the generated tiles into a single chunk? Also, if they're a single GameObject, how would I interact with (or destroy) a single tile?
Some questions about this: 1) If I already have a sprite sheet of all the images used in assembling the map, what steps should be taken to make that into something usable with a $$anonymous$$esh? 2) Ins$$anonymous$$d of altering the $$anonymous$$esh, can I have that created in the scene and then create/modify the Texture2D applied to it at runtime? 3) Extending from the above, if I have ~250 pickups placed in a single 28x31 grid, should each pickup be its own GameObject to completely avoid SetPixels() calls after creating the map?