Noticias

¡RECUERDA QUE SI ERES UN NUEVO USUARIO, DEBES PRESENTARTE PARA PODER PUBLICAR MENSAJES! | TENEMOS CANAL OFICIAL DE TELEGRAM: t.me/unity3dspain

Las referencias y la memoria...

Iniciado por digimikeh, Noviembre 04, 2018, 04:24:10 AM

Tema anterior - Siguiente tema
Buenas noches.
 
Tengo una duda con las referencias y el uso de la memoria:
 
Tengo en escena dos clases especiales:
 
CanvasController.cs (controla la interfaz UI)
 
MediaController.cs (controla los videos, efectos de sonido y música)
 
Después tengo 10 objetos que tienen sus respectivas clases, cada uno de estos objetos necesita invocar tanto a CanvasController como a MediaController, que harían ustedes en este caso?:
 
 
 
1) Crear 2 referencias canvasController y mediaController respectivamente en cada una de las 10 clases de objetos en escena...


Ej: objeto1 llama a mediaController para reproducir música
 
     objeto2 llama a canvasController para mostrar un telón
 
     objeto3 llama a mediaController para reproducir efectos de sonido
 
     objeto4 llama a mediaController para reproducir un videoclip
 
     objeto5 llama a canvasController para mostrar unos botones, etc etc.
 
En este caso, todos los objetos podrían llamar directamente a las clases especiales.
 
 
 
2) Crear una clase (supongamos masterController) que haga referencia a canvasController y mediaController y que las otras 10 clases accedan a masterController para que ésta llame a canvasController y mediaController?..  masterController sería algo asi como un guardia que le da acceso a las 10 clases solo mediante él mismo.
 
 
 
Ej: objeto1 llama a masterController para que le diga a mediaController que reproduzca una musica
 
     objeto2 llama a masterController para que le diga a canvasController que muestre un telón
 
     ...etc, etc.
 
 
 
En este caso, los objetos no conocen ni a canvasController ni mediaController, solo tienen referencia a masterController.
 
 
 
Qué tan viable son?.. hay alguna otra alternativa?
 
Gracias, Saludos.
 
 
 
 
 
 

Noviembre 04, 2018, 04:22:20 PM #1 Ultima modificación: Noviembre 04, 2018, 04:47:59 PM por J Montes
Hola.
 
La verdad es que hay un montón de maneras de resolver tu caso. Depende de tu gusto personal, de si esas clases serán creadas una única vez o varias, de si esos objetos deben sobrevivir a un cambio de escena...
 
Te comento algunas ideas.
 
Cita de: digimikeh date=15413018501) Crear 2 referencias canvasController y mediaController respectivamente en cada una de las 10 clases de objetos en escena...
   


No hay nada malo en ello. El consumo de memoria de un par de punteros (o diez) no debería tener el más mínimo impacto en tu aplicación.
 
Si esos objetos van a ser únicos durante la vida de la aplicación, también puedes considerar un patrón singleton, de esa forma puedes obtener en cualquier momento una referencia a ellas. Un ejemplo de singleton, en este caso uno que instancia un prefab si el objeto no está en la escena, y que gestiona un AudioSource, y que no quiere ser destruido si se cambia de escena:
 

public class MusicManager : MonoBehaviour {
    private AudioSource audioSource;
    static MusicManager instance = null; // Esta es la referencia al único MusicManager    
    // Este es el método que os da acceso a la única instancia de MusicManager
    public static MusicManager Instance() {
        if (MusicManager.instance == null) {
            
            if (GameObject.Find("MusicManager")) {
                DestroyImmediate(GameObject.Find("MusicManager"));
            }
            MusicManager.instance = ((GameObject)Instantiate((GameObject)Resources.Load("MusicManagerPrefab"))).GetComponent<MusicManager>();
            MusicManager.instance.name = "MusicManager";           
            
            instance.audioSource = instance.GetComponent<AudioSource>();
            instance.SetVolume(PlayerPrefs.GetFloat(Globals.PLAYERPREF_MUSIC_VOLUME, 0.5f));
            DontDestroyOnLoad (instance.gameObject);
        } 
        
        return MusicManager.instance;
    }

 
Yo uso este patrón en algunos casos. 
 
Pero al margen del uso de singletons, no hay nada malo en que cada clase, por ejemplo en su método Start(), o cuando mejor te convenga, obtengan referencias a las clases de servicio como MediaController o CanvasController, y las almacenen para su uso. Ejemplo (este es el método que te recomiendo a priori):
 

public class MyGameObjectType : MonoBehaviour {
MediaManager mediaManager;
// Use this for initialization
void Start() {
   // Obtenemos la referencia via GameObject.Find en este caso...
   // ...pero podríamos obtenerla de otras formas (algún otro podría pasárnosla)
   mediaManager = GameObject.Find("MediaManager").GetComponent<MediaManager>();
}
}

 
Este método tiene la ventaja de que lal forma en que obtienes la referencia es un poco indiferente: podrías establecerla a través de Unity (eso sí, haciendo público el miembro mediaManager), o puedes obtenerla en el método Start() como en este ejemplo. Pero sería fácil cambiar de una a otra, en función de si la referencia la vas a establecer en tiempo de diseño (via Editor de Unity) o en tiempo de ejecución (desde el código).
 
En este caso, deberás asegurarte de que el objeto "MediaManager" ha sido creado antes. Eso es cosa tuya.
 
 
 
Cita de: digimikeh date=15413018502) Crear una clase (supongamos masterController) que haga referencia a canvasController y mediaController y que las otras 10 clases accedan a masterController para que ésta llame a canvasController y mediaController?..  masterController sería algo asi como un guardia que le da acceso a las 10 clases solo mediante él mismo.
   


No te recomiendo que hagas una clase que haga de proxy (intermediaria) de muchas otras. Acabarás llenando esa clase con un combinado de todos los métodos de las clases a las que despacha.
 
Sí es más común usar una clase que tenga referencias a otras varias clases importantes, simplemente funcionando un contenedor de referencias. En tu caso, de todas formas, no te recomiendaría este camino ya que no veo motivo para mantener CanvasController y MediaController "juntas". Son dos clases diferentes, con diferentes funciones, y no veo beneficio en presentarlas agrupadas, cuando es igual de fácil o de difícil obtener una referencia a ellas cuando sea necesario (y en caso de que esa referencia vaya a ser usada a lo largo de la vida de tu objeto, almacenar esa referencia como comentábamos en el punto 1).
 
Saludos,

Gracias por tu explicación, efectivamente hubo un momento en que me iba a inclinar por la 2, pero vi que tendría problemas ya que ese proxy debería sobrevivir a las escenas al igual que media y canvascontroller, y no puedo hacer que sobreviva porque usa herencia, cada escena tiene su propio manager de escena y heredan de ese proxy... sería otro spaghetti mas.
 
 
 
Saludos!

Ja tus recientes post @digimikeh hacen referencia a patrones de diseño, quizas te interese mirar por esos lados.
 
De 1) : Yo no lo haría así, no por tema de memoria (que en la "big-picture" no es nada) sino por tema de organización y salud mental, tener las referencias en cada uno de los scripts puede llegar a ser lioso, imaginate 10 managers y 3409 scripts en todos en cada Awake o Start tener que andar buscando la referencia :(. Prefiero el singleton mil veces y además es lo que usa, sea C# o C++. Generalmente no se tiende a usar un singleton para todo pero que sea un singleton importante.
 
De 2) : Podés separarlo todo y aún así hacerlo todo bien y limpito. Yo uso/usaba (depende de la fecha que inicié un proyecto) un monobehaviour DDOL que hace de GameManager y dentro tengo N referencias a los N managers, todo separado y prolijo. En otras ocaciones tenía (o quizás tengo) algunos managers que viven por sí solos, pero más allá de eso por supuesto todo separado en sus clases, supongo que por tener las referencias te referís a esto.
 
Ej:
 


  •    El GameManager: Nace en la primer escena, crea al resto de los managers o a los que quiera, se encarga de lo específico del Juego (el motor quedó en el pasado ahora todo se empieza a especializar) tareas tales como monitorear el estado del juego, pasar datos que llegan de X lugar a Y (quizás que venga de la escena y termine en algún manager), Cargar datos en la partida, Guardar datos de la partida, administrar algunos delegate events importantes ... es como el Core mío y hace de intermediario entre Mi aplicación y Unity. Esta parte podés hacerla como el jefe o simplemente como un socio más, la vida no cambia demasiado, va más en un tema de gustos casi.


  •    SceneManager: Todo lo que tenga que ver con la escena (cuac) cargar, adicionar, async, etc


  •    InputManager: hace de Rewire custom (jajaja) ya que Unity es una basura manejando multiples mandos, teclados, joystick, mouse, etc.


  •    UIManager: todo UI a la vista pasa por este tipo, tales como las notificaciones y pop ups, mensajes de X tipo, main menu, controles, etc


  •    AudioManager: Nada muy raro de aca, solamente hace de filtro final a la hora de controlar la mezcla que irá a los parlantes, con el Mixer practicamente, y recibe las ordenes de las opciones (UIManager -> GameManager->AudioManager) y escucha algunos eventos (quizás para manejar algunos FX's si se da X evento en el juego)


  •    ... (etc)


En líneas generales podés tener tu singleton de quien quieras de la manera que gustes (jefe vs empleados , o socios) y estar bien sin necesidad de andar metiendo referencias en cada script (1).
 
 
 
Cita de: digimikeh date=1541347666Gracias por tu explicación, efectivamente hubo un momento en que me iba a inclinar por la 2, pero vi que tendría problemas ya que ese proxy debería sobrevivir a las escenas al igual que media y canvascontroller, y no puedo hacer que sobreviva porque usa herencia, cada escena tiene su propio manager de escena y heredan de ese proxy... sería otro spaghetti mas.
   


Por su puesto que ese "proxy" o en mi ej GameManager debe vivir en todas las escenas, es más todas las escenas son parte del juego. No entendí bien el punto de la herencia. Si cada escena tiene su propio manager de escena quizás estás reinventando la rueda sin razón o diseñandolo erroneamente. Un manager debería ser un contenedor de datos (recursos internos, no configurables en tiempo de Diseño) y de funcionalidades importantes (en lo que va de la vida de la aplicación) que te ofrezca una manera de dialogar entre las entidades del mundo (llamalos GameObjects o actores) y el motor. Dicho esto, en cada escena podés en una línea llamar a la carga de otra escena, eso ya es muy power de parte de Unity (y casi de cualquier motor de hoy en día), pero ponele que te guste llamar a tu Manager de escena porque resulta que Unity pasado mañana decide usar otra API y te cagaría todo el código, bueno no hay problema lo abstraés un poco y listo (tendrías que modificar el código de tu manager y no el de los N scripts), pero este manager debería vivir y estar preparado para dialogar con vos en todo momento que estés sobre una escena (osea siempre), justamente por esto es un manager, es alguien de alta jerarquía (no es un gameObject pichi) que se encarga de lo que le incumbe (claro) y lo podés llamar a toda hora, debería estar preparado para la escena 1 como para la 10.
 
Por eso no me cierra que digas que tenés un manager para cada una, si tenés datos exclusivos o personalizados de cada escena podés enviarlos al manager o este recogerlos de X lugar, es una opción.
 
 
 
Saludos
 
 
 
 

Buenas,
 
En este punto quizás no debas preocuparte por el uso de la memoria sino mas bien en la modularidad y legibilidad del código. Se por experiencia que usar un singleton no es la mejor decisión que se puede tomar y mas tratándose de Unity donde todos los objetos que derivan de la clase MonoBehaviour deben estar en escena. Cuando se programa para Unity una buena practica que ahorra dolores de cabeza es que la mayor cantidad de objetos sean independientes entre sí, es decir yo debo ser capaz de abrir una escena, borrar un objeto y que el resto de funcionalidades del programa se mantengan intactas. ¿Es necesario tener esos 10 objetos en escena? 
 
Saludos :)

Etiquetas: