some GameObjects
in my scene implement the interace ISaveable
. In my script, I want to find all these interfaces and store them. Later on I can loop through them and call their implemented method SaveData()
.
My current workaround to find these interfaces:
List<ISaveable> saveables = new List<ISaveable>();
MonoBehaviour[] sceneObjects = FindObjectsOfType<MonoBehaviour>();
for (int i = 0; i < sceneObjects.Length; i++)
{
MonoBehaviour currentObj = sceneObjects[i];
ISaveable currentComponent = currentObj.GetComponent<ISaveable>();
if (currentComponent != null)
{
saveables.Add(currentComponent);
}
}
The code works fine but is there a better way? I don't want to search for each Monobehaviour in the scene and then try to get its interface component.
You can use Linq OfType
ISaveable[] saveables = FindObjectsOfType<MonoBehaviour>().OfType<ISaveable>().ToArray();
of course it still requires to find all objects first.
Note though that it underlies the general limitations of FindObjectsOfType
regarding inactive GameObjects or disabled behaviors.
You could extend it to go through all root level objects of the scene. This works since afaik GetComponentsInChildren
indeed does work also with interfaces!
var saveables = new List<ISaveable>();
var rootObjs = SceneManager.GetActiveScene().GetRootGameObjects();
foreach(var root in rootObjs)
{
// Pass in "true" to include inactive and disabled children
saveables.AddRange(root.GetComponentsInChildren<ISaveable>(true));
}
If it's more efficient - yes, no, maybe, I don't know - but it includes also inactive and disabled objects.
And yes one could extend that to iterate through multiple loaded scenes using
var saveables = new List<ISaveable>();
for(var i = 0; i < SceneManager.sceneCount; i++)
{
var rootObjs = SceneManager.GetSceneAt(i).GetRootGameObjects();
foreach(var root in rootObjs)
{
saveables.AddRange(root.GetComponentsInChildren<ISaveable>(true));
}
}
This alternative is a bit similar to this answer but it has a huge flaw: You would need a specific implementation for the interface in order to make it work which invalidates the whole idea of an interface.
So the big question also from the comments there is: Why have an interface at all?
If it is anyway only going to be used for MonoBehaviour
you should rather have an abstract class
like
public abstract class SaveableBehaviour : MonoBehaviour
{
// Inheritors have to implement this (just like with an interface)
public abstract void SaveData();
}
This already solves the entire issue with using FindObjectsOfType
anyway since now you could simply use
SaveableBehaviour[] saveables = FindObjectsOfType<SaveableBehaviour>();
but you can still go further: For even easier and more efficient access you can make them register themselves completely without the need of a manager or Singleton pattern! Why should a the type not simply handle its instances itself?
public abstract class SaveableBehaviour : MonoBehaviour
{
// Inheritors have to implement this (just like with an interface)
public abstract void SaveData();
private static readonly HashSet<SaveableBehaviour> instances = new HashSet<SaveableBehaviour>();
// public read-only access to the instances by only providing a clone
// of the HashSet so nobody can remove items from the outside
public static HashSet<SaveableBehaviour> Instances => new HashSet<SaveableBehaviour>(instances);
protected virtual void Awake()
{
// simply register yourself to the existing instances
instances.Add(this);
}
protected virtual void OnDestroy()
{
// don't forget to also remove yourself at the end of your lifetime
instances.Remove(this);
}
}
so you can then simply inherit
public class Example : SaveableBehaviour
{
public override void SaveData()
{
// ...
}
protected override void Awake()
{
base.Awake(); // Make sure to always keep that line
// do additional stuff
}
}
and you could access all instances of that type via
HashSet<SaveableBehaviour> saveables = SaveableBehaviour.Instances;
foreach(var saveable in saveables)
{
saveable.SaveData();
}
You could have a manager class holding a collection/list of ISaveable
.
You can then make that manager class a singleton by setting the Singleton value in its Awake method.
class SaveManager(){
public static SaveManager Instance;
Awake(){
if (Instance == null)
{
Instance = this;
}
}
List<ISaveable> SaveAbles;
public void AddSaveAble(ISaveAble saveAble){
//add it to the list.
}
}
Then in the Start method of class implementing the ISaveable
interface you can use the Singleton to add it to the total list.
class Example : MonoBehaviour, ISaveable
{
void Start(){
SaveManager.Instance.AddSaveAble(this);
}
}
That way, each SaveAble adds itself to the manager via the managers method when it is created.
Note that it is important that the Singleton is set in Awake so it can be used in Start, as Awake comes first in the lifecycle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With