A little help with draw calls if you please.

So I am re-tooling a “rogue like” into a random dungeon generating game, a very fun project. I have access to a type enum representation of a game map, like tiles, and monsters, and player. I take that information and draw out unity3d in simple cubes, scaled * 10. This is all working as intended. The issue is that the frames are extremely low. I have seen many of those Cube World type and they have a ton more cubes drawn then I do. So my question is how do I get the draw calls down. I have a little over 150 cubes being drawn at a time currently. I need to get that low so I can start spawning a ton of monsters. I do handle everything internally, like the maps, gameObjects, Materials, ect. Any one have an approach to take? Here is my main code, this is the dungeon manager, its job is to know about all of the dungeons structures, and initiate objects.

    void Awake()
    {
        //start rogue like
        SlashNetGame.Initialise();

        //tell tiler where to start
        StartingTileLocation = Vector3.zero;

        //assign the rogue like game
        SlashNetGame game = SlashNetGame.NewGame(DungeonStyle.Hack, 20, 80, 80);

         // set local
        _SNDungeon = game.Dungeon;

        // custom loader for game objects and materials by enum type.
        LoadTypesAsGameObjects<TileType>(ref TileGameObjects, ref _tileMaterials);
        LoadTypesAsGameObjects<MonsterClass>(ref SpawnPointGameObjects, ref _spawnPointMaterials);

        // load the player
        LoadPlayer(ref Player);

        // populate the dungeon, and set up the unity locations
        Populate(DungeonContainer, SpawnpointContainer);

        // dont need _SNDungeon, null it.
        _SNDungeon = null;
    }

    private void LoadPlayer(ref GameObject Player)
    {
        Debug.Log("Loading Player");
        
        Player = Resources.Load("Prefabs/Player") as GameObject;
        if (Player == null)
        {
            Debug.LogError("Player did not load");
            return;
        }
    }

    // send in the enum type you want loaded, a referance to their gameObject arrary, and a refrance to their              //materials   array see the paths below.
    private void LoadTypesAsGameObjects<T>(ref GameObject[] gameObjects, ref Material[] materials)
    {
        // Initialize the arrays
        Type t = typeof(T);
        if (!t.IsEnum)
            Debug.LogError("Type is not enum");

        var names = Enum.GetNames(t);
        var values = Enum.GetValues(t);

        gameObjects = new GameObject[values.Length];
        materials = new Material[values.Length];

        // load the game object, and apply the correct material
        for (int i = 0; i < values.Length; i++)
        {
            Debug.Log("Loading " + names[i].ToString());
            
            // notice the path is Resources/Prefabs/ (the name of the gameobject, so name it the same as the enum)
            gameObjects[i] = Resources.Load("Prefabs/" + names[i].ToString()) as GameObject;
            gameObjects[i].AddComponent<Rigidbody>();
            gameObjects[i].rigidbody.isKinematic = true;
        
            // notice the path is Resources/Graphics/Materials/ (the name of the gameobject, so name it the same as the enum)
            materials[i] = Resources.Load("Graphics/Materials/" + names[i].ToString()) as Material;

            if (gameObjects[i] == null || materials[i] == null)
            {
                Debug.LogError(names[i].ToString() + " Failed to load");

                return;
            }
            else
            {
                // apply material and name
                gameObjects[i].renderer.material = materials[i];
                gameObjects[i].name = names[i].ToString();
            }

        }
    }
 
    // now load the dungeon, and their pointers
    void Populate(GameObject dungeon, GameObject spawnPoints)
    {
        dungeon = new GameObject();
        dungeon.name = "Dungeon";

        spawnPoints = new GameObject();
        spawnPoints.name = "Spawn Points";

        for (int j = 0; j < GenericDungeon.HEIGHT; j++)
        {
            for (int i = 0; i < GenericDungeon.WIDTH; i++)
            {
                if (SlashNetGame.Instance.PCPosition.X == i  SlashNetGame.Instance.PCPosition.Y == j)
                {
                    (Instantiate(Player, GetTilePosition(i, j), Quaternion.identity) as GameObject).transform.position += Vector3.up * 11f;
                }
                //Debug.Log((int)_Dungeon[i, j].Type);

                (Instantiate(TileGameObjects[(int)_SNDungeon[i, j].Type], GetTilePosition(i, j), Quaternion.identity) as GameObject).transform.parent = dungeon.transform;
        
                if (_SNDungeon[i, j].Monster != null)
                {
                    GameObject temp = Instantiate(SpawnPointGameObjects[(int)_SNDungeon[i, j].Type], GetTilePosition(i, j), Quaternion.identity) as GameObject;
                    temp.transform.position += Vector3.up * 1f;
                    temp.transform.parent = spawnPoints.transform;

                }
            }
        }

        
    }

    private Vector3 GetTilePosition(int j, int i)
    {
        return new Vector3(StartingTileLocation.x + (i * _tileWidth), StartingTileLocation.y, StartingTileLocation.z + (j * _tileWidth));
    }

Games like Minecraft use single objects for multiple cubes. They can get away with it because they are using very simple graphics. So say you have 150 cubes, and 40 of them have the same texture. Simply make a single object with 40 cubes and 1 texture. Very fast draw call wise.

I do not know if the method works for what you are intending. You can also increase the artwork and texture space by limiting the field of view. If you always run 3/4 down view, then you can get alot more graphical information without increasing the draw calls near as much. (i.e. Dungeon Siege.)

That does make since, the dungeon is created out of about 11 unique cube types. I am not entirely clear on how I would make a single object with 40 cubes. I do parent all of the cube objects to one master object, but they are not separated by cube type. Is this something that can be done dynamically? Can you give me a brief pseudo example?

OK, the best way to explain it is to start empty. Lets say you want to add a cube to your world. You have the texture you want to use, all you really need to to “gridify” your cube and create the geometry based on the cubes placement. Since each object is at world zero all your values are based in real world positions. The only thing you then have to do is make sure that you use the grid size in your geometry.

This is a simple version. I am sure there will be kinks along the way.
Pseudo code:

class cube{
	texture : int
	position : Vector3
	
	function cube(tex, pos, gridSize){
		texture=tex
		x=Mathf.Round(pos.x / gridSize) * gridSize
		y=Mathf.Round(pos.y / gridSize) * gridSize
		z=Mathf.Round(pos.z / gridSize) * gridSize
		position=new Vector3(x,y,z)
	}
}

gridSize=10;
textures : Texture2D[] // fill the array with textures
private cubes=Array() //fill the array with cubes
private cubeObjects : GameObject[] // physical game objects
private cubeCounts=int[] // counts for each type of cube


// make sure there is an object for each texture
cubeObjects=new GameObject[number of textures]
cubeCounts=new int[number of textures]
for(i=0 to number of textures){ // note we are going backwards
	go=new GameObject()
	go.add component meshfilter
	go.add component meshrenderer
	go.renderer.material=new material(standard shader)
	go.renderer.material.mainTexture=textures[i]
	cubeObjects[i]=go
	cubeCounts[i]=0
}

function GetTextureNumber(texture){
	for(i=0 to number of textures){
		if(texture=textures[i]) return i
	}
	return -1
}

function AddCube(texture, position){
	n=GetTextureNumber(texture)
	if(n=-1) return
	cubes.Add(new cube(n, position, gridSize))
	cubeCounts[n]++
	CreateCubeGeometry(n)
}

function RemoveCube(position){
	for(i=0 to number of cubes){
		if(cubes[i].position=position){
			n=GetTextureNumber(texture)
			if(n==-1) return
			cubes.RemoveAt(i)
			cubeCounts[n]--
			CreateCubeGeometry(n)
			return;
		}
	}
}

function CreateCubeGeometry(n){ // n is the texture number
	vertices=new Vector3[cubeCounts[n] * 24]
	uvs=new Vector2[cubeCounts[n] * 24]
	triangles=new int[cubeCounts[n] * 12]

	ccube=0
	for(i=0 to number of cubes){
		if cube.texture=texture[i][0]{
			vertices[ccube * 24 + 0]=cube[number].position + new Vector3(1,1,0) * (gridSize / 2)
			...
			
			uv[ccube * 24 + 0]=new Vector3(1,0)
			...
			
			triangles[ccube * 12 + 0]=ccube * 24 + 0
			...
			
			ccube++
		}
	}
	mesh.vertices=vertices
	mesh.uv=uvs
	mesh.triangles=triangles
	cubeObjects[i].mesh=mesh
}

Sweet, thanks for the pseudo, I will get to working on it tonight when I get home. I figure that combined with a Field of view limiter, and I will be able to spawn a ton of baddies.

BMB, I love how your pseudo is actually like 90% of the work done :slight_smile: I really wish you used spaces around you equals signs, though. That always bothers me in your code, lol.

That went very well, I got my draw calls down to 27, not to bad, considering the different materials. Thanks again for the help.