Unity Reusing Ui element in list - c#

I have ran into a sort of a problem in my project, I am creating a Scroll listview to show all elements present in list in screen.
I am using button in panel to show list.
Now When I call ShowList() it shows elements in the list.
But if i add some objects to list and again call ShowList() then it also make clone of previous object as instantiated objects are present.
To solve this I am deleting clones using Destroy() but when list contain too many item (300~400) deleting them cause lag in the game . How can I create object pool for ui button or just deactivate them.
public class two : MonoBehaviour {
public GameObject Button_Template;
private List<GameName> gm = new List<GameName>();
public void Exit()
{
var og = GameObject.FindGameObjectsWithTag("clone");
for (int i = 0; i < og.Length; i++)
{
Destroy(og[i]);
}
}
void Start()
{ gm.Add(new GameName("1"));
gm.Add(new GameName("2"));
gm.Add(new GameName("3"));
gm.Add(new GameName("4"));
}
public void ShowList()
{
for (int i = 0 ; i < gm.Count; i++)
{
GameObject go = Instantiate(Button_Template) as GameObject;
go.SetActive(true);
one TB = go.GetComponent<one>();
TB.SetName(gm[i].GName);
go.transform.SetParent(Button_Template.transform.parent);
go.tag = "clone";
}
}
}

List can be used to implement Object Pooling. Simply disable the GameObject when you are done using it. Re-enable it again if you want to use it again. The simple GameObject Pool script below re-implements the Instantiate and Destroy functions.
public class BUTTONPOOL : MonoBehaviour
{
GameObject buttonPrefab;
List<GameObject> buttonPool;
bool ready = false;
public void createButtonPool(GameObject buttonPrefab, int amount = 400)
{
if (ready)
{
Debug.LogError("createButtonPool can only be called once");
return;
}
this.buttonPrefab = buttonPrefab;
//Create 400 Buttons
buttonPool = new List<GameObject>();
for (int i = 0; i < amount; i++)
{
buttonPool.Add(Instantiate(this.buttonPrefab) as GameObject);
}
ready = true;
}
//Creates/Enables single Button
public GameObject Instantiate()
{
if (!ready)
{
showError();
return null;
}
//Return any Button that is not active
for (int i = 0; i < buttonPool.Count; i++)
{
if (!buttonPool[i].activeSelf)
{
return buttonPool[i];
}
}
//Create new Button if there is none available in the pool. Add it to the list then return it
GameObject tempButton = Instantiate(this.buttonPrefab) as GameObject;
buttonPool.Add(tempButton);
return tempButton;
}
//Destroys/Disables single Button
public void Destroy(GameObject button)
{
if (!ready)
{
showError();
return;
}
button.SetActive(false);
}
//Destroys/Disables all Buttons
public void DestroyAll()
{
if (!ready)
{
showError();
return;
}
for (int i = 0; i < buttonPool.Count; i++)
{
if (buttonPool[i].activeSelf)
{
buttonPool[i].SetActive(false);
}
}
}
private void showError()
{
Debug.LogError("createButtonPool must be called once before any other function can be called");
}
}
Usage:
public GameObject ButtonPrefab;
BUTTONPOOL bPool;
void test()
{
if ((bPool = GetComponent<BUTTONPOOL>()) == null)
{
gameObject.AddComponent<BUTTONPOOL>();
bPool = GetComponent<BUTTONPOOL>();
}
//Initiate with 300 Buttons
bPool.createButtonPool(ButtonPrefab, 50);
GameObject tempButton = bPool.Instantiate();
//MUST SET BUTTON ACTIVE CALLING Instantiate()
tempButton.SetActive(true);
//You can do other things with the button
one TB = tempButton.GetComponent<one>();
//To destroy that single button
bPool.Destroy(tempButton);
//OR destroy that all button
bPool.DestroyAll();
}
Note that you must set the Button to active after calling the custom Instantiate() function from the BUTTONPOOL script.

Related

Deactivate other gameObject's script

I am using one script for multiple gameObjects. If my current gameObject is triggered, I want the script to be deactivated on all other gameObjects except for current gameObject. I can set current script to be active after I have disabled it. But I do not want it to be enabled as false at all. How do I achieve this?
public MyScript[] myScript;
void Start () {
myScript= FindObjectsOfType (typeof (MyScript)) as MyScript[];
}
void Activated() {
for (int i = 0; i < myScript.Length; i++) {
myScript[i].enabled = false; //All other scripts except for current gameObject's script
}
Event System
Something like this.
public class MyScript : MonoBehaviour
{
public static event Action<GameObject> Triggered;
private void Start()
{
MyScript.Triggered += OnTriggered;
}
private void OnTriggered(GameObject triggeredGameObject)
{
if (triggeredGameObject != gameObject)
{
enabled = false;
}
}
private void OnTriggerEnter(Collider other)
{
Triggered?.Invoke(gameObject);
}
}
Your Way
You need to pass your triggered gameobject into the Activated function somehow so you make a comparison
public MyScript[] myScript;
void Start () {
myScript= FindObjectsOfType (typeof (MyScript)) as MyScript[];
}
void Activated(GameObject triggeredGameObject)// pass the triggered gameobject here
{
for (int i = 0; i < myScript.Length; i++)
{
if(triggeredGameObject != myScript[i].gameObject)
myScript[i].enabled = false;
}

How can I find all the ui text components in the hierarchy and then to disable/enable them?

For now I created array of Text and then dragging in the editor one by one to the inspector to the array.
The problem is that some Text ui are childs or childs of childs and it's not easy to find them all one by one.
How can I loop over the hierarchy or find all the text ui and then to disable/enable them ?
I need it for my game pause/resume.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class BackToMainMenu : MonoBehaviour
{
public Text[] uiTexts;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
SceneManager.UnloadSceneAsync(0);
DisableEnableUiTexts(false);
Cursor.visible = false;
Time.timeScale = 1;
}
else
{
Time.timeScale = 0;
MenuController.LoadSceneForSavedGame = false;
DisableEnableUiTexts(true);
SceneManager.LoadScene(0, LoadSceneMode.Additive);
Cursor.visible = true;
}
}
}
private void DisableEnableUiTexts(bool uiTextEnabled)
{
if (uiTexts.Length > 0)
{
foreach (Text ui in uiTexts)
{
if (uiTextEnabled)
{
ui.GetComponent<Text>().enabled = false;
}
else
{
ui.GetComponent<Text>().enabled = true;
}
}
}
}
}
Update :
I tried this :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class BackToMainMenu : MonoBehaviour
{
[ContextMenuItem("Fetch", nameof(FetchAllTexts))]
public Text[] uiTexts;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
SceneManager.UnloadSceneAsync(0);
DisableEnableUiTexts(false);
Cursor.visible = false;
Time.timeScale = 1;
}
else
{
Time.timeScale = 0;
MenuController.LoadSceneForSavedGame = false;
DisableEnableUiTexts(true);
SceneManager.LoadScene(0, LoadSceneMode.Additive);
Cursor.visible = true;
}
}
}
private void FetchAllTexts()
{
var tmp = new List<Text>();
for (var i = 0; i < SceneManager.sceneCount; i++)
{
foreach (var root in SceneManager.GetSceneAt(i).GetRootGameObjects())
{
tmp.AddRange(root.GetComponentsInChildren<Text>(true));
}
}
Text[] texts = tmp.ToArray();
uiTexts = texts;
}
private void DisableEnableUiTexts(bool uiTextEnabled)
{
if (uiTexts.Length > 0)
{
foreach (Text ui in uiTexts)
{
ui.enabled = uiTextEnabled;
}
}
}
}
but I don't see the "Fetch" ContextMenuItem anywhere. Tried right click on the object in hierarchy where the script is attached to tried in the Assets tried in the editor menu/s it's not there.
As said either use FindObjectsOfType
Text[] texts = FindObjectsOfType<Text>();
will return all active and enabled instances of Text.
However to get also disabled/inactive ones you can use
var tmp = new List<Text>();
for(var i = 0; i < SceneManager.sceneCount; i++)
{
foreach(var root in SceneManager.GetSceneAt(i).GetRootGameObjects)
{
tmp.AddRange(root.GetComponentsInChildren<Text>(true));
}
}
Text[] texts = tmp.ToArray();
which iterates over all loaded scenes and fetches ALL instances also currently inactive or disabled ones.
See
SceneManager.sceneCount
SceneManager.GetSceneAt
Scene.GetRootGameObjects
GameObject.GetComponentsInChildren
Then you can e.g. fetch this once either in Start or even earlier via the Inspector itself so you don't have to do it at every app start using [ContextMenuItem].
public class BackToMainMenu : MonoBehaviour
{
[ContextMenuItem("Fetch", nameof(FetchAllTexts)]
public Text[] uiTexts;
private void FetchAllTexts()
{
uiTexts = // One of the methods shown above
}
...
}
Why is this a good thing? As said this way you don't have to do it every time in Start and thus delaying the app start until it is done.
Instead you store all the references already via the Inspector in a SerializeField so when your app starts, this component already "knows" these references and you don't have to get them first.
Simply right click on the uiTexts field in the Inspector and hit Fetch
Then in general you would simply write
private void DisableEnableUiTexts(bool uiTextEnabled)
{
foreach (Text ui in uiTexts)
{
ui.enabled = uiTextEnabled;
}
}

quiz game unity-problem with button while i try to get answer input from the user, no results have been obtained

I am trying to create an simple quiz game, and below is my source code. I'm unable to get user input for the question. So I have added home and back button to the scene. Now that's also not working. Please take a look.
public class Level2UI: MonoBehaviour {
private List < string > sceneHistory = new List < string > ();
[SerializeField] private Level2Manager gameManager;
[SerializeField] private TextMeshProUGUI questionText;
[SerializeField] private List < Button > options;
[SerializeField] private Color CorrColor,
WrongColor,
NormalColor;
private Question question;
private bool answered;
// Start is called before the first frame update
void setButton() {
for (int i = 0; i < options.Count; i++) {
Button localBtn = options[i];
localBtn.onClick.AddListener(delegate {
ClickedAnswer(localBtn);
});
Debug.Log("testclick");
}
}
public void SetQuestion(Question question) {
this.question = question;
questionText.text = question.questionInfo;
List < string > answerList = ShuffleList.ShuffleListItems < string > (question.options);
for (int i = 0; i < options.Count; i++) {
options[i].GetComponentInChildren < TextMeshProUGUI > ().text = answerList[i];
Debug.Log("check");
options[i].name = answerList[i];
options[i].image.color = NormalColor;
}
answered = false;
setButton();
}
public void ClickedAnswer(Button btn) {
print("txt");
if (!answered) {
answered = true;
bool val = gameManager.Answer(btn.name);
if (val) {
btn.image.color = CorrColor;
Debug.Log("test1");
}
else {
btn.image.color = WrongColor;
Debug.Log("test");
}
}
}
public void LoadScene(string newScene) {
sceneHistory.Add(newScene);
SceneManager.LoadScene(newScene);
}
public bool PreviousScene() {
bool returnValue = false;
if (sceneHistory.Count >= 2) {
returnValue = true;
sceneHistory.RemoveAt(sceneHistory.Count - 1);
}
}
}
Did you assign the button? You need to assign the button or initialize it. Then put listener to it in the code or attach the method directly to the gameObject itself in the editor.
To assign a button
Button btn = transform.Find("btn").getComponent<Button>();
btn.onClick.AddListener(()=>{
// perform your action
});
I hope I helped a little? If not, I'm sorry.

Why the door/s colors flag is all the time true/false and all the time change between true and false?

This script is attached to a door :
Even if the variable doorLockState is set to true enabled true in the editor and the door should be color in red the door is green using a breakpoint the state variable inside ColorDoors is change between red and green non stop once state is true next it's false but in the editor it's all the time checked as true :
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
[ExecuteAlways]
public class HoriDoorManager : MonoBehaviour
{
public List<DoorHori> doors = new List<DoorHori>();
public bool doorLockState;
private void Awake()
{
if (transform.parent != null)
{
Transform parent = transform.parent;
var children = parent.GetComponentsInChildren<Transform>();
if (children != null)
{
foreach (Transform door in children)
{
if (door.name == "Door_Left" || door.name == "Door_Right")
doors.Add(door.GetComponent<DoorHori>());
}
}
ColorDoors(Color.red, Color.green, doorLockState);
}
}
void OnTriggerEnter()
{
if (doorLockState == false)
{
if (doors != null)
{
for (int i = 0; i < doors.Count; i++)
{
doors[i].OpenDoor();
}
}
}
}
private void Update()
{
ColorDoors(Color.red, Color.green, doorLockState);
}
private void ColorDoors(Color red, Color green, bool state)
{
List<Transform> children = new List<Transform>();
for (int i = 0; i < doors.Count; i++)
{
foreach (Transform child in doors[i].GetComponentsInChildren<Transform>())
{
if (child == doors[i].transform)
continue;
var renderer = child.GetComponent<Renderer>();
renderer.sharedMaterial.shader = Shader.Find("Unlit/ShieldFX");
if (state == true)
{
renderer.sharedMaterial.SetColor("_MainColor", red);
LockState(true);
}
else
{
renderer.sharedMaterial.SetColor("_MainColor", green);
LockState(false);
}
}
}
}
public bool GetLockState
{
get { return doorLockState; }
set { doorLockState = value; }
}
private void LockState(bool state)
{
var collider = gameObject.GetComponent<BoxCollider>();
if (state == false)
{
collider.size = new Vector3(2.3f, 2.736307f, 2.5f);
collider.center = new Vector3(0, 1.378154f, 0);
collider.transform.localPosition = new Vector3(-1.57f, 0, -2.98f);
collider.isTrigger = true;
}
else
{
collider.size = new Vector3(2.3f, 2.736307f, 3);
collider.center = new Vector3(0, 1.378154f, 0);
collider.transform.localPosition = new Vector3(-1.57f, 0, -2.98f);
collider.isTrigger = false;
}
}
}
Screenshot of a door constructor in the Hierarchy and the script in the Inspector:
The object Horizontal_Doors_Kit tag is set to Door.
And this is the editor script that should let me control the doors but there is also a problem here too since in the editor it's not listing all the doors :
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(DoorsLockManager))]
public class DoorsLockManagerEditor : Editor
{
private SerializedProperty _doors;
private SerializedProperty _globalLockState;
private bool shouldOverwrite;
private void OnEnable()
{
_doors = serializedObject.FindProperty("Doors");
_globalLockState = serializedObject.FindProperty("_globalLockState");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
shouldOverwrite = false;
// Begin a change check here
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_globalLockState);
if (EditorGUI.EndChangeCheck())
{
// overwrite only once if changed
shouldOverwrite = true;
}
for (int i = 0; i < _doors.arraySize; i++)
{
var door = _doors.GetArrayElementAtIndex(i);
// if door == null the script itself has an error since it can't even find the SerializedProperty
if (door == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get door property", target);
return;
}
if (door.objectReferenceValue == null) continue;
var serializedDoor = new SerializedObject(door.objectReferenceValue);
var lockState = serializedDoor.FindProperty("doorLockState");
serializedDoor.Update();
if (lockState == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get lockState property", target);
return;
}
// HERE OVERWRITE
if (shouldOverwrite)
{
lockState.boolValue = _globalLockState.boolValue;
}
else
{
EditorGUILayout.PropertyField(lockState, new GUIContent("Door " + i + " Lockstate"));
}
serializedDoor.ApplyModifiedProperties();
}
serializedObject.ApplyModifiedProperties();
}
}
Screenshot of the editor script inspector :
It's showing the two variables Global Lock State but it should also list the 12 doors.
The first problem the state of the door/s is all the time unlocked green color.
The second problem is why it's not listing all the doors so I can control each/all the doors either in editor or runtime ?
And the script DoorsLockManager :
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DoorsLockManager : MonoBehaviour
{
[HideInInspector]
public List<HoriDoorManager> Doors = new List<HoriDoorManager>();
// The global state
[SerializeField] private bool _globalLockState;
// During runtime use a property instead
public bool GlobalLockState
{
get { return _globalLockState; }
set
{
_globalLockState = value;
// apply it to all doors
foreach (var door in Doors)
{
// now you would need it public again
// or use the public property you had there
door.doorLockState = _globalLockState;
}
}
}
private void Awake()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
}
Currently your Doors list is only being updated when Awake() is called in your DoorsLockManager (and not accessible from the inspector), which means the list is always empty outside of runtime. Changing this should allow the list to be displayed in the inspector while editing.
Making the following minor changes to DoorsLockManager.cs:
private void Awake()
{
UpdateDoors();
}
public void UpdateDoors()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
and adding the following to the top of the OnInspectorGUI() method in DoorsLockManagerEditor.cs:
if (GUILayout.Button("Update Door List"))
{
((DoorsLockManager)target).UpdateDoors();
}
should achieve this by providing a button to update the list when needed.
Individual and global lock states should now be functional, however only one of the two Global Lock State toggles is properly functional, this can be fixed by either removing base.OnInspectorGUI(); from the OnInspectorGUI() method in DoorsLockManagerEditor.cs or by adding [HideInInspector] before [SerializeField] private bool _globalLockState; in DoorsLockManager.cs.
Currently HoriDoorManager.cs will only update their respective doors at runtime as the ColorDoors method is called only in Awake() and Update(). This can instead be made to update whenever the doorLockState bool is changed by modifying your GetLockState property as so:
public bool GetLockState
{
get { return doorLockState; }
set
{
doorLockState = value;
ColorDoors(Color.red, Color.green, doorLockState);
}
}
and assigning to this property instead of the backing variable in the GlobalLockState property of DoorsLockManager.cs (replacing line 28: door.doorLockState = _globalLockState; with door.GetLockState = _globalLockState;).
Edit:
This is a bit of a quick hack but adding the following to DoorsLockManager.cs:
public void UpdateColors()
{
foreach (var door in Doors)
{
door.GetLockState = door.GetLockState;
}
}
and the following to OnInspectorGUI() in DoorsLockManagerEditor.cs beneath the other button:
if (GUILayout.Button("Update Door Colors"))
{
((DoorsLockManager)target).UpdateColors();
}
should allow you to tell the doors to update their colours from the inspector manually.

How i can get GameObject index in the list?

At the start, the object adds a link to it to the list. Then when i click on this object, i need to get index reference of this object. How to do it?
Little example what i need:
public static List<GameObject> myObjects = new List<GameObject> ();
public GameObject ObjectA; //this is prefab
void Start(){
for (int i = 0; i < 10; i++) {
GameObject ObjectACopy = Instantiate (ObjectA);
myObjects.Add (ObjectACopy);
}
}
ObjectA script:
void OnMouseDown(){
Debug.Log(//Here i need to return index of this clicked object from the list);
}
Loop through the Objects List. Check if it matches with the GameObject that is clicked. The GameObject that is clicked can be obtained in the OnMouseDown function with the gameObject property. If they match, return the current index from that loop. If they don't match, return -1 as an error code.
void OnMouseDown()
{
int index = GameObjectToIndex(gameObject);
Debug.Log(index);
}
int GameObjectToIndex(GameObject targetObj)
{
//Loop through GameObjects
for (int i = 0; i < Objects.Count; i++)
{
//Check if GameObject is in the List
if (Objects[i] == targetObj)
{
//It is. Return the current index
return i;
}
}
//It is not in the List. Return -1
return -1;
}
This should work but it is better that you stop using the OnMouseDown function and instead use the OnPointerClick functions.
public class Test : MonoBehaviour, IPointerClickHandler
{
public static List<GameObject> Objects = new List<GameObject>();
public void OnPointerClick(PointerEventData eventData)
{
GameObject clickedObj = eventData.pointerCurrentRaycast.gameObject;
int index = GameObjectToIndex(clickedObj);
Debug.Log(index);
}
int GameObjectToIndex(GameObject targetObj)
{
//Loop through GameObjects
for (int i = 0; i < Objects.Count; i++)
{
//Check if GameObject is in the List
if (Objects[i] == targetObj)
{
//It is. Return the current index
return i;
}
}
//It is not in the List. Return -1
return -1;
}
}
See this post for more information on OnPointerClick.
EDIT:
Objects.IndexOf can also be used for this. This answer is here to make you understand how to do it yourself so that you can solve similar issues in the future.

Categories