Prefab Instantiate performance improvment - c#

so I have a multiple category buttons in every category there is more than 20 items. So make prefab and instantiate when button is pressed. The problem is When I instantiate prefab I have to initialize it with info like name, sprite, btn click event etc. this will take time to instantiate prefabs and game hangs. Here is code
for (int i = 0; i < prefab.Length; i++)
{
GameObject go = Instantiate(basePrefab) as GameObject;
go.SetActive(true);
go.transform.SetParent(prefabParent, false);
go.transform.localScale = Vector3.one;
go.GetComponent<Category>().Init(prefab[i]);
}
This code is called in button click. Alternatively to achieve some performance in start method I already instantiate some basePrefab on a empty gameobject and on button click just change parent and initialize them. this approach will give some benefits but still game is hangs for 2 sec here is that code
for (int i = 0; i < prefab.Length; i++)
{
GameObject go = InstantiatedGameObject[i];
go.SetActive(true);
go.transform.SetParent(prefabParent, false);
go.transform.localScale = Vector3.one;
go.GetComponent<Category>().Init(prefab[i]);
}
Any solution to improve performance ??
So Here is About prefab contenet: Basically prefab contains Background image, Mask image in this image Catgory image, Selection image, and
a Text. In init function the category image change and Text value change Also Background contains button in init set this button's click event

If you know maximum quantity of categories and category items you don't have to instantiate them. You can use slots that you activate and deactivate based on the amount of items. During activation you can also bind and unbind them to objects which you can use to assign correct sprites and texts to your button elements.
If you don't know the quantity of category items in advance you can still do the same but using pagination which can be preferable from user perspective as well if there's a lot of items.
public class SomeUI : MonoBehavior
{
public GameObject categorySlotsParent;
public GameObject categoryItemSlotsParent;
public Category[] categories;
CategorySlot[] categorySlots;
CategoryItemSlot[] categoryItemSlots;
public void Awake(){
categorySlots = GetComponentsInChildren<CategorySlot>();
categoryItemSlots = GetComponentsInChildren<CategoryItemSlot>();
AssignCategories(categories);
if(categories.Length > 0 && categories[0] != null)
SelectCategory(categories[0]);
}
void AssignCategories(Category[] categories){
for(int i = 0; i < categorySlots.Length; i++){
if(i < categories.Length)
{
categorySlots[i].SetActive(true);
categorySlots[i].AssignCategory(categories[i]);
}
else
{
categorySlots[i].SetActive(false);
categorySlots[i].AssignCategory(null);
}
}
}
void AssignCategoryItems(CategoryItem[] categoryItems){
for(int i = 0; i < categorySlots.Length; i++)
{
if(i < categoryItems.Length)
{
categoryItemSlots[i].SetActive(true);
categoryItemSlots[i].AssignCategoryItem(categoryItems[i]);
}
else
{
categoryItemSlots[i].SetActive(false);
categoryItemSlots[i].AssignCategoryItem(null);
}
}
}
void SelectCategory(Category category){
AssignCategoryItems(category.categoryItems);
}
}

If your instantiated object is a common object , you can improved it pooling the prefab avoiding to instantiate & destroy this object each time. You have a good example of workflow here

Related

Instantiate a Object below another object instead of above

First of all, i created a GIF to show what is currently happen.
GIF with my current problem
and
Awhat I want
I have a List of GameObject which add the bodyParts temp and Instantiate it in the correct time and position.
Now this is working like expected, but i want this new bodyParts below another object instead of above.
As you can see the Head is "under" the new body parts, but it should always on Top and every new part should spawn under the next. (only should looks like! I dont want to change the Z position.)
i tried :
bodyParts.transform.SetAsFirstSibling();
to change the Hierarchy, but this do nothing. I also can drag and drop the Clones to a other position in Hierarchy but they just stay at the same position (above another).
Is this possible and what should i have to do?
Here some of my Code which makes the process:
private void CreateBodyParts()
{
if (snakeBody.Count == 0)
{
GameObject temp1 = Instantiate(bodyParts[0], transform.position, transform.rotation, transform);
if (!temp1.GetComponent<MarkerManager>())
temp1.AddComponent<MarkerManager>();
if (!temp1.GetComponent<Rigidbody2D>())
{
temp1.AddComponent<Rigidbody2D>();
temp1.GetComponent<Rigidbody2D>().gravityScale = 0;
}
snakeBody.Add(temp1);
bodyParts.RemoveAt(0);
}
MarkerManager markM = snakeBody[snakeBody.Count - 1].GetComponent<MarkerManager>();
if (countUp == 0)
{
markM.ClearMarkerList();
}
countUp += Time.deltaTime;
if (countUp >= distanceBetween)
{
GameObject temp = Instantiate(bodyParts[0], markM.markerList[0].position, markM.markerList[0].rotation, transform);
if (!temp.GetComponent<MarkerManager>())
temp.AddComponent<MarkerManager>();
if (!temp.GetComponent<Rigidbody2D>())
{
temp.AddComponent<Rigidbody2D>();
temp.GetComponent<Rigidbody2D>().gravityScale = 0;
}
snakeBody.Add(temp);
bodyParts.RemoveAt(0);
temp.GetComponent<MarkerManager>().ClearMarkerList();
countUp = 0;
}
}
Finally i found the working Solution.
It has nothing to do with which hierarchy order GameObjects spawn in.
Just the Layer and the LayerOrder are responsible for it.
So I give my parent object a specific layer name (manually in the inspector under "Additional Settings" or programmatically)
I chose the programmatic way...
Any newly spawned GameObject that is Child would get a lower number
yourGameObject.GetComponent<Renderer>().sortingLayerID = SortingLayer.NameToID("Player");
yourGameObject.GetComponent<Renderer>().sortingOrder = -snakeBody.Count;

How can I create instances of a unity class in order to have multiple instances of the class running simultaneously w/o interrupting each other

I am working on a city builder game and trying to create a crop growing system. This issue is with 2 different scripts, a placement manager and a farm timer. The placement manager class detects mousedown events and will setTile to my farm tilemap based off of the click location and the selected tile. The farm timer class contains a method which is called when a farm tile is set in the placement manager which then starts the timer using the update method to countdown from 60.
3 if statements check for 45, 30, and 15 seconds and calls another method in the placement manager which will set the tile to its respective growth stage tile. This works when only placing a single crop, but when there are multiple it starts the timer over with whichever tile was placed last.
I am guessing I need to find a way to create new instances of the class but I am also wondering if there would be a better way???
PlacementManager.cs
public void Update()
{
if (Input.GetMouseButtonDown(0) && isIndexSet)
{
Vector3 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Location = farmGround.WorldToCell(pos);
Location.z = 0;
//6 is a carrot crop
if(curIndex == 6 && isSpaceOpen())
{
PlacePlant();
CurrencyManager.Instance.currency -= 1;
//FarmTimer.instance.PlantPlaced("Carrot", Location);
timer.PlantPlaced("Carrot", Location);
}
if(curIndex < 6 && isSpaceOpen())
{
PlaceBuilding();
CurrencyManager.Instance.currency -= 5;
}
}
}
public void ChangePlantTile(int index, Vector3Int selectedLocation)
{
farmPlants.SetTile(selectedLocation, plants[index]);
}
FarmTimer.cs
private string carrotChk1 = "Farm_36 (UnityEngine.Tilemaps.Tile)";
private string carrotChk2 = "Farm_35 (UnityEngine.Tilemaps.Tile)";
private string carrotChk3 = "Farm_34 (UnityEngine.Tilemaps.Tile)";
private int curIndex;
private bool isIndexSet = false;
public Tile[] tiles;
public Tile[] plants;
void Update()
{
if (isTimerActive == true)
{
if(plant == "Carrot")
{
CarrotTimer();
}
}
}
public void CarrotTimer()
{
if (m_time < 45f && m_time > 30f
&& FarmMap.GetTile(selectedLocation).ToString() != carrotChk1)
{
curIndex = 0;
PlacementManager.Instance.ChangePlantTile(curIndex, selectedLocation);
curIndex++;
}
else if (m_time < 15f && m_time > 1
&& FarmMap.GetTile(selectedLocation).ToString() != carrotChk2)
{
PlacementManager.Instance.ChangePlantTile(curIndex, selectedLocation);
curIndex++;
}
else if (m_time < 1f
&& FarmMap.GetTile(selectedLocation).ToString() != carrotChk3)
{
PlacementManager.Instance.ChangePlantTile(curIndex, selectedLocation);
curIndex = 0;
isTimerActive = false;
}
}
For Testing purposes I am checking for the index of the carrot crop but I have 5 other crops which I plan to create the same functionality with.
Since i can't comment... i will write here directly. If you don't like the answer just comment it i will delete it.
I think the only way you can do this is either:
create a class and store yout imer there.
create a scriptable object
maybe use some sort of 2D list in your farmTimer class. This List could contain a reference to the cell you planted, and an int variable to store the timer of this cell.
maybe you can use an interface.
I never used the 3rd method in unity.
For me the best option is either 1 or 2. The class would be easier to manage.
I hope it answer your question, have a great day !

RaycastHit to work only when hitting collider

I am making find a difference game. I made a ray that hits an image with collider and changes certain text property. Problem is that where ever i touch the screen it detects a ray, and changes the text. How can i solve that, i also unchecked Raycast Target for all other objects on screen.
public List<GameObject> hiddenObjects;
public Text test;
public Text click;
GameObject[] objekti;
private RaycastHit2D result;
// Start is called before the first frame update
void Start()
{
//This is for a list that will fill with GameObjects
objects= GameObject.FindGameObjectsWithTag("Slike");
hiddenObjects = new List<GameObject>();
for (int i = 0; i < objects.Length; i++)
{
hiddenObjects.Add(objects[i]);
}
}
// Update is called once per frame
void Update()
{
if (Input.touchCount > 0)
{
Vector2 camRay = Camera.main.ScreenToWorldPoint(Input.GetTouch(0).position);
result = Physics2D.Raycast(camRay, (Input.GetTouch(0).position));
if (result.collider.gameObject)
{
click.text = "CLICK";
}
//This is supposed to delete 1 object at time as it is clicked on
for (int j = 0; j < hiddenObjects.Count; j++)
{
if (hiddenObjects[j] = null)
{
hiddenObjects.RemoveAt(j);
}
}
//Simple if statement if list is empty, print "Win"
if (hiddenObjects.Count == 0)
{
test.text = "WIN";
}
}
}
From the information you have provided. I think in those code:
if (result.collider.gameObject)
{
click.text = "CLICK";
}
You need to identify the object that has been hit either by the name property or by comparing a tag.
Let's do it by name for simplicity.
In the left hand side menu in Unity A.K.A "Hierarchy" let's say the GameObject is called "ObjectToDetectHitOn" you would change the above code to:
if (result.collider.gameObject.name.Equals("ObjectToDetectHitOn"))
{
click.text = "CLICK";
}
This will not work if you Instantiate gameobject in code because they will then have (Clone) added to the end of the name. In this scenario you would have to use the .Contains() method of the String class.
The preferred method would be to actually add a tag to the GameObject, let's say you created a tag with the name "ObjectToDetectRay" and use the code like this:
if (result.collider.gameObject.CompareTag("ObjectToDetectRay"))
{
click.text = "CLICK";
}
Let me know if this doesn't work or if it is unclear and I will try to explain further.

How to get the value of gameobject array from one script to another?

I'm new in c# and arcore. I have an example controller script called GUIController.cs and another list of item call ItemScrollList.cs. I made a gameobject with arrays in GUIController with 2 prefabs element 0 and 1.. What I'm trying to achieve is, Everytime I click on the content of itemscrollList (in this case 2 items that is item1 and item2) I need to change the prefab on GUIController.
The errors I got from the codes below is everytime I click on content item 1 or 2 they return nullpointerexceptions.
The line on GUIController.cs "AndyPrefab = nowPrefabs.GetComponent().currentPrefabs;" is the line I need it to show the prefab properly. Please help me
GUIController.cs
if (hit.Trackable is FeaturePoint)
{
AndyPrefab = AndyPlanePrefab[0];
}
else
{
//this is manual input value AndyPlanePrefab as array 1
//AndyPrefab = AndyPlanePrefab[1];
//this is where i need it to properly show the correct prefab
AndyPrefab = nowPrefabs.GetComponent<ItemScrollList>().currentPrefabs;
}
ItemScrollList.cs
//this is where i create the button for itemlist
private void AddButtons()
{
for (int i = 0; i < itemList.Count; i++)
{
Item item = itemList[i];
GameObject newButton = buttonObjectPool.GetObject();
newButton.transform.SetParent(contentPanel);
ItemButton itemButton = newButton.GetComponent();
itemButton.Setup(item, this);
}
}
//this is where im trying to change the prefabs and connect it to
GUIController, if i click on item 1 it should change the gameobject[] value
to AndyPrefab.
public void TryToChangePrefabs(Item item)
{
if (item == itemList[0]) {
currentPrefabs = changePrefabs.GetComponent().AndyPlanePrefab[0];
Debug.Log("Condition number 1 done");
}
else if (item == itemList[1])
{
currentPrefabs = changePrefabs.GetComponent().AndyPlanePrefab[1];
Debug.Log("Condition number 2 done");
}
}
You need to create a gameobject variable in the script you want to access the other script from and drag and drop the gameobject with the script you want to access into the gameobject variable to access it you must do this
mygameobjectvariable.getComponent<myscriptname>().myarray[1]

How to display list in gui in runtime

I'm trying to create a search interface similar to Facebook's. That is, you type in all or part of a name, and the matches are displayed in a list below.
I know how to extract the input from the InputField (SearchBar) but I don't know how to display the matching results in the panel below during runtime.
Create a new label/button for each match and append to... the panel?
What container should I use?
How do I actually "add/append"?
Any help would be much appreciated.
Here is my scene:
And here is my code:
using UnityEngine;
using UnityEngine.UI;
using System;
public class SearchScript : MonoBehaviour {
public InputField SearchBar;
public GameObject Panel;
public List<String> myList;
public void Start() {
myList = new List <String>();
myList.Add("Andre");
myList.Add("Angela");
myList.Add("Temi");
myList.Add("Tupac");
myList.Add("Graham");
myList.Add("Grandpa");
myList.Add("Michael");
myList.Add("Miguel");
SearchBar.onValueChanged.AddListener(delegate {ValueChangeCheck(myList); });
}
public void ValueChangeCheck(List<string> myList) {
string contents = SearchBar.text;
List<String> outList = new List <String> ();
for (int i = 0; i < myList.Count; i++) {
if (myList [i].Contains (contents)) {
outList.Add (myList [i]);
}
}
for (int i = 0; i < outList.Count; i++) {
>>HELP<<
}
}
}
This page in the Unity manual describes what you'll need to do in general terms. The first thing you'll want to do is (in the editor) make a blank button/label or whatever it is you want your final product to look like. For this example, I'm going to act like it's going to be a button. Once you've got the button looking the way you want, set the position to (0,0) and make it a prefab. Make a public field in your MonoBehaviour and drag the prefab into it in the editor. That will look like this:
public GameObject ButtonPrefab;
Next, in your loop, you'll need to instantiate each button, making sure that you parent the new object to your canvas (which is your SearchBar's parent, so we have an easy way to get at it). Then assign your text and shift it down and you'll be golden!
for (int i = 0; i < outList.Count; i++) {
var newButton = Instantiate(ButtonPrefab,SearchBar.transform.parent,false) as GameObject;
// This assumes you have a Text component in your prefab
newButton.GetComponent<Text>().text = outList[i];
// You'll just have to experiment to find the value that works for you here
newButton.transform.position += Vector2.down * 20 * (i + 1);
}

Categories