Hace algún tiempo comencé a trabajar con Unity y todavía lucho con el tema de los scripts estrechamente acoplados. ¿Cómo puedo estructurar mi código para evitar este problema?
Por ejemplo:
Quiero tener sistemas de salud y muerte en guiones separados. También quiero tener diferentes guiones de caminata intercambiables que me permitan cambiar la forma en que se mueve el personaje del jugador (controles inerciales basados en la física como en Mario versus controles apretados y nerviosos como en Super Meat Boy). El script de Salud debe contener una referencia al script de Muerte, para que pueda activar el método Die () cuando la salud de los jugadores alcance 0. El script de Muerte debe contener alguna referencia al script de caminar utilizado, para deshabilitar caminar sobre la muerte (I Estoy cansado de zombies).
Yo normalmente crear interfaces, como IWalking
, IHealth
y IDeath
, de modo que pueda cambiar estos elementos en un capricho sin romper el resto de mi código. Me gustaría tenerlos configurados por un guión separado en el objeto jugador, por ejemplo PlayerScriptDependancyInjector
. Tal vez ese guión tendría públicas IWalking
, IHealth
y IDeath
atributos, de manera que las dependencias pueden ser establecidas por el diseñador de niveles del inspector de arrastrar y soltar los scripts apropiados.
Eso me permitiría simplemente agregar comportamientos a los objetos del juego fácilmente y no preocuparme por las dependencias codificadas.
El problema en la unidad
El problema es que en Unity no puedo exponer interfaces en el inspector, y si escribo mis propios inspectores, las referencias no se serializarán, y es un trabajo innecesario. Es por eso que me queda escribir código estrechamente acoplado. Mi Death
guión expone una referencia a un InertiveWalking
guión. Pero si decido que quiero que el personaje del jugador controle estrictamente, no puedo simplemente arrastrar y soltar el TightWalking
guión, necesito cambiar el Death
guión. Eso apesta. Puedo lidiar con eso, pero mi alma llora cada vez que hago algo así.
¿Cuál es la alternativa preferida a las interfaces en Unity? ¿Cómo soluciono este problema? Encontré esto , pero me dice lo que ya sé, ¡y no me dice cómo hacerlo en Unity! Esto también analiza lo que debe hacerse, no cómo, no aborda el problema del acoplamiento estrecho entre scripts.
En general, creo que están escritos para personas que vinieron a Unity con experiencia en diseño de juegos y que simplemente aprenden a codificar, y hay muy pocos recursos en Unity para desarrolladores habituales. ¿Hay alguna forma estándar de estructurar su código en Unity, o tengo que descubrir mi propio método?
The problem is that in Unity I can't expose interfaces in the inspector
No creo entender lo que quieres decir con "exponer la interfaz en el inspector" porque mi primer pensamiento fue "¿por qué no?"Respuestas:
Hay algunas formas en que puede trabajar para evitar el acoplamiento apretado de scripts. Internal to Unity es una función SendMessage que, cuando se dirige a un GameObject de Monobehaviour, envía ese mensaje a todo el objeto del juego. Entonces, podría tener algo como esto en su objeto de salud:
Esto está realmente simplificado, pero debería mostrar exactamente a qué me refiero. La propiedad arroja el mensaje como si fuera un evento. Su secuencia de comandos de muerte interceptaría este mensaje (y si no hay nada para interceptarlo, SendMessageOptions.DontRequireReceiver garantizará que no reciba una excepción) y realice acciones basadas en el nuevo valor de salud después de que se haya configurado. Al igual que:
Sin embargo, no hay garantía de orden en esto. En mi experiencia, siempre va desde el guión más alto en un GameObject hasta el guión más bajo, pero no confiaría en nada que no puedas observar por ti mismo.
Hay otras soluciones que podría investigar, que es construir una función GameObject personalizada que obtenga Componentes y solo devuelva aquellos componentes que tienen una interfaz específica en lugar de Tipo, tomaría un poco más de tiempo pero podría servir a sus propósitos.
Además, puede escribir un editor personalizado que verifique que un objeto se asigne a una posición en el editor para esa interfaz antes de comprometer realmente la asignación (en este caso, estaría asignando el script como normal permitiendo que se serialice, pero arrojando un error si no usó la interfaz correcta y denegó la asignación). He hecho ambos antes y puedo producir ese código, pero necesitaré algo de tiempo para encontrarlo primero.
fuente
Yo personalmente NUNCA uso SendMessage. Todavía hay una dependencia entre sus componentes con SendMessage, se muestra muy mal y es fácil de romper. El uso de interfaces y / o delegados realmente elimina la necesidad de usar SendMessage (lo cual es más lento, aunque eso no debería ser una preocupación hasta que sea necesario).
http://forum.unity3d.com/threads/free-vfw-full-set-of-drawers-savesystem-serialize-interfaces-generics-auto-props-delegates.266165/
Usa las cosas de este chico. Proporciona un montón de cosas de editor como interfaces expuestas Y delegados. Hay un montón de cosas como esta, o incluso puedes hacer la tuya. Hagas lo que hagas, NO comprometas tu diseño debido a la falta de soporte de script de Unity. Estas cosas deberían estar en Unity por defecto.
Si esto no es una opción, arrastre y suelte las clases de monoconductas en su lugar y vuélvalas dinámicamente a la interfaz requerida. Es más trabajo y menos elegante, pero hace el trabajo.
fuente
Un enfoque específico de Unity que se me ocurre sería inicialmente
GetComponents<MonoBehaviour>()
obtener una lista de scripts y luego convertir esos scripts en variables privadas para las interfaces específicas. Desea hacer esto en Inicio () en la secuencia de comandos del inyector de dependencia. Algo como:De hecho, con este método incluso podría hacer que los componentes individuales le digan al script de inyección de dependencia cuáles son sus dependencias, utilizando el comando typeof () y el tipo 'Tipo' . En otras palabras, los componentes le dan al inyector de dependencia una lista de tipos, y luego el inyector de dependencia devuelve una lista de objetos de esos tipos.
Alternativamente, podría usar clases abstractas en lugar de interfaces. Una clase abstracta puede lograr prácticamente el mismo propósito que una interfaz, y puede hacer referencia a eso en el Inspector.
fuente
GetComponent<IMyInterface>()
yGetComponents<IMyInterface>()
directamente, lo que devuelve los componentes que lo implementan.Desde mi propia experiencia, el desarrollo de juegos tradicionalmente implica un enfoque más pragmático que el desarrollo industrial (con menos capas de abstracción). En parte porque desea favorecer el rendimiento sobre la capacidad de mantenimiento, y también porque es menos probable que reutilice su código en otros contextos (generalmente hay un fuerte acoplamiento intrínseco entre los gráficos y la lógica del juego)
Entiendo totalmente su preocupación, especialmente porque la preocupación por el rendimiento es menos crítica con los dispositivos recientes, pero creo que tendrá que desarrollar sus propias soluciones si desea aplicar las mejores prácticas industriales de OOP al desarrollo de juegos de Unity (como la inyección de dependencia, etc ...)
fuente
Eche un vistazo a IUnified ( http://u3d.as/content/wounded-wolf/iunified/5H1 ), que le permite exponer interfaces en el editor, tal como lo menciona. Hay un poco más de cableado que hacer en comparación con la declaración de variable normal, eso es todo.
Además, como mencionan otras respuestas, el uso de SendMessage no elimina el acoplamiento. En cambio, hace que no se produzca un error en tiempo de compilación. Muy malo: a medida que amplías tu proyecto, puede convertirse en una pesadilla para mantener.
Si desea desacoplar su código sin usar la interfaz en el editor, puede usar un sistema basado en eventos fuertemente tipado (o incluso uno tipado en realidad, pero de nuevo, más difícil de mantener). He basado el mío en esta publicación , que es muy conveniente como punto de partida. Tenga en cuenta la creación de un montón de eventos, ya que podría desencadenar el GC. En cambio, solucioné el problema con un grupo de objetos, pero eso es principalmente porque trabajo con dispositivos móviles.
fuente
Fuente: http://www.udellgames.com/posts/ultra-useful-unity-snippet-developers-use-interfaces/
fuente
Utilizo algunas estrategias diferentes para sortear las limitaciones que mencionas.
Uno de los que no veo mencionado es usar un
MonoBehavior
que expone el acceso a las diferentes implementaciones de una interfaz determinada. Por ejemplo, tengo una interfaz que define cómo se implementan los Raycasts llamadaIRaycastStrategy
Luego lo implemento en diferentes clases con clases como
LinecastRaycastStrategy
ySphereRaycastStrategy
e implemento un MonoBehaviorRaycastStrategySelector
que expone una sola instancia de cada tipo. Algo asíRaycastStrategySelectionBehavior
, que se implementa algo como:Si es probable que las implementaciones hagan referencia a las funciones de Unity en sus construcciones (o simplemente para estar del lado seguro, me olvidé de esto al escribir este ejemplo), use
Awake
para evitar problemas al llamar a los constructores o hacer referencia a las funciones de Unity en el editor o fuera de la página principal hiloEsta estrategia tiene algunos inconvenientes, especialmente cuando tiene que administrar muchas implementaciones diferentes que están cambiando rápidamente. Los problemas con la falta de verificación en tiempo de compilación pueden solucionarse utilizando un editor personalizado, ya que el valor de enumeración se puede serializar sin ningún trabajo especial (aunque generalmente lo olvido, ya que mis implementaciones no tienden a ser tan numerosas).
Utilizo esta estrategia debido a la flexibilidad que le brinda al estar basado en MonoBehaviors sin obligarlo a implementar las interfaces usando MonoBehaviors. Esto le brinda "lo mejor de ambos mundos", ya que se puede acceder al Selector utilizando
GetComponent
, la serialización es manejada por Unity y el Selector puede aplicar la lógica en tiempo de ejecución para cambiar automáticamente las implementaciones en función de ciertos factores.fuente