2D Efficiency With Sprites and Game Objects

I’m generating a map that uses game objects containing sprites as the base of the map. This is an isometric tile system. Based on research and checking the stats I can see that if sprites are not in view they are automatically hidden/not drawn. However, the encapsulating Game Objects still exist. My question is, if I have, let’s say, 10,000 tiles. At most you might see 100 drawn to the screen, but all 10,000 encapsulating objects would exist. Over time (if let’s say we expand out to >100,000 tiles) would these encapsulating game objects create a slowdown? Or do they exist solely as memory footprints for when a sprite needs to be rendered?

Well I would avoid so many game objects, they will add to memory usage and probably will also crawl the dynamic batching, consider that dynamic batching have to check what is in sight and what not, and may do it on many objects at once.

I tried too using individual sprites objects for a tile system (topdown), on a 128x128 grid, wasn’t that happy with performance so I opted for another method, generating meshes in cunks of 16x16 as single gameobjects, so a total of 8x8 objects, and fps sky rocket to the sky. Surely doing that way you lose the ability to use sprite renders, but as I was reading on some post the next update of unity should bring the ability to tile single sprites so it can save you some gameobjects.

My suggestion is to try your method first, see how it performs and in case look for an alternative.

I don’t really know the answer but you can easily test this :slight_smile: Make a new scene, add a new script to camera and in the start function of this script you do something like:

void Start()
{
    for(int i = 0; i < 100000; i++)
    {
        new GameObject("Test");
    }
}

Just to see what happens when you there are a lot of objects in your scene.

You could try OnBecameVisible and OnBecameInvisible as well as object pooling with instantiating them.

Yes, because they all have to be checked to see if they should be culled. Also Unity can’t really handle that many GameObjects.

–Eric

Alright thanks, I have a chunking system in mind for fixing the memory issue, but I’m glad for the information.

@Neurological
I’m trying exactly to do what you specify (“…, generating meshes in chunks of 16x16 as single gameobjects, …” ) but I’m rather clueless on how to do this. Can you maybe explain it in a bit more detail on how to combine lets say 2 sprites as a single gameobject?

Well for first I don’t combine sprites, you can’t combine sprite renderer, I use procedurally generated meshes, simple planes. I use a 2D array as reference for where to start adding the vertices.

So all I do is to add vertices, uvs and triangles to three Lists, then add all of them to a Mesh component. I use some static functions to go trough a loop and read a text file from where the 2d array is. Is a bit complicated for me to explain, I just share a part of my code so you can take a look:

using UnityEngine;
using System.IO;
using System.Collections.Generic;

public class UDData {

	static private List<Vector3> verts = new List<Vector3>();	//List of vertices needed for the mesh
	static private List<Vector2> uvs = new List<Vector2>();		//List of uvs needed for the mesh
	static private List<int> tris = new List<int>();			//List of triangles needed for the mesh
	static private Vector4 uvOffset;							//UV offset calculated from the 2D array ints
	
	static private int cCount = 8;								//Number of cells
	static private int cSize = 16;								//Cell size (16*16)
	
	static private int tWidth = 128;							//TEMP texture width
	static private int tHeight = 128;							//TEMP texture height
	static private float tSize = 16;							//TEMP base tile size (16*16 pixels)
	
	// Build geometry of each cell
	static public void CreateCellMesh (string mPath, string ID, Transform parent) {
		verts.Clear (); uvs.Clear (); tris.Clear ();
		
		int[,] cGrid = ReadGridData (mPath, ID, cSize);
		
		Vector3 p = Vector3.zero;
		int i = 0;
		
		for(int x = 0; x < cSize; x++) {
			for(int y = 0; y < cSize; y++) {
				p = new Vector3(x * 1 + 0.5f,y * 1 + 0.5f, 0);
				
				if(cGrid[y, x] != 0) {
					
					verts.Add (new Vector3(-0.5f + p.x, -0.5f + p.y, p.z));
					verts.Add (new Vector3(-0.5f + p.x, 0.5f + p.y,  p.z));
					verts.Add (new Vector3 (0.5f + p.x, 0.5f + p.y, p.z));
					verts.Add (new Vector3( 0.5f + p.x, -0.5f + p.y, p.z));
					
					
					Vector4 uvOffset = new Vector4(
						((cGrid[y, x]) * tSize)  / tWidth,
						((cGrid[y, x]) * tSize) / tHeight,
						tSize / tWidth,
						tSize / tHeight
						);
					
					uvs.Add (new Vector2 (uvOffset.x - uvOffset.z, 1f - uvOffset.w )); //0, 0
					uvs.Add (new Vector2 (uvOffset.x - uvOffset.z, 1f )); //0, 1	
					uvs.Add (new Vector2 (uvOffset.x, 1f)); //1, 1
					uvs.Add (new Vector2 (uvOffset.x, 1f - uvOffset.w)); //1, 0
					
					Debug.Log ("0.0 = " + uvs[0] + " 0.1 = " + uvs[1] + " 1.1 = " + uvs[2] + " 1.0 = " + uvs[3]); 
					
					/*
					uvs.Add (new Vector2 (0,0 )); //0, 0
					uvs.Add (new Vector2 (0,1 )); //0, 1	
					uvs.Add (new Vector2 (1,1)); //1, 1
					uvs.Add (new Vector2 (1,0)); //1, 0
					*/
					
					tris.Add (i);
					tris.Add (i + 1);
					tris.Add (i + 2);
					tris.Add (i);
					tris.Add (i + 2);
					tris.Add (i + 3);
					
					i += 4;
				}
			}
		}
		
		Vector3[] vertices = verts.ToArray ();
		Vector2[] uv = uvs.ToArray ();
		int[] triangles = tris.ToArray ();
		
		if(vertices.Length != 0 || uv.Length != 0 || triangles.Length != 0) {
			
			GameObject o = new GameObject (ID);
			
			Mesh mesh = new Mesh ();
			
			if (!o.GetComponent<MeshFilter> ())
				o.AddComponent<MeshFilter> ();
			
			mesh = o.GetComponent<MeshFilter> ().mesh;
			
			mesh.vertices = vertices;
			mesh.uv = uv;
			mesh.triangles = triangles;
			
			if (!o.GetComponent<MeshRenderer> ())
				o.AddComponent<MeshRenderer> ();
			
			MeshRenderer mr = o.GetComponent<MeshRenderer> ();
			mr.castShadows = mr.receiveShadows = false;
			
			mesh.RecalculateBounds ();
			
			o.transform.parent = parent;
		}
	}

	//Return a 2D array of ints
	static private int[,] ReadGridData (string fPath, string id, int size) {
		string data = NMPath.mPath + fPath;
		int[,] a = new int[size,size];

		using (StreamReader sr = new StreamReader(data)) {
			string line;

			int[] aInt = new int[size * size];

			while ((line = sr.ReadLine()) != null) {

				if(line.Contains(id + "_GRID")) {
					string[] fLine = line.Split('=');

					string[] iString = fLine[1].Split (',');
				
					for(int i = 0; i < aInt.Length ; i++) 
						aInt[i] = int.Parse(iString[i].Trim(' '));
				}

				int j = 0;
				for (int x = 0; x < size; x++){
					for (int y = 0; y < size; y++){
						a[x,y] = aInt[j];
						j++;
					}
				}
			}
		}	
		return a;
	}
}

Is not self explanatory enough I guess.

1 Like

I really think the same as your algorithm. I was going to try your code but

string data = NMPath.mPath + fPath;

the “NMPath.mPath” is missing ? Can you please explain more about this :smile: Thanks !

Thats the path to the file I read data from.