TL; DR
Esta resposta fica um pouco louca. Mas é porque vejo que você está falando sobre a implementação de suas habilidades como "Comandos", o que implica padrões de design em C ++ / Java / .NET, o que implica uma abordagem pesada de código. Essa abordagem é válida, mas há uma maneira melhor. Talvez você já esteja fazendo o contrário. Se sim, tudo bem. Espero que outros achem útil se for esse o caso.
Veja a Abordagem Orientada a Dados abaixo para ir direto ao ponto. Obtenha o CustomAssetUility de Jacob Pennock aqui e leia seu post sobre isso .
Trabalhando com Unidade
Como outros já mencionaram, percorrer uma lista de 100 a 300 itens não é tão importante quanto você imagina. Portanto, se essa é uma abordagem intuitiva para você, faça isso. Otimize para a eficiência do cérebro. Mas o Dicionário, como o @Norguard demonstrou em sua resposta , é a maneira fácil e sem necessidade de inteligência para eliminar esse problema, já que você obtém inserção e recuperação em tempo constante. Você provavelmente deveria usá-lo.
Em termos de tornar esse trabalho bem dentro do Unity, meu instinto me diz que um MonoBehaviour por habilidade é um caminho perigoso a seguir. Se alguma de suas habilidades mantiver o estado ao longo do tempo e executar, você precisará gerenciar isso e fornecer uma maneira de redefinir esse estado. As corotinas atenuam esse problema, mas você ainda está gerenciando uma referência IEnumerator em todos os quadros de atualização desse script e precisa ter certeza absoluta de que tem uma maneira certa de redefinir as habilidades, a fim de que as lacunas sejam incompletas e presas em um estado As habilidades silenciosamente começam a estragar a estabilidade do seu jogo quando passam despercebidas. "Claro que vou fazer isso!" você diz: "Sou um 'bom programador'!". Mas, realmente, você sabe, somos todos programadores objetivamente terríveis e até os maiores pesquisadores e escritores de compiladores de IA estragam tudo o tempo todo.
De todas as maneiras pelas quais você pode implementar instanciação e recuperação de comando no Unity, posso pensar em duas: uma é boa e não lhe dará um aneurisma, e a outra permite a CRIATIVIDADE MÁGICA NÃO LIMITADA . Tipo de.
Abordagem centrada em código
Primeiro, é uma abordagem principalmente em código. O que eu recomendo é que você torne cada comando uma classe simples que herda de uma classe BaseCommand abtract ou implemente uma interface ICommand (estou assumindo, por uma questão de brevidade, que esses comandos serão apenas habilidades de personagem, não é difícil incorporar outros usos). Este sistema assume que cada comando é um ICommand, possui um construtor público que não aceita parâmetros e requer atualização de cada quadro enquanto está ativo.
As coisas são mais simples se você usar uma classe base abstrata, mas minha versão usa interfaces.
É importante que seus MonoBehaviours encapsulem um comportamento específico, ou um sistema de comportamentos intimamente relacionados. Não há problema em ter muitos MonoBehaviours que efetivamente apenas proxy para classes C # simples, mas se você também estiver fazendo isso, pode atualizar chamadas para todos os tipos de objetos diferentes, a ponto de começar a parecer um jogo XNA, então você ' está com sérios problemas e precisa mudar sua arquitetura.
// 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);
}
}
Isso funciona totalmente bem, mas você pode fazer melhor (também, a List<T>
não é a estrutura de dados ideal para armazenar habilidades cronometradas, você pode querer um LinkedList<T>
ou um SortedDictionary<float, T>
).
Abordagem orientada a dados
Provavelmente, é possível reduzir os efeitos de sua habilidade em comportamentos lógicos que podem ser parametrizados. É para isso que o Unity foi realmente construído. Você, como programador, cria um sistema que você ou um designer pode manipular no editor para produzir uma ampla variedade de efeitos. Isso simplificará muito o "rigging" do código e se concentrará exclusivamente na execução de uma habilidade. Não há necessidade de manipular classes básicas ou interfaces e genéricos aqui. Tudo será puramente orientado a dados (o que também simplifica a inicialização de instâncias de comando).
A primeira coisa que você precisa é de um ScriptableObject que possa descrever suas habilidades. ScriptableObjects são impressionantes. Eles foram projetados para funcionar como MonoBehaviours, pois você pode definir seus campos públicos no inspetor do Unity e essas alterações serão serializadas em disco. No entanto, eles não estão anexados a nenhum objeto e não precisam ser anexados a um objeto do jogo em uma cena ou instanciados. Eles são os blocos de dados abrangentes do Unity. Eles podem serializar tipos básicos, enumerações e classes simples (sem herança) marcadas [Serializable]
. As estruturas não podem ser serializadas no Unity e a serialização é o que permite editar os campos de objeto no inspetor, lembre-se disso.
Aqui está um ScriptableObject que tenta fazer muito. Você pode dividir isso em classes mais serializadas e ScriptableObjects, mas isso deve fornecer apenas uma idéia de como fazê-lo. Normalmente, isso parece feio em uma linguagem moderna e orientada a objetos, como C #, já que realmente parece uma merda de C89 com todas essas enumerações, mas o verdadeiro poder aqui é que agora você pode criar todo tipo de habilidades diferentes sem precisar escrever um novo código para dar suporte eles. E se o seu primeiro formato não fizer o que você precisa, continue adicionando até ele fazer. Desde que você não altere os nomes dos campos, todos os seus arquivos de ativos serializados antigos ainda funcionarão.
// 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();
}
Você pode abstrair ainda mais a seção Dano em uma classe Serializable para poder definir habilidades que causam dano ou curam e têm vários tipos de dano em uma habilidade. A única regra é não herança, a menos que você use vários objetos que possam ser gravados e faça referência aos diferentes arquivos de configuração de danos complexos no disco.
Você ainda precisa do AbilityActivator MonoBehaviour, mas agora ele faz um pouco mais de trabalho.
// 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)
}
}
A parte MAIS FRESCA
Portanto, a interface e os truques genéricos na primeira abordagem funcionarão bem. Mas, para tirar o máximo proveito do Unity, o ScriptableObjects leva você aonde você quer estar. A Unity é excelente, pois fornece um ambiente muito consistente e lógico para os programadores, mas também possui todas as vantagens de entrada de dados para designers e artistas que você obtém da GameMaker, UDK, et. al.
No mês passado, nosso artista utilizou um tipo de script ScriptableObject que deveria definir o comportamento de diferentes tipos de mísseis, combinando-o com um AnimationCurve e um comportamento que fazia com que os mísseis pairassem no chão e fizesse esse novo e louco hóquei em rotação. arma da morte.
Ainda preciso voltar e adicionar suporte específico para esse comportamento para garantir que ele esteja funcionando de maneira eficiente. Mas, como criamos essa interface genérica de descrição de dados, ele conseguiu tirar essa ideia do nada e colocá-la no jogo sem que nós programadores soubéssemos que ele estava tentando fazê-lo até que ele se aproximou e disse: "Ei, pessoal! nessa coisa legal! " E como foi claramente incrível, estou animado para adicionar um suporte mais robusto a ele.