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.
// ICommand.cs
public interface ICommand
{
public void Execute(AbilityActivator originator, TargetingInfo targets);
public void Update();
public bool IsActive { get; }
}
// CommandList.cs
// Attach this to a game object in your loading screen
public static class CommandList
{
public static ICommand GetInstance(string key)
{
return commandDict[key].GetRef();
}
static CommandListInitializerScript()
{
commandDict = new Dictionary<string, ICommand>() {
{ "SwordSpin", new CommandRef<SwordSpin>() },
{ "BellyRub", new CommandRef<BellyRub>() },
{ "StickyShield", new CommandRef<StickyShield>() },
// Add more commands here
};
}
private class CommandRef<T> where T : ICommand, new()
{
public ICommand GetNew()
{
return new T();
}
}
private static Dictionary<string, ICommand> commandDict;
}
// AbilityActivator.cs
// Attach this to your character objects
public class AbilityActivator : MonoBehaviour
{
List<ICommand> activeAbilities = new List<ICommand>();
void Update()
{
string activatedAbility = GetActivatedAbilityThisFrame();
if (!string.IsNullOrEmpty(acitvatedAbility))
ICommand command = CommandList.Get(activatedAbility).GetRef();
command.Execute(this, this.GetTargets());
activeAbilities.Add(command);
}
foreach (var ability in activeAbilities) {
ability.Update();
}
activeAbilities.RemoveAll(a => !a.IsActive);
}
}
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 a LinkedList<T>
o a SortedDictionary<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.
// CommandAbilityDescription.cs
public class CommandAbilityDecription : ScriptableObject
{
// Identification and information
public string displayName; // Name used for display purposes for the GUI
// We don't need an identifier field, because this will actually be stored
// as a file on disk and thus implicitly have its own identifier string.
// Description of damage to targets
// I put this enum inside the class for answer readability, but it really belongs outside, inside a namespace rather than nested inside a class
public enum DamageType
{
None,
SingleTarget,
SingleTargetOverTime,
Area,
AreaOverTime,
}
public DamageType damageType;
public float damage; // Can represent either insta-hit damage, or damage rate over time (depend)
public float duration; // Used for over-time type damages, or as a delay for insta-hit damage
// Visual FX
public enum EffectPlacement
{
CenteredOnTargets,
CenteredOnFirstTarget,
CenteredOnCharacter,
}
[Serializable]
public class AbilityVisualEffect
{
public EffectPlacement placement;
public VisualEffectBehavior visualEffect;
}
public AbilityVisualEffect[] visualEffects;
}
// VisualEffectBehavior.cs
public abtract class VisualEffectBehavior : MonoBehaviour
{
// When an artist makes a visual effect, they generally make a GameObject Prefab.
// You can extend this base class to support different kinds of visual effects
// such as particle systems, post-processing screen effects, etc.
public virtual void PlayEffect();
}
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.
// AbilityActivator.cs
public class AbilityActivator : MonoBehaviour
{
public void ActivateAbility(string abilityName)
{
var command = (CommandAbilityDescription) Resources.Load(string.Format("Abilities/{0}", abilityName));
ProcessCommand(command);
}
private void ProcessCommand(CommandAbilityDescription command)
{
foreach (var fx in command.visualEffects) {
fx.PlayEffect();
}
switch(command.damageType) {
// yatta yatta yatta
}
// and so forth, whatever your needs require
// You could even make a copy of the CommandAbilityDescription
var myCopy = Object.Instantiate(command);
// So you can keep track of state changes (ie: damage duration)
}
}
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.