I want to collect all monobehaviours in a certain (large) gameobject and then set the variables of some of them.
I have a gameobject called “base”, which contains walls, building, defensive structures and gates. I want to set the gates and defenses, so that they are on the right team. To do this, I collect all Monobehaviours in the base and then then want to set the Team of the ones that control the gates and defenses. To this end, I wrote the following code:
using UnityEngine;
using System.Collections;
public class Base : MonoBehaviour {
public GameObject m_Base;
private MonoBehaviour[] m_Scripts; //Array of all Monobehaviours active in the base
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public void Setup(int team) // setup all Monobehaviours to be from the team "team".
{
Debug.Log("Entered the Setup() method for team " + team);
m_Scripts = m_Base.GetComponentsInChildren<MonoBehaviour>();
Debug.Log("Collected Scripts");
/* for (int i = 0; i < m_Scripts.Length; i++)
{
Debug.Log(m_Scripts[i]);
}*/
foreach (DoorControl doorControl in m_Scripts)
{
Debug.Log("Setting DoorControls");
doorControl.m_Team = team;
}
foreach (GunFire gunFire in m_Scripts)
{
Debug.Log("Setting GunFire");
gunFire.m_Team = team;
}
foreach (ArtilleryFire artilleryFire in m_Scripts)
{
Debug.Log("Setting ArtilleryFire");
artilleryFire.m_Team = team;
}
}
}
The scripts are all collected correctly, but then i get an error:
InvalidCastException: Cannot cast from source type to destination type.
Base.Setup (Int32 team) (at Assets/Base.cs:34)
GameManager.Start () (at Assets/Scripts/Managers/GameManager.cs:54)
which apparently happens at the first call of “foreach” (I never enter the inside of that loop). Can anyone help or do I have to collect all scripts separately and then perform the action?
Yeah @LeftyRighty 's certainly given you the easy way to do it… but for the sake of understanding, here’s what’s going on with your original code (and another way to fix it).
You’re asking for all MonoBehaviours. So this is a list including all scripts on this object and any of its children, possibly including the occasional DoorControl, GunFire, and ArtilleryFire.
Then you use “for each” on this list. This iterates over each and every member of that list, and attempts to assign it to the loop variable. If your loop variable is the same type as your list element (i.e. MonoBehaviour), then this will always work. But if your loop variable is some more specific subtype — say, DoorControl — then this will fail, as soon as it hits something that is not a DoorControl. A DoorControl variable simply can’t hold a reference to a GunFire. And no, the foreach statement doesn’t automatically skip over non-matching types for you; instead it just throws an exception (as you found).
So, you could fix it by checking the type as you iterate, like this:
foreach (MonoBehaviour script in m_Scripts) {
if (script is DoorControl) {
(script as DoorControl).m_Team = team;
} else if (script is GunFire) {
(script as GunFire).m_Team = team;
} // etc.
}
Not that this is good code… but it would work. (See next message for another suggestion.)
I notice that you ( @bustee ) are checking for three types, but doing the exact same thing to all three. This is a job for… Interfaces! (Dum da da DUM dum!)
You could declare an interface for any MonoBehaviour that is team-oriented. Something like:
public interface TeamItem {
public void SetTeam(int team);
}
Now, for each of your classes (DoorControl, etc.) that this applies to, you declare that they implement that interface in addition to being a MonoBehaviour:
public class DoorControl : MonoBehaviour, TeamItem {
int m_Team;
public void SetTeam(int team) {
m_Team = team;
}
Now, when you want to find all of the TeamItems, you can do that in one fell swoop — it doesn’t matter what the specific type is in each case; it could be some mix of DoorControl, GunControl, ArtilleryFire, and six other things you will implement over the course of the next year. It doesn’t matter, because you talk to them all through the TeamItem interface.
foreach (TeamItem ti in m_base.GetComponentsInChildren<TeamItem>()) {
ti.SetTeam(team);
}
It’s just a little more setup initially declaring that interface and implementing it in each class, but it’s a huge win everywhere you use it. This is a really powerful technique — I encourage you to take the time to fully grok it!