TL; DR
Cette réponse devient un peu folle. Mais c'est parce que je vois que vous parlez d'implémenter vos capacités en tant que «commandes», ce qui implique des modèles de conception C ++ / Java / .NET, ce qui implique une approche lourde de code. Cette approche est valable, mais il existe un meilleur moyen. Vous faites peut-être déjà l'inverse. Si oui, eh bien. J'espère que d'autres trouveront cela utile si c'est le cas.
Regardez l'approche basée sur les données ci-dessous pour aller droit au but. Obtenez la fonctionnalité CustomAssetUility de Jacob Pennock ici et lisez son article à ce sujet .
Travailler avec Unity
Comme d'autres l'ont mentionné, parcourir une liste de 100 à 300 éléments n'est pas aussi important que vous ne le pensez. Donc, si c'est une approche intuitive pour vous, faites-le. Optimiser pour l'efficacité du cerveau. Mais le dictionnaire, comme @Norguard l'a démontré dans sa réponse , est le moyen facile sans cervelle nécessaire pour éliminer ce problème puisque vous obtenez une insertion et une récupération à temps constant. Vous devriez probablement l'utiliser.
Pour ce qui est de bien fonctionner dans Unity, mon instinct me dit qu'un comportement unique par capacité est un chemin dangereux à suivre. Si l'une de vos capacités maintient son état au fil du temps, elle devra être gérée et fournir un moyen de réinitialiser cet état. Les coroutines atténuent ce problème, mais vous gérez toujours une référence IEnumerator sur chaque cadre de mise à jour de ce script, et vous devez absolument vous assurer d'avoir un moyen sûr de réinitialiser les capacités de peur que la boucle soit incomplète et coincée dans un état. les capacités commencent tranquillement à gâcher la stabilité de votre jeu quand elles passent inaperçues. "Bien sûr que je vais faire ça!" vous dites: "Je suis un" bon programmeur "!". Mais vraiment, vous savez, nous sommes tous des programmeurs objectivement terribles et même les plus grands chercheurs en IA et rédacteurs de compilateurs bousillent tout le temps.
De toutes les façons dont vous pouvez implémenter l'instanciation et la récupération de commandes dans Unity, je peux en penser à deux: l'une va bien et ne vous donnera pas d'anévrisme, et l'autre permet une CRÉATIVITÉ MAGIQUE NON LIMITÉE . Sorte de.
Approche centrée sur le code
La première est une approche principalement dans le code. Ce que je recommande, c'est que vous fassiez de chaque commande une classe simple qui hérite d'une classe abtract BaseCommand ou implémente une interface ICommand (je suppose par souci de concision que ces commandes ne seront que des capacités de caractère, il n'est pas difficile à incorporer autres utilisations). Ce système suppose que chaque commande est une ICommand, possède un constructeur public qui ne prend aucun paramètre et nécessite de mettre à jour chaque trame pendant qu'elle est active.
Les choses sont plus simples si vous utilisez une classe de base abstraite, mais ma version utilise des interfaces.
Il est important que vos comportements uniques encapsulent un comportement spécifique ou un système de comportements étroitement liés. Il est normal d'avoir beaucoup de MonoBehaviours qui procurent simplement un proxy vers des classes C # simples, mais si vous vous trouvez en train de faire trop, vous pouvez mettre à jour les appels à toutes sortes d'objets différents au point où cela commence à ressembler à un jeu XNA, alors vous '' re en difficulté et besoin de changer votre architecture.
// 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);
}
}
Cela fonctionne parfaitement, mais vous pouvez faire mieux (aussi, a List<T>
n'est pas la structure de données optimale pour stocker des capacités chronométrées, vous voudrez peut-être un LinkedList<T>
ou un SortedDictionary<float, T>
).
Approche axée sur les données
Il est probablement possible que vous réduisiez les effets de votre capacité en comportements logiques qui peuvent être paramétrés. C'est pour cela que Unity a vraiment été conçu. En tant que programmeur, vous concevez un système que vous ou un concepteur pouvez ensuite manipuler dans l'éditeur pour produire une grande variété d'effets. Cela simplifiera grandement le "truquage" du code et se concentrera exclusivement sur l'exécution d'une capacité. Pas besoin de jongler avec les classes de base ou les interfaces et les génériques ici. Tout sera purement piloté par les données (ce qui simplifie également l'initialisation des instances de commande).
La première chose dont vous avez besoin est un ScriptableObject qui peut décrire vos capacités. ScriptableObjects est génial. Ils sont conçus pour fonctionner comme MonoBehaviours dans la mesure où vous pouvez définir leurs champs publics dans l'inspecteur d'Unity, et ces modifications seront sérialisées sur le disque. Cependant, ils ne sont attachés à aucun objet et n'ont pas besoin d'être attachés à un objet de jeu dans une scène ou instanciés. Ce sont les ensembles de données fourre-tout de Unity. Ils peuvent sérialiser les types de base, les énumérations et les classes simples (sans héritage) marquées [Serializable]
. Les structures ne peuvent pas être sérialisées dans Unity, et la sérialisation vous permet de modifier les champs d'objet dans l'inspecteur, alors n'oubliez pas cela.
Voici un ScriptableObject qui essaie de faire beaucoup. Vous pouvez répartir cela en classes plus sérialisées et en objets scriptables, mais cela est censé vous donner simplement une idée de la façon de procéder. Normalement, cela semble moche dans un joli langage orienté objet moderne comme C #, car il ressemble vraiment à de la merde C89 avec toutes ces énumérations, mais le vrai pouvoir ici est que maintenant vous pouvez créer toutes sortes de capacités différentes sans jamais écrire de nouveau code à prendre en charge leur. Et si votre premier format ne fait pas ce dont vous avez besoin, continuez à y ajouter jusqu'à ce qu'il le fasse. Tant que vous ne modifiez pas les noms de champs, tous vos anciens fichiers d'actifs sérialisés fonctionneront toujours.
// 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();
}
Vous pouvez en outre résumer la section Dommages dans une classe sérialisable afin de définir des capacités qui infligent des dégâts ou guérissent, et ont plusieurs types de dégâts dans une seule capacité. La seule règle n'est pas d'héritage sauf si vous utilisez plusieurs objets scriptables et référencez les différents fichiers de configuration de dommages complexes sur le disque.
Vous avez toujours besoin du comportement unique AbilityActivator, mais maintenant il fait un peu plus de travail.
// 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 partie la plus COOL
Ainsi, l'interface et la supercherie générique dans la première approche fonctionneront bien. Mais pour tirer le meilleur parti d'Unity, ScriptableObjects vous amènera où vous voulez être. Unity est génial en ce qu'il fournit un environnement très cohérent et logique pour les programmeurs, mais a également toutes les subtilités de saisie de données pour les concepteurs et les artistes que vous obtenez de GameMaker, UDK, et. Al.
Le mois dernier, notre artiste a pris un powerup ScriptableObject type qui était censé définir le comportement de différents types de missiles à tête chercheuse, l'a combiné avec une AnimationCurve et un comportement qui a fait planer des missiles sur le sol, et a créé cette nouvelle folle rondelle de hockey-hockey-puck- arme mortelle.
J'ai encore besoin de revenir en arrière et d'ajouter un support spécifique pour ce comportement pour m'assurer qu'il fonctionne efficacement. Mais parce que nous avons créé cette interface de description de données générique, il a réussi à tirer cette idée de nulle part et à la mettre dans le jeu sans que nous, les programmeurs, sachions qu'il essayait de le faire jusqu'à ce qu'il vienne et dit: "Hé les gars, regardez à cette chose cool! " Et parce que c'était clairement génial, je suis ravi d'y ajouter un support plus robuste.