- Home /
 
How to render a lot of the same small objects
In my game I want to create a pressure plate trap of which the user is able to set at runtime. The player should be able to set the trap on a specific part of a tile. Having every brick of a tile as seperate gameobject can be quite heavy in the amount of batches, in terms of performance this would be bad especially if you have a big floor. So to solve that I want to use a more combined mesh but still be able to raycast for the individual tile parts so they can still be manipulated. So kind of batched for the GPU to render but still able to manipulate them individually using a raycast to identify the tile part. 
 To visualize what I want to create, see the screenshot below. The part marked in red I have a empty parent GO and each tile piece is an individual GO named TilePart. They both already have a trap that is triggered, one of their tiles is slightly pushed down. If I were to duplicate these tiles extensively, my batch count would go up by a lot which will kill performance. 
 On the left side I have a tile, which is 1 single mesh but now I am unable to select the individual tile part using raycasting, because it is no longer a seperate object. 
 So I am quite uncertain how to solve this problem. I kind of don't want to end up with a lot of loose mesh parts because of performance (high batch count, imagine a lot of these duplicated to form a floor). But I do need to be able to select and manipulate parts of a mesh else the user cannot select a specific part to place the trap. 

Edit 1: ok so I instantiated a floor using a prefab with every brick as child. I even used a GPU instanced material to keep the batch count low. But the framerate will still drop after instantiating a lot of them? So I would still need to Mesh Combine to solve that issue. But then I cannot manipulate them individually.
@$$anonymous$$asked$$anonymous$$ouse I generated 40,000 cubes just fine with no lag. All at once of course it will lag. That is why I instantiate them during a load screen. Then I do what I want to the objects I instantiated. I do this by putting them in a public list that I can access using any script. Then I do what I need to do. Can you fill me in on more of what you want so I can come up with a example script that fits your needs?
Of course the generation isn't the problem, but rather having them all on screen is. With GPU Instancing I kept the batches low, like 70 batches or so, still having them all on screen lags.
Basically what I want to do is create a large floor of individual bricks. The "runtime editor" player who looks over the whole place, should be able to select one of the bricks on the floor and change it into a trap. Once the "runner" player is walking over such a brick it should move down a little bit (cause you stepped on it, activating the trap).
As I have in the screenshot, I want to use the right side setup where I can manipulate each individual bricks. The left hand side is 1 single mesh, I cannot manipulate individual bricks (orange). The right side prefab would lag, the left side prefab would not do so as quickly. Even though both ways, they both use GPU Instancing, having a low batch count, low set pass count. The merged version outperforms the separated version.
$$anonymous$$aybe another solution would be, being less precise. Using the left side prefab but use the whole tile as a trap rather than individual bricks. $$anonymous$$aybe I am being too ambiguous :)
Thanks for the replies so far though!
Answer by MrMatthias · Apr 26, 2018 at 12:02 PM
you could use colliders, or other helper objects that don't get rendered
Well I can combine the mesh and use colliders for each part. But then I still have a problem, how am I going to move the mesh down after it is combined?
I should be able to change it there, but I have no clue how. So supposedly the right top tile, each tilePart would not be a meshrenderer but rather only a box collider so I can identify the part of the tile. The parent "Tile" gameobject will then be the mesh renderer, combined of all parts.
But how would I still be able to go from the right bottom tile state to right top tile state. Because when a mesh is combined, I have no control over the individual parts anymore.
What I am kind of searchong for is like.. Give the gpu the combined mesh but still be able to manipulate it in parts. With gpu instancing it doesn’t rlly seem to do that
Answer by KittenSnipes · Apr 27, 2018 at 11:31 AM
@MaskedMouse Here is a huge example:
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.UI;
 
 public class BuildSystem : MonoBehaviour {
 
     //Enum holding the types of possible build modes
     public enum BuildModes
     {
         None,
         Build,
         Edit,
     }
 
     [Header("Key Used To Place Prefabs")]
     public KeyCode buildKey = KeyCode.N;
 
     [Header("Key Used To Edit Placed Prefabs")]
     public KeyCode editKey = KeyCode.M;
 
     [Header("List Of All Placeable Prefabs")]
     public List<GameObject> buildables;
 
     //These two public variables make up my build menu
     public Canvas BuildMenu;
     public Dropdown selector;
 
     //All objects that have been currently placed
     List<BuildObject> buildObjects;
 
     //Currently selected placeable prefab
     GameObject chosenPrefab;
 
     //Reference of the parent object
     Transform parentObj;
 
     //Current build mode
     BuildModes mode = BuildModes.None;
 
     //Boolean variables of our current build statuses
     bool build = false;
     bool edit = false;
     bool currentlyEditing = false;
 
     /// <summary>
     /// Sets current build mode
     /// </summary>
     public void Mode(BuildModes mode)
     {
         //Sets our boolean statuses based on build mode
         switch (mode)
         {
             case BuildModes.None:
                 build = false;
                 edit = false;
                 break;
             case BuildModes.Build:
                 build = true;
                 edit = false;
                 break;
             case BuildModes.Edit:
                 build = false;
                 edit = true;
                 break;
         }
     }
 
     void Start()
     {
         //Clears our lists for a fresh start
         buildObjects = new List<BuildObject>();
         selector.options = new List<Dropdown.OptionData>();
 
         //This fills our build list with all currently buildable prefabs
         for (int i = 0; i < buildables.Count; i++)
         {
             selector.options.Add(new Dropdown.OptionData(buildables[i].name, null));
         }
 
         //This sets our current buildable object to the start of our list
         chosenPrefab = buildables[selector.value];
     }
 
     void Update()
     {
         //If we are pressing the build key
         if (Input.GetKeyDown(buildKey))
         {
             //Set our mode accordingly:
 
             //If we are already in build mode
             if (mode == BuildModes.Build)
             {
                 //Disable it
                 mode = BuildModes.None;
                 Debug.Log("Build Mode Disabled!");
             }
 
             //Else
             else
             {
                 //Enable build mode
                 mode = BuildModes.Build;
                 Debug.Log("Build Mode Enabled!");
             }
         }
 
         //If we are pressing the edit key
         else if (Input.GetKeyDown(editKey))
         {
             //Set our mode accordingly:
 
             //If we are already in edit mode
             if (mode == BuildModes.Edit)
             {
                 //Disable it
                 mode = BuildModes.None;
                 currentlyEditing = false;
                 Debug.Log("Edit Mode Disabled!");
             }
 
             //Else
             else
             {
                 //Enabled edit mode
                 mode = BuildModes.Edit;
                 Debug.Log("Edit Mode Enabled!");
             }
         }
 
         //Set up our booleans
         Mode(mode);
 
         //If editing
         if (edit)
         {
             //If pressing left click
             if (Input.GetMouseButtonDown(0))
             {
                 //If we are not currently editing
                 if (currentlyEditing == false)
                 {
                     //Setup a raycast
                     RaycastHit hit;
 
                     //And a ray from our camera to the clicked mouse position
                     Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
 
                     //And if our raycast hits something
                     if (Physics.Raycast(ray, out hit))
                     {
                         //And the raycasts parent isnt null
                         if (hit.transform.parent != null)
                         {
                             //Set our parent object 
                             parentObj = hit.transform.parent;
                             
                             //If our parent object is a build object
                             if (ContainsBuildObject(parentObj.gameObject))
                             {
                                 //Allow it to be edited
                                 EditMode(parentObj.gameObject);
                             }
                         }
 
                         //If the raycast isnt null
                         else if (hit.transform != null)
                         {
                             //Set it as our parent object
                             parentObj = hit.transform;
 
                             //If our parent object is a build object
                             if (ContainsBuildObject(parentObj.gameObject))
                             {
                                 //Allow it to be edited
                                 EditMode(parentObj.gameObject);
                             }
                         }
 
                         //Else
                         else
                         {
                             //Our parent object doesnt exist and is null
                             parentObj = null;
                         }
                     }
                     //We are editing an object so we are currently editing
                     currentlyEditing = true;
                 }
             }
 
             //If we are currently editing and are pressing right click
             else if (Input.GetMouseButtonDown(1) && currentlyEditing)
             {
                 //If the parent object has children
                 if (GetBuildObjectFromGameObject(parentObj.gameObject).GetChildObjects() != null)
                 {
                     //And the child count is greater than 0
                     if (GetBuildObjectFromGameObject(parentObj.gameObject).GetChildObjects().Count > 0)
                     {
                         //Loop though the child objects
                         for (int j = 0; j < GetBuildObjectFromGameObject(parentObj.gameObject).GetChildObjects().Count; j++)
                         {
                             //Setup a raycast
                             RaycastHit hit;
 
                             //And a ray from our camera to the clicked mouse position
                             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
 
                             //If our raycast hits something
                             if (Physics.Raycast(ray, out hit))
                             {
                                 //If the hit object is a child of the parent
                                 if (hit.transform.gameObject.Equals(GetBuildObjectFromGameObject(parentObj.gameObject).GetChildObjects()[j].GetObject()))
                                 {
                                     //And if it is not a trap
                                     if (GetBuildObjectFromGameObject(parentObj.gameObject).GetChildObjects()[j].GetIsTrap() == false)
                                     {
                                         //Set it as a trap
                                         GetBuildObjectFromGameObject(parentObj.gameObject).GetChildObjects()[j].SetIsTrap(true);
                                         Debug.Log("Trap Set!");
                                     }
 
                                     //Else
                                     else
                                     {
                                         //Disable the trap
                                         GetBuildObjectFromGameObject(parentObj.gameObject).GetChildObjects()[j].SetIsTrap(false);
                                         Debug.Log("Trap Deactivated!");
                                     }
                                 }
                             }
                         }
                     }
                 }
             }
         }
 
         //If we are not editing
         else
         {
             //And we have built objects
             if (buildObjects.Count > 0 && buildObjects != null)
             {
                 //Loop through the built objects
                 for (int i = 0; i < buildObjects.Count; i++)
                 {
                     //If the built objects have children
                     if (buildObjects[i].GetChildObjects() != null)
                     {
                         //And the child count is greater than 0
                         if (buildObjects[i].GetChildObjects().Count > 0)
                         {
                             //Loop through the children
                             for (int j = 0; j < buildObjects[i].GetChildObjects().Count; j++)
                             {
                                 //And parent them back if they were edited
                                 buildObjects[i].GetChildObjects()[j].GetObject().transform.parent = buildObjects[i].GetBuildObject().transform;
                             }
                         }
                     }
                 }
             }
         }
 
         //If we are building
         if (build)
         {
             //Enabled our build menu
             BuildMenu.enabled = true;
 
             //Set up our game object to build based on what has been selected
             chosenPrefab = buildables[selector.value];
 
             //If we are left clicking
             if (Input.GetMouseButtonDown(0))
             {
                 //Setup a raycast
                 RaycastHit hit;
 
                 //And a ray from our camera to the clicked mouse position
                 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
 
                 //If our raycast hit something
                 if (Physics.Raycast(ray, out hit))
                 {
                     //Create an instance of our currently selected buildable prefab at that point clicked
                     GameObject obj = Instantiate(chosenPrefab, hit.point, Quaternion.identity);
 
                     //Set the created prefab as a built object
                     BuildMode(obj);
                 }
             }
         }
 
         //If we are not building
         else
         {
             //Disable the build menu
             BuildMenu.enabled = false;
         }
     }
 
     /// <summary>
     /// Converts given object in to a build object
     /// </summary>
     public void BuildMode(GameObject obj)
     {
         //Reference of new built object
         BuildObject ofb;
 
         //If the built object exists
         if (ContainsBuildObject(obj))
         {
             //We say so
             ofb = GetBuildObjectFromGameObject(obj);
         }
 
         //Else
         else
         {
             //Setup a new built object using the game object
             ofb = new BuildObject(obj);
 
             //Make a new list for the possible children of the built object
             List<BuildObjectChild> objects = new List<BuildObjectChild>();
 
             //Loop through the possible children
             for (int i = 0; i < obj.transform.childCount; i++)
             {
                 //If there is then add it to the list of children of the built object and set them up as normal blocks
                 //Meaning they are not traps
                 objects.Add(new BuildObjectChild(obj.transform.GetChild(i).gameObject, false));
             }
 
             //If the children exist
             if (objects.Count > 0)
             {
                 //Add it to the built object
                 ofb.SetChildObjects(objects);
             }
 
             //Our object is now a built object and will be added to the currently built objects list
             buildObjects.Add(ofb);
         }
     }
 
     /// <summary>
     /// Returns if our object is a build object
     /// </summary>
     bool ContainsBuildObject(GameObject obj)
     {
         //Loop through the currently built objects
         for (int i = 0; i < buildObjects.Count; i++)
         {
             //If it exists
             if (buildObjects[i].GetBuildObject() == obj)
             {
                 //return true
                 return true;
             }
         }
         //Else we return false
         return false;
     }
 
     /// <summary>
     /// Gets a build object from the given game object
     /// </summary>
     BuildObject GetBuildObjectFromGameObject(GameObject obj)
     {
         for (int i = 0; i < buildObjects.Count; i++)
         {
             if (buildObjects[i].GetBuildObject() == obj)
             {
                 return buildObjects[i];
             }
         }
         Debug.LogError("ERROR: The List 'buildObjects' does not contain " + obj + "(Line 167)");
         return null;
     }
 
     /// <summary>
     /// Edit Mode: Detaches child/children of given object for editing
     /// </summary>
     public void EditMode(GameObject obj)
     {
         obj.transform.DetachChildren();
     }
 }
 
 public class BuildObject
 {
     private GameObject buildObj;
     private List<BuildObjectChild> children;
 
     public BuildObject()
     {
         buildObj = null;
         children = null;
     }
 
     public BuildObject(GameObject obj)
     {
         buildObj = obj;
         children = null;
     }
 
     public BuildObject(GameObject obj, List<BuildObjectChild> co)
     {
         buildObj = obj;
         children = co;
     }
 
     /// <summary>
     /// Gets the game object of a build object
     /// </summary>
     public GameObject GetBuildObject()
     {
         return buildObj;
     }
 
     /// <summary>
     /// Sets the game object of a build object
     /// </summary>
     public void SetBuildObject(GameObject obj)
     {
         buildObj = obj;
     }
 
     /// <summary>
     /// Gets the children of a build object
     /// </summary>
     public List<BuildObjectChild> GetChildObjects()
     {
         return children;
     }
 
     /// <summary>
     /// Sets the children of a build object
     /// </summary>
     public void SetChildObjects(List<BuildObjectChild> co)
     {
         children = co;
     }
 }
 
 public class BuildObjectChild
 {
     private bool isTrap;
     private GameObject child;
 
     public BuildObjectChild()
     {
         child = null;
         isTrap = false;
     }
 
     public BuildObjectChild(GameObject obj)
     {
         child = obj;
         isTrap = false;
     }
 
     public BuildObjectChild(GameObject obj, bool status)
     {
         child = obj;
         isTrap = status;
     }
 
     /// <summary>
     /// Gets the gameobject of a build object's child
     /// </summary>
     public GameObject GetObject()
     {
         return child;
     }
 
     /// <summary>
     /// Sets the gameobject of a build object's child
     /// </summary>
     public void SetObject(GameObject obj)
     {
         child = obj;
     }
 
     /// <summary>
     /// Gets the gameobject of a build object's child current trap status
     /// </summary>
     public bool GetIsTrap()
     {
         return isTrap;
     }
 
     /// <summary>
     /// Sets the gameobject of a build object's child current trap status
     /// </summary>
     public void SetIsTrap(bool status)
     {
         isTrap = status;
     }
 }
 
               In order for this script to work you must have a canvas a UI Element attached to it called a Dropdown
@$$anonymous$$ittenSnipes I appreciate the work you've put into this. Like I said generation isn't the problem. I am more trying to tackle the problem of rendering than creation. For creation I'm eventually going to save them as modular prefab pieces. Let the user use that to create his level. I am more worried about rendering while keeping an eye on the level. That level may be quite huge.
So here's two screenshots, one prefab with 1 mesh, one prefab with 5 meshes. Both creating similar floors (slightly different, but neglectable). one is 30 fps, other is 90+
Problem with a merged mesh is, that I don't have control over the individual bricks because the mesh is merged into 1 floor tile, sure I can put a box colliders on it to resemble the tile. But OnCollisionEnter I cannot tell the individual brick to move it's Y value down because it is 1 single mesh.
Problem with individual bricks as a mesh is that the performance is 30 fps where my target would be 60.
What you see here is 80x80 instantiated prefabs. The prefab looks like the one in my original post screenshot. Situation 1 uses the 5 individual bricks as 1 small floor tile, situation 2 uses 1 merged mesh.
Situation 1: 
Situation 2: 
Would be nice if I could tell the GPU like, here have some gameobjects, they're all the same mesh, all the same material, render it in 1 go. I thought that's what GPU Instancing was doing but apparently having a lot of individual bricks it brings the FPS down to 30. $$anonymous$$erging that mesh brings the FPS up to 90 which is nice but I can't tell the individual brick anymore to move it's Y value because they're merged into 1 tile.
I've looked at Graphics.Draw$$anonymous$$eshInstanced, not sure if thats the way to go. Or just keep them as the single mesh merge and use that as trap, but lose the fiddlety of choosing an individual brick that is part of 1 tile as trap.
$$anonymous$$aybe the original question wasn't clear I'll update the title.
@$$anonymous$$asked$$anonymous$$ouse
I will figure out a way. I would like to know for my own learning
@$$anonymous$$asked$$anonymous$$ouse
I edited my answer. When editing it will take apart one big prefab and access it's children so you can make them traps. Then when it is all done it will turn in to one big prefab again.
Your answer
 
             Follow this Question
Related Questions
Mesh deformation with collisions 2 Answers
How to check if a raycast is not hitting any tagged colliders? 0 Answers
How do i make an object move around a 3D mesh? 1 Answer
Using raycast for visible bullet collision (not oncollisionenter) in C# 1 Answer
What's a reliable way to detect if an object is no longer being hit by a ray? 1 Answer