Estoy diseñando para un juego que consiste en personajes que tienen habilidades ofensivas únicas y otras habilidades como construir, reparar, etc. Los jugadores pueden controlar múltiples de esos personajes.
Estoy pensando en poner todas esas habilidades y habilidades en comandos individuales. Un controlador estático registraría todos estos comandos en una lista de comandos estáticos. La lista estática consistiría en todas las habilidades y habilidades disponibles de todos los personajes del juego. Entonces, cuando un jugador selecciona uno de los personajes y hace clic en un botón en la interfaz de usuario para lanzar un hechizo o realizar una habilidad, la Vista llamará al controlador estático para obtener el comando deseado de la lista y ejecutarlo.
Sin embargo, no estoy seguro de si este es un buen diseño dado que estoy construyendo mi juego en Unity. Creo que podría haber hecho todas las habilidades y destrezas como componentes individuales, que luego se unirían a los GameObjects que representan a los personajes del juego. Luego, la interfaz de usuario necesitaría mantener el GameObject del personaje y luego ejecutar el comando.
¿Cuál sería un mejor diseño y práctica para un juego que estoy diseñando?
Respuestas:
TL; DR
Esta respuesta se vuelve un poco loca. Pero es porque veo que estás hablando de implementar tus habilidades como "Comandos", lo que implica patrones de diseño C ++ / Java / .NET, lo que implica un enfoque de código pesado. Esa aproximación es válida, pero hay una mejor manera. Tal vez ya estás haciendo lo contrario. Si es así, bueno. Esperemos que otros lo encuentren útil si ese es el caso.
Mire el enfoque basado en datos a continuación para ir al grano. Obtenga el CustomAssetUility de Jacob Pennock aquí y lea su publicación al respecto .
Trabajando con la unidad
Como otros han mencionado, atravesar una lista de 100-300 artículos no es tan importante como podría pensar. Entonces, si ese es un enfoque intuitivo para usted, simplemente haga eso. Optimizar para la eficiencia del cerebro. Pero el Diccionario, como lo demostró @Norguard en su respuesta , es la forma fácil de eliminar el problema sin necesidad de capacidad intelectual, ya que se obtiene una inserción y recuperación en tiempo constante. Probablemente deberías usarlo.
En términos de hacer que esto funcione bien en Unity, mi instinto me dice que un MonoBehaviour por habilidad es un camino peligroso para seguir. Si alguna de sus habilidades mantiene el estado a lo largo del tiempo y se ejecutan, deberá administrar eso y proporcionar una forma de restablecer ese estado. Las rutinas alivian este problema, pero aún está administrando una referencia de IEnumerator en cada marco de actualización de ese script, y tiene que asegurarse absolutamente de que tiene una forma segura de restablecer las habilidades para que no sea incompleto y se atasque en un bucle de estado Las habilidades comienzan a arruinar la estabilidad de tu juego cuando pasan desapercibidas. "¡Por supuesto que haré eso!" usted dice: "Soy un 'buen programador'". Pero realmente, ya sabes, todos somos programadores objetivamente terribles e incluso los mejores investigadores de IA y escritores de compiladores arruinan todo el tiempo.
De todas las formas en que podría implementar la instanciación y recuperación de comandos en Unity, puedo pensar en dos: una está bien y no le dará un aneurisma, y la otra permite una CREATIVIDAD MÁGICA SIN LÍMITES . Algo así como.
Enfoque centrado en el código
Primero es un enfoque mayormente en código. Lo que recomiendo es que convierta cada comando en una clase simple que herede de una clase abstracta de BaseCommand o implemente una interfaz ICommand (supongo, por razones de brevedad, que estos comandos solo serán habilidades de personaje, no es difícil de incorporar otros usos). Este sistema asume que cada comando es un ICommand, tiene un constructor público que no toma parámetros, y requiere actualizar cada trama mientras está activo.
Las cosas son más simples si usa una clase base abstracta, pero mi versión usa interfaces.
Es importante que sus MonoBehaviours encapsulen un comportamiento específico o un sistema de comportamientos estrechamente relacionados. Está bien tener muchos MonoBehaviours que efectivamente se deleguen a las clases simples de C #, pero si te encuentras haciendo esto también puedes actualizar las llamadas a todo tipo de objetos diferentes hasta el punto en que empiece a parecerse a un juego XNA, entonces tú ' Estás en serios problemas y necesitas cambiar tu arquitectura.
Esto funciona totalmente bien, pero puede hacerlo mejor (además, a
List<T>
no es la estructura de datos óptima para almacenar habilidades cronometradas, es posible que desee aLinkedList<T>
o aSortedDictionary<float, T>
).Enfoque basado en datos
Probablemente sea posible que pueda reducir los efectos de su habilidad a comportamientos lógicos que puedan parametrizarse. Para esto se construyó realmente Unity. Usted, como programador, diseña un sistema que luego usted o un diseñador pueden manipular en el editor para producir una amplia variedad de efectos. Esto simplificará enormemente la "manipulación" del código y se centrará exclusivamente en la ejecución de una habilidad. No es necesario hacer malabarismos con las clases base o interfaces y genéricos aquí. Todo estará basado únicamente en datos (lo que también simplifica la inicialización de instancias de comandos).
Lo primero que necesita es un ScriptableObject que pueda describir sus habilidades. ScriptableObjects son increíbles. Están diseñados para funcionar como MonoBehaviours, ya que puede configurar sus campos públicos en el inspector de Unity, y esos cambios se serializarán en el disco. Sin embargo, no están unidos a ningún objeto y no tienen que estar unidos a un objeto del juego en una escena o instanciados. Son los cubos de datos generales de Unity. Pueden serializar tipos básicos, enumeraciones y clases simples (sin herencia) marcadas
[Serializable]
. Las estructuras no se pueden serializar en Unity, y la serialización es lo que le permite editar los campos de objetos en el inspector, así que recuerde eso.Aquí hay un ScriptableObject que intenta hacer mucho. Puede dividir esto en clases más serializadas y ScriptableObjects, pero se supone que esto solo le dará una idea de cómo hacerlo. Normalmente esto se ve feo en un agradable lenguaje moderno orientado a objetos como C #, ya que realmente se siente como una mierda de C89 con todas esas enumeraciones, pero el verdadero poder aquí es que ahora puedes crear todo tipo de habilidades diferentes sin tener que escribir código nuevo para soportar ellos. Y si su primer formato no hace lo que necesita hacer, continúe agregando hasta que lo haga. Mientras no cambie los nombres de los campos, todos sus archivos de activos serializados anteriores seguirán funcionando.
Podrías abstraer aún más la sección Daño en una clase Serializable para poder definir habilidades que infligen daño, o sanan, y tienen múltiples tipos de daño en una habilidad. La única regla es no heredar a menos que use varios objetos programables y haga referencia a los diferentes archivos de configuración de daños complejos en el disco.
Todavía necesitas el AbilityActivator MonoBehaviour, pero ahora hace un poco más de trabajo.
La parte más fresca
Entonces, la interfaz y el truco genérico en el primer enfoque funcionarán bien. Pero para realmente aprovechar al máximo Unity, ScriptableObjects lo llevará a donde quiere estar. Unity es excelente, ya que proporciona un entorno muy coherente y lógico para los programadores, pero también tiene todas las características de entrada de datos para diseñadores y artistas que obtienes de GameMaker, UDK, et. Alabama.
El mes pasado, nuestro artista tomó un tipo de Objeto Scriptable que se suponía que definía el comportamiento de diferentes tipos de misiles guiados, lo combinó con un AnimationCurve y un comportamiento que hizo que los misiles se movieran por el suelo, e hizo este nuevo y loco disco de hockey giratorio. arma de la muerte.
Todavía necesito regresar y agregar soporte específico para este comportamiento para asegurarme de que se ejecuta de manera eficiente. Pero debido a que creamos esta interfaz genérica de descripción de datos, fue capaz de sacar esta idea de la nada y ponerla en el juego sin que los programadores supiéramos que estaba tratando de hacerlo hasta que vino y dijo: "Hola chicos, miren a esta cosa genial! Y debido a que fue claramente increíble, estoy emocionado de poder agregarle un soporte más robusto.
fuente
TL: DR: si está pensando en incorporar cientos o miles de habilidades en una lista / matriz que luego iteraría, cada vez que se llame una acción, para ver si la acción existe y si hay un personaje que pueda realizarlo, luego lea a continuación.
Si no, no te preocupes por eso.
Si estás hablando de 6 personajes / tipos de personajes y tal vez 30 habilidades, entonces realmente no importará lo que hagas, porque la sobrecarga de gestionar las complejidades en realidad podría requerir más código y más procesamiento que simplemente tirar todo en una pila y clasificación...
Es por eso que @eBusiness sugiere que es poco probable que vea problemas de rendimiento durante el envío de eventos, porque a menos que esté tratando de hacerlo, no hay mucho trabajo abrumador aquí, en comparación con la transformación de la posición de 3- millones de vértices en pantalla, etc.
Además, esta no es la solución , sino una solución para gestionar conjuntos más grandes de problemas similares ...
Pero...
Todo se reduce a qué tan grande estás haciendo el juego, cuántos personajes comparten las mismas habilidades, cuántos personajes diferentes / habilidades diferentes hay, ¿verdad?
Tener las habilidades como componentes del personaje, pero hacer que se registren / anulen el registro desde una interfaz de comando a medida que los personajes se unen o dejan su control (o quedan eliminados / etc.) todavía tiene sentido, de una manera muy StarCraft, con teclas de acceso rápido y La tarjeta de comando.
He tenido muy, muy poca experiencia con los scripts de Unity, pero estoy muy cómodo con JavaScript como lenguaje.
Si lo permiten, ¿por qué no hacer que esa lista sea un simple objeto?
Y podría usarse como:
Donde la función Dave (). Init podría verse así:
Si tiene más personas que solo Dave
.Repair()
, pero puede garantizar que solo habrá un Dave, entonces cámbielo asystem.notify("register-ability", "dave:repair", this.Repair);
Y llama a la habilidad usando
system.notify("use-action", "dave:repair");
No estoy seguro de cómo son las listas que está utilizando. (En términos del sistema de tipos UnityScript, Y en términos de lo que sucede después de la compilación).
Probablemente puedo decir que si tiene cientos de habilidades que planeaba simplemente incluir en la lista (en lugar de registrarse y anular el registro, en función de los caracteres que tiene actualmente disponibles), eso iterando a través de toda una matriz JS (nuevamente, si eso es lo que están haciendo) para verificar una propiedad de una clase / objeto, que coincida con el nombre de la acción que desea realizar, será menos eficaz que esto.
Si hay estructuras más optimizadas, entonces serán más eficaces que esto.
Pero en cualquier caso, ahora tiene Personajes que controlan sus propias acciones (vaya un paso más allá y conviértalos en componentes / entidades, si lo desea), Y tiene un sistema de control que requiere un mínimo de iteración (ya que solo haciendo búsquedas de tablas por nombre).
fuente