How to add all MonoBehaviours of Scene into the List? - c#

I've read an article 10000 UPDATE() CALLS.
Author uses UpdateManager. It has Update method which calls Update method in all of other MonoBehaviours of objects. It works faster than calling Update method from each MonoBehaviour separately.
This Manager looks like:
private ManagedUpdateBehavior[] list;
private void Start() {
list = GetComponents<ManagedUpdateBehavior>();
}
private void Update() {
var count = list.Length;
for (var i = 0; i < count; i++) {
// UpdateMe
list[i].UpdateMe();
}
}
And every objects now contains component with the code:
public class ManagedUpdateBehavior : MonoBehaviour {
// some variables
public void UpdateMe() {
// some logic
}
}
It's okay if 5-6-7-8- objects will be have that component.
But what if I have 100 objects? 1000? 10000?
How to find and add all ManagedUpdateBehaviors from all objects of scene? Should I use some recursive method on On Start? Cause every object may contain other objects with the script, they may contain the other objects etc...unlimited nesting
Also some objects can be instantiating dynamically...How to add their mono to manager? What is the right way?

Also some objects can be instantiating dynamically
Let each ManagedUpdateBehavior subscribe to the ManagedUpdateBehavior list by adding its instance to the ManagedUpdateBehavior instance. They should also unsubscribe from the ManagedUpdateBehavior when destroyed by removing itself from the ManagedUpdateBehavior instance.
Your new ManagedUpdateBehavior script:
public class ManagedUpdateBehavior : MonoBehaviour
{
UpdateSubscriber updateSUBSCR;
//Add it self to the List
void Start()
{
updateSUBSCR = GameObject.Find("UpdateSUBSCR").GetComponent<UpdateSubscriber>();
updateSUBSCR.addManagedUpdateBehavior(this);
}
//Remove it self from the List
void OnDestroy()
{
updateSUBSCR.removeManagedUpdateBehavior(this);
}
public void UpdateMe()
{
// some logic
Debug.Log("Update from: " + gameObject.name);
}
}
Create a GameObject called UpdateSUBSCR and attach the Script below to it:
public class UpdateSubscriber : MonoBehaviour
{
private List<ManagedUpdateBehavior> managedUpdateBehavior = new List<ManagedUpdateBehavior>();
public void addManagedUpdateBehavior(ManagedUpdateBehavior managedUB)
{
managedUpdateBehavior.Add(managedUB);
}
public void removeManagedUpdateBehavior(ManagedUpdateBehavior managedUB)
{
for (int i = 0; i < managedUpdateBehavior.Count; i++)
{
if (managedUpdateBehavior[i] == managedUB)
{
managedUpdateBehavior.RemoveAt(i);
break;
}
}
}
/*
public List<ManagedUpdateBehavior> getManagedUpdateBehaviorinstance
{
get
{
return managedUpdateBehavior;
}
}*/
public void updateAll()
{
for (int i = 0; i < managedUpdateBehavior.Count; i++)
{
managedUpdateBehavior[i].UpdateMe();
}
}
}
Then you can call it with:
public class Test : MonoBehaviour
{
UpdateSubscriber updateSUBSCR;
void Start()
{
updateSUBSCR = GameObject.Find("UpdateSUBSCR").GetComponent<UpdateSubscriber>();
}
void Update()
{
updateSUBSCR.callUpdateFuncs();
}
}
Be aware that array is faster than List but this is better than the having the Update() function in every script.

You can find and add all ManagedUpdateBehaviours using Object.FindObjectsOfType method.
You can add your dynamically instantiate objects using a static method that can be called from created game object to add it to list.
My suggestion is created a SceneUpdateManager, something like this:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class SceneUpdateManager : MonoBehaviour {
private static List<ManagedUpdateBehavior> list;
private void Start ()
{
list = Object.FindObjectsOfType<ManagedUpdateBehavior> ().ToList ();
}
public static void AddBehavior(ManagedUpdateBehavior behaviour)
{
list.Add (behaviour);
}
private void Update () {
var count = list.Count;
for (var i = 0; i < count; i++)
{
list [i].UpdateMe();
}
}
}
Object.FindObjectsOfType does not return inactive objects.

Related

I can't get a variable into a collision method in C#

I am making a script for a unity project to destroy an instantiated clone if it collides with another clone, but since the Game Object (the clone) is declared in the start method and I cannot put it at the top I need to figure out how to destroy the clone if it collides with something else.
This is the error I get if I put it on top:
A field initializer cannot reference the non-static field, method, or property
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class topbigspike : MonoBehaviour
{
public GameObject Flame;
// Start is called before the first frame update
void Start()
{
int a = 30;
int i = 0;
while (0 < a)
{
a--;
i++;
GameObject FlameClone = Instantiate(Flame);
FlameClone.transform.position = new Vector3(Random.Range(10, 2000), -3, 0);
}
}
void OnCollisionEnter2D(Collision2D col)
{
Destroy(FlameClone);
}
}
As the error message says, you cannot use Flame as field initializer. But you can still declare FlameClone as field (at the top, as you say) ) and initialize it in the Start method:
public class topbigspike : MonoBehaviour
{
public GameObject Flame;
public GameObject FlameClone; // <=== Declare here, as class field.
// Start is called before the first frame update
void Start()
{
int a = 30;
int i = 0;
while (0 < a)
{
a--;
i++;
FlameClone = Instantiate(Flame); // <=== Initialize here.
FlameClone.transform.position = new Vector3(Random.Range(10, 2000), -3, 0);
}
}
void OnCollisionEnter2D(Collision2D col)
{
Destroy(FlameClone);
}
}
As already mentioned you need to store it a field in order to access it from other methods.
However, seeing a while loop there instantiating multiple (30) instances a single field isn't enough anyway except you really want to only destroy the last created instance.
It should probably rather be e.g.
public class topbigspike : MonoBehaviour
{
public int amount = 30;
public GameObject Flame;
private GameObject[] flameInstances;
void Start()
{
flameInstances = new GameObject[amount];
for(var i = 0; i < amount; i++)
{
var flameInstance = Instantiate(Flame);
flameInsance.transform.position = new Vector3(Random.Range(10, 2000), -3, 0);
flameInstances[i] = flameInstance;
}
}
void OnCollisionEnter2D(Collision2D col)
{
foreach(var flameInstance in flameInstances)
{
Destroy(flameInstance);
}
}
}

Unity android. Loading Function after killing an app doesn't work properly (Object Reference not set to an instance of an object)

I am at begginner level with unity.
I have Load() function that goes off in OnApplicationPause(false). It works fine if I block the screen or minimalise app, and come back to it. However, when I kill it, I get error and the data doesnt get loaded.
Below is the script attached to the GameObject "SaveManager"
using System.Collections.Generic;
using UnityEngine;
using System;
public class SaveManager : MonoBehaviour
{
public GameObject ZwierzetaGroup;
public GameObject JedzeniaGroup;
public GameObject PrzedmiotyGroup;
public List<GameObject> zwierzeta_sprites;
public List<GameObject> jedzenia_sprites;
public List<GameObject> przedmioty_sprites;
public static DateTime oldDate;
Camera mainCamera;
public SaveState saveState;
void Start()
{
mainCamera = Camera.main;
FillArrays();
}
public void Save()
{
Debug.Log("Saving.");
SaveSpriteArray("zwierze", zwierzeta_sprites);
SaveSpriteArray("przedmiot", przedmioty_sprites);
SaveSpriteArray("jedzenie", jedzenia_sprites);
PlayerPrefs.SetInt("pieniazki", saveState.GetPieniazki());
PlayerPrefs.SetInt("HayAmount", saveState.GetHayAmount());
PlayerPrefs.SetInt("HayMax", saveState.GetHayMax());
PlayerPrefs.SetInt("FruitAmount", saveState.GetFruitAmount());
PlayerPrefs.SetInt("FruitMax", saveState.GetFruitMax());
//time:
PlayerPrefs.SetString("sysString", System.DateTime.Now.ToBinary().ToString());
PlayerPrefs.SetInt("First", 1);
}
public void SaveSpriteArray(string saveName, List<GameObject> sprites)
{
for (int i = 0; i < sprites.Count; i++)
{
if (sprites[i].activeSelf)
{
PlayerPrefs.SetInt(saveName + i, 1);
}
else
{
PlayerPrefs.SetInt(saveName + i, 0);
}
}
}
public void Load()
{
Debug.Log("Loading.");
//wczytanie czasu:
long temp = Convert.ToInt64(PlayerPrefs.GetString("sysString"));
oldDate = DateTime.FromBinary(temp);
Debug.Log("oldDate: " + oldDate);
//wczytywanie aktywnych sprite'ow
LoadSpriteArray("zwierze", zwierzeta_sprites);
LoadSpriteArray("przedmiot", przedmioty_sprites);
LoadSpriteArray("jedzenie", jedzenia_sprites);
saveState.SetPieniazki(PlayerPrefs.GetInt("pieniazki"));
saveState.SetHayAmount(PlayerPrefs.GetInt("HayAmount"));
saveState.SetHayMax(PlayerPrefs.GetInt("HayMax"));
saveState.SetFruitAmount(PlayerPrefs.GetInt("FruitAmount"));
saveState.SetFruitMax(PlayerPrefs.GetInt("FruitMax"));
mainCamera.GetComponent<UpdateMoney>().MoneyUpdate();
}
public void LoadSpriteArray(string saveName, List<GameObject> sprites)
{
for (int i = 0; i < sprites.Count; i++)
{
if (PlayerPrefs.GetInt(saveName + i) == 1)
{
sprites[i].SetActive(true);
}
else
{
sprites[i].SetActive(false);
}
}
}
private void FillArrays()
{
//find children
foreach (Transform child in ZwierzetaGroup.transform)
{
zwierzeta_sprites.Add(child.gameObject);
}
foreach (Transform child in PrzedmiotyGroup.transform)
{
przedmioty_sprites.Add(child.gameObject);
}
foreach (Transform child in JedzeniaGroup.transform)
{
jedzenia_sprites.Add(child.gameObject);
}
}
}
Below is a chunk of script attached to the main camera (probably a mistake). SaveManager GameObject with Script is attached to this one in inspector. This script is pretty big, so I'll skip the parts that I don't find relevant.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class ManageEncouters: MonoBehaviour
{
DateTime currentDate;
public int First;
public SaveState saveState;
public SaveManager saveManager;
public HayBar hayBar;
public FruitBar fruitBar;
public GameObject[] jedzenia_sprites;
void Start()
{
}
void OnApplicationPause(bool pauseStatus)
{
if (!pauseStatus)
{
currentDate = System.DateTime.Now;
//Sprawdzanie czy jest to piersze uruchomienie gry (brak zapisu)
First = PlayerPrefs.GetInt("First");
if (First == 0)
{
Debug.Log("First time in app.");
RandomiseAnimals();
SaveManager.oldDate = currentDate;
hayBar.SetHayMax(1);
hayBar.SetHay(0);
fruitBar.SetFruitMax(1);
fruitBar.SetFruit(0);
saveState.SetPieniazki(100);
this.GetComponent<UpdateMoney>().MoneyUpdate();
}
else
{
Debug.Log("Not the first time in app.");
saveManager.Load();
}
if (TimeInSeconds(currentDate, SaveManager.oldDate) > 12)
{
Debug.Log("It's been more than 12 seconds sience last time.");
EatFood(currentDate, SaveManager.oldDate);
RandomiseAnimals();
}
else
{
Debug.Log("It's been less than 12 seconds sience last time.");
}
}
if (pauseStatus)
{
saveManager.Save();
}
}
private int TimeInSeconds(DateTime newD, DateTime oldD)
{
TimeSpan difference = newD.Subtract(oldD);
int seconds = (int)difference.TotalSeconds;
return seconds;
}
}
Below is the error I get, I don't know how to copy the text, so it's an Image.
I'm pretty sure that what you have here is a timing issue.
OnApplicationPause
Note: MonoBehaviour.OnApplicationPause is called as a GameObject starts. The call is made after Awake. Each GameObject will cause this call to be made.
So to me this sounds like it might be called when your SaveManager is not yet initialized, in particular the mainCamera.
I think you could already solve the issue by moving the initialization into Awake instead
private void Awake()
{
mainCamera = Camera.main;
FillArrays();
}
In general my little thumb rule is
use Awake wherever possible. In particular initialize everything where you don't depend on other scripts (initialize fields, use GetComponent, etc)
use Start when you need other scripts to be initialized already (call methods on other components, collect and pass on instances of some prefabs spawned in Awake, etc)
This covers most of cases. Where this isn't enough you would need to bother with the execution order or use events.

How do I store the same types of classes that have a different generic inside of a list?

I've been tinkering with this and I have a 'RespawnManager' that I want to use to manage my multiple 'SpawnPoint' classes with different generics but it ended up forcing me to use generics for my 'RespawnManager' which I don't want.
Let's say I had a SpawnPoint<T> class and I made a SpawnPoint<Enemy1>, SpawnPoint<Enemy2>, and SpawnPoint<Enemy3>. Is there any way I can make a list that can just manage multiple 'SpawnPoint's of any generic?
Base class:
public abstract class SpawnPoint<T> : MonoBehaviour
{
//how big the range of the spawn protection is
public int spawnProtectionRadius = 20;
public bool Occupied { get; set; }
public bool IsInSpawn(Transform target)
{
Debug.Log((target.position - transform.position).magnitude);
if ((target.position - transform.position).magnitude <= spawnProtectionRadius)
{
return true;
}
return false;
}
public abstract T Get();
}
Class that Inherits this
public class SeaMineSpawnPoint : SpawnPoint<Seamine>
{
public override Seamine Get()
{
return SeaMineObjectPool.PoolInstance.Get();
}
private void Start()
{
RespawnManager<Seamine>.respawnManager.AddSpawn(this);
}
}
Respawn manager:
public class RespawnManager<T> : MonoBehaviour where T : Component
{
public static RespawnManager<T> respawnManager;
[SerializeField]
private List<Transform> playerList;
[SerializeField]
private List<SpawnPoint<T>> spawnpoints;
private float respawnCounter;
private void Awake()
{
respawnManager = this;
}
private void Start()
{
foreach (SpawnPoint<T> sp in spawnpoints)
{
Debug.Log(sp.transform.position);
}
}
public void AddSpawn(SpawnPoint<T> spawnPoint)
{
spawnpoints.Add(spawnPoint);
}
public void RespawnSeaMines()
{
if (respawnCounter > 5)
{
respawnCounter = 0;
foreach (SpawnPoint<T> sp in spawnpoints)
{
foreach (Transform playerT in playerList)
{
if (sp.Occupied == false && !sp.IsInSpawn(playerT))
{
Component ourGameObj = sp.Get();
ourGameObj.transform.position = sp.transform.position;
ourGameObj.gameObject.SetActive(true);
sp.Occupied = true;
return;
}
}
}
}
}
private void Update()
{
respawnCounter += Time.deltaTime;
Debug.Log(respawnCounter);
RespawnSeaMines();
}
}
ObjectPool
//Class that's used for object pooling of different types.
//'T' must be a Unity component or it will error.
public abstract class ObjectPool<T> : MonoBehaviour where T : Component
{
//An object with this specific component that we use to copy.
[SerializeField]
private T prefab;
//Makes sure that only 1 coroutine runs at a time
private bool coroutineIsRunning;
//The singleton instance to our object pool.
public static ObjectPool<T> PoolInstance { get; private set; }
//A queue is used to organize plus activate and deactivate objects which
//have this component.
protected Queue<T> objects = new Queue<T>();
private void Awake()
{
//Set the instance of this pool to this class instance. Only one of these can be set.
if (PoolInstance != null)
{
throw new System.Exception("Singleton already exists. Cannot make another copy of this");
}
PoolInstance = this;
}
public T Get()
{
//If the queue happens to be empty, then add a brand new component.
if (objects.Count == 0) AddObjects(1);
//Returns the generic component and removes it from the queue.
return objects.Dequeue();
}
public void ReturnToPool(T objectToReturn)
{
//Disables the game object that the T component is attached to.
objectToReturn.gameObject.SetActive(false);
//Stores the T component in the queue.
objects.Enqueue(objectToReturn);
}
public void AddObjects(int count)
{
for (int i = 0; i < count; i++)
{
//Create a new copy of the prefab.
//The prefab is a game object with the T component attached to it.
T newObject = Instantiate(prefab);
//Disable the game object.
newObject.gameObject.SetActive(false);
//Add the T component to the queue.
//The T component is attached to the game object we created earlier.
objects.Enqueue(newObject);
}
}
public T GetWithDelay(int time)
{
T genericToReturn = null;
if (!coroutineIsRunning)
{
coroutineIsRunning = true;
StartCoroutine(GetCoroutine(time, genericToReturn));
}
return genericToReturn;
}
private IEnumerator GetCoroutine(int time, T generic)
{
float counter = 0;
while (counter < time)
{
counter += Time.deltaTime;
yield return null;
}
generic = Get();
generic.gameObject.SetActive(true);
coroutineIsRunning = false;
}
}
You should be able to declare your spawnpoints property in RespawnManager as a List<SpawnPoint<Component>> instead of List<SpawnPoint<T>>. That will allow you to get rid of the <T> type parameter entirely from RespawnManager and make it non-generic.

Unity C# list being cleared when the method is complete - object pool

I am trying to make a simple object pool in c# for Unity.
So far I have used this class:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ObjectPool : MonoBehaviour
{
public static ObjectPool ShareInstance;
public List<GameObject> pooledObjects;
public GameObject objectToPool;
public int amountToPool;
// Use this for initialization
void Start()
{
pooledObjects = new List<GameObject>();
for (int i = 0; i < amountToPool; i++)
{
GameObject obj = (GameObject)Instantiate(objectToPool);
obj.SetActive(false);
pooledObjects.Add(obj);
}
}
void Awake()
{
ShareInstance = this;
}
public GameObject GetPooledObject()
{
for (int i = 0; i < pooledObjects.Count; i++)
{
if (!pooledObjects[i].activeInHierarchy)
{
return pooledObjects[i];
}
}
return null;
}
}
The start method makes the inactive instances of my prefab correctly. However I am finding an error when I use this code to call GetPooledObject:
GameObject optionTile = ObjecdtPooler.ShareInstance.GetPooledObject();
if (optionTile != null)
{
optionTile.SetActive(true);
}
The GetPooledObject method appears to think that the list (pooledObjects) is empty. This means that the for loop within this method is just skipped, and null is returned. I used debug.log and the list is correctly filled with the instances of my prefab. However when I call the GetPooledObject method, the list is said to be empty.
Any ideas?
Thanks
I expect that either amountToPool has a inital value of 0 or if it's greater than 0 all objects are in use.
So you should expand your pool to grow by a given value when all objects are in use.

Am I Doing Something Wrong here Unity 3D

I've been stuck on this script for about some weeks. I have this script where as to the items that are on a list shuffle every time a new game starts. I don't know why but I can't seem to shuffle an item list at all. Please help, and tested it for yourself if you can. Also please explain to me what might be the problem or what I can do to fix it, I'm still pretty new to this.
`using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class RayCasting : MonoBehaviour
{
public float pickupDistance;
public List<Item> items;
public List<Item> finalItems;
#region Unity
void Start ()
{
Screen.lockCursor = true;
// Do a while loop until finalItems contains 5 Items
while (finalItems.Count < 5) {
Item newItem = items[Random.Range(0, items.Count)];
if (!finalItems.Contains(newItem)) {
finalItems.Add(newItem);
}
items.Clear();
}
}
void Update ()
{
RaycastHit hit;
Ray ray = new Ray(transform.position, transform.forward);
if (Physics.Raycast(ray, out hit, pickupDistance))
{
foreach(Item item in finalItems)
{
if(Input.GetMouseButtonDown(0)) {
if (item.gameObject.Equals(hit.collider.gameObject))
{
numItemsCollected++;
item.Collect();
break;
}
}
}
}
}
void OnGUI()
{
GUILayout.BeginArea(new Rect(130,400,100,130));
{
GUILayout.BeginVertical();
{
if (numItemsCollected < items.Count)
{
foreach (Item item in finalItems)
GUILayout.Label(string.Format("[{0}] {1}", items.Collected ? "" + items.password: " ", items.name ));
}
else
{
GUILayout.Label("You Win!");
}
}
GUILayout.EndVertical();
}
GUILayout.EndArea();
}
#endregion
#region Private
private int numItemsCollected;
#endregion
}
[System.Serializable]
public class Item
{
public string name;
public GameObject gameObject;
public int password;
public bool Collected { get; private set; }
public void Collect()
{
Collected = true;
gameObject.SetActive(false);
}
public void passwordNumber()
{
password = 0;
Collected = true;
gameObject.SetActive(false);
}
}
`
This example is too complicated, and has a lot of unrelated code to the question you seem to be asking. As a basic strategy, you should try to isolate the code you think has a problem, fix that problem, then apply it to the larger case.
For example, your basic logic has at least one error: Random.Range is inclusive, so you could possibly have an out-of-bounds index unless you use "index.Count-1".
Here's an example that creates a list of 5 random items from a larger "original" list. You should be able to apply this to your code:
using UnityEngine;
using System.Collections.Generic;
public class RandomList : MonoBehaviour {
public List <int> originals = new List<int>();
public List <int> randomized = new List<int>();
public int desiredNumberOfRandomInts = 5;
// Use this for initialization
void Awake () {
for(int i = 1; i <= 20; ++i) {
originals.Add(i);
}
while(randomized.Count < desiredNumberOfRandomInts) {
int randomSelection = originals[Random.Range(0, originals.Count-1)];
if (!randomized.Contains(randomSelection)) {
randomized.Add(randomSelection);
}
}
}
}
I think removing the items.Clear() might work, with the reason I explained in my comment.
while (finalItems.Count < 5) {
Item newItem = items[Random.Range(0, items.Count)];
if (!finalItems.Contains(newItem)) {
finalItems.Add(newItem);
}
}
But still, I'm assuming you already have list of items somewhere already (and the list must be >5 since you will loop minimum 5 times). If you don't items.Count will still stay 0, and making your random become Random.Range(0,0).

Categories