Obtenga todos los componentes con una interfaz específica en Unity
– UnityAssets3Free
buenas , me llamo juansito y aqui os traigo
esta nueva pregunta
ningún GameObjects
en mi escena implementar la interacción ISaveable
. En mi script quiero encontrar todas estas interfaces y almacenarlas. Más tarde puedo recorrerlos y llamar al método implementado. SaveData()
.
Mi solución actual para encontrar estas 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);
El código funciona bien, pero ¿hay una mejor manera? No quiero buscar cada Monobehavior en la escena y luego tratar de obtener su componente de interfaz.
10 respuestas 10
Filtro simplificado usando Linq
Usted puede utilizar Enlace OfType
ISaveable[] saveables = FindObjectsOfType<MonoBehaviour>().OfType<ISaveable>().ToArray();
por supuesto, todavía es necesario encontrar todos los objetos primero.
Tenga en cuenta, sin embargo, que subyacente a las limitaciones generales de FindObjectsOfType
con respecto a GameObjects inactivos o comportamientos deshabilitados.
Iterar a través de la jerarquía
Puede extenderlo para pasar por todos los objetos de nivel raíz en la escena. Esto funciona desde afaik GetComponentsInChildren
en verdad lo hace trabajar con interfaces también!
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));
Si es más eficiente, sí, no, tal vez, no sé, pero también incluye objetos inactivos y desactivados.
Y sí, uno puede extender esto para iterar a través de múltiples escenas cargadas usando
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));
No uses una interfaz en primer lugar
Esta alternativa es algo similar a esta respuesta, pero tiene un defecto importante: necesitaría una implementación específica para que la interfaz funcione, lo que invalida toda la idea de una interfaz.
Entonces, la gran pregunta también de los comentarios es: ¿Por qué tener una interfaz?
Si es así, sólo se utilizará para MonoBehaviour
deberías tener uno abstract class
me gusta
public abstract class SaveableBehaviour : MonoBehaviour
// Inheritors have to implement this (just like with an interface)
public abstract void SaveData();
Esto ya resuelve todo el problema con el uso FindObjectsOfType
de todos modos, ya que ahora solo puedes usar
SaveableBehaviour[] saveables = FindObjectsOfType<SaveableBehaviour>();
pero puedes ir más allá: para un acceso aún más fácil y eficiente, ¡puedes hacer que se registren completamente sin necesidad de un administrador o un patrón Singleton! ¿Por qué un tipo no debería manejar sus propias instancias?
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);
así que solo puedes heredar
public class Example : SaveableBehaviour
public override void SaveData()
// ...
protected override void Awake()
base.Awake(); // Make sure to always keep that line
// do additional stuff
y puede acceder a todas las instancias de ese tipo a través de
HashSet<SaveableBehaviour> saveables = SaveableBehaviour.Instances;
foreach(var saveable in saveables)
saveable.SaveData();
Podría tener una clase de administrador que contenga una colección/lista de ISaveable
.
A continuación, puede convertir esta clase de controlador en un singleton configurando el valor Singleton en su método Awake.
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.
Luego, en el método Start de la clase que implementa el ISaveable
puede usar Singleton para agregarlo a la lista total.
class Example : MonoBehaviour, ISaveable
void Start()
SaveManager.Instance.AddSaveAble(this);
De esta forma, cada SaveAble se agrega al administrador a través del método de administradores cuando se crea.
Tenga en cuenta que es importante que Singleton se defina en Awake para que se pueda usar en Start, ya que Awake es lo primero en el ciclo de vida.
¿Qué tal tener un estado global, tal vez una lista de ISaveable
y hacer que cada objeto guardable agregue una referencia a él durante start()
? Luego, simplemente puede iterar a través de la matriz, invocando SaveData()
en cada ISaveable
¿referencia?
En mi caso estaba usando esta implementación:
MonoBehaviour[] saveableScripts = (MonoBehaviour[])FindObjectsOfType(typeof(MonoBehaviour));
foreach (var enemy in saveableScripts)
if (enemy is ISaveable == false)
continue;
// you code ...
saveables.Add(currentComponent);
Tenga en cuenta que esta solución es muy ineficiente, pero puede usar LINQ para filtrar los componentes que implementan su interfaz. En ese caso MyInterface
es la interfaz que queremos consultar.
MyInterface[] myInterfaces = GameObject.FindObjectsOfType<MonoBehaviour>()
.OfType<MyInterface>()
.ToArray();
Pequeña nota para aclarar «ineficiente»: con Unity optimizando sus métodos de consulta de objetos, la llamada ni siquiera es tan mala. El problema es que LINQ asigna una gran cantidad de GC, lo que puede provocar caídas de fotogramas, por lo que nunca debe realizar estas operaciones «pesadas» por fotograma.
Ejecutar la consulta al inicio o cuando se realiza una operación intensiva obvia (guardar/cargar) está totalmente bien.
Aquí hay una mejora de su método para obtener todo el MonoBehavior con su interfaz en sus escenas.
void Start()
var myInterface = typeof(MyInterface);
var monoBehavior = typeof(MonoBehaviour);
var types = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(p => myInterface.IsAssignableFrom(p) && monoBehavior.IsAssignableFrom(p));
var myInstance = new List<MyInterface>();
foreach (var type in types)
myInstance.AddRange(FindObjectsOfType(type).Select(x => x as MyInterface));
Aquí no pasas por todo el GameObject en las escenas, pero haces más llamadas a FindObjectsOfType, + tienes que emitir. Pero GetComponent ya no es necesario.
Por lo tanto, depende de su caso, pero si almacena en caché la parte de reflexión (durante un momento de carga), puede evitar que implemente el patrón del Administrador y puede mantener su arquitectura de interfaz.
Si no, puede echar un vistazo al patrón de observador, es una forma diferente de administrar las llamadas en diferentes tipos de clase. https://sourcemaking.com/design_patterns/observer
nota: si aun no se resuelve tu pregunta por favor dejar un comentario y pronto lo podremos de nuevo , muchas gracias
eso es todo,espero que te halla servido