Estou desenvolvendo um jogo que consiste em personagens que possuem habilidades ofensivas únicas e outras habilidades, como construção, reparo etc. Os jogadores podem controlar vários desses personagens.
Estou pensando em colocar todas essas habilidades e habilidades em comandos individuais. Um controlador estático registraria todos esses comandos em uma lista de comandos estáticos. A lista estática consistiria em todas as habilidades disponíveis de todos os personagens do jogo. Portanto, quando um jogador seleciona um dos personagens e clica em um botão na interface do usuário para lançar um feitiço ou executar uma habilidade, o View chama o controlador estático para buscar o comando desejado da lista e executá-lo.
No entanto, não tenho certeza se esse é um bom design, pois estou construindo meu jogo no Unity. Eu acho que poderia ter feito todas as habilidades e componentes como componentes individuais, que seriam anexados aos GameObjects que representam os personagens do jogo. Em seguida, a interface do usuário precisaria conter o GameObject do personagem e, em seguida, executar o comando.
Qual seria um melhor design e prática para um jogo que estou criando?
fonte
Respostas:
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.
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 umLinkedList<T>
ou umSortedDictionary<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.
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.
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.
fonte
TL: DR - se você estiver pensando em incluir centenas ou milhares de habilidades em uma lista / matriz que você percorreria, toda vez que houver uma ação chamada, para ver se a ação existe e se há um personagem que possa execute-o e leia abaixo.
Caso contrário, não se preocupe.
Se você está falando de 6 caracteres / tipos de caracteres e talvez 30 habilidades, então não importa o que você faz, porque a sobrecarga de gerenciar complexidades pode realmente exigir mais código e mais processamento do que simplesmente jogar tudo em uma pilha e Ordenação...
É exatamente por isso que o @eBusiness sugere que é improvável que você veja problemas de desempenho durante o envio do evento, porque, a menos que esteja se esforçando muito para fazê-lo, não há muito trabalho avassalador aqui, comparado à transformação da posição de 3 milhões de vértices na tela, etc ...
Além disso, essa não é a solução , mas uma solução para gerenciar conjuntos maiores de problemas semelhantes ...
Mas...
Tudo se resume ao tamanho do jogo, quantos personagens compartilham as mesmas habilidades, quantos personagens / habilidades diferentes existem, certo?
Ter as habilidades como componentes do personagem, mas tê-las registrando / cancelando o registro em uma interface de comando à medida que os personagens ingressam ou deixam seu controle (ou são nocauteados / etc) ainda faz sentido, de uma maneira muito StarCraft, com teclas de atalho e o cartão de comando.
Eu tive muito pouca experiência com os scripts do Unity, mas estou muito confortável com o JavaScript como idioma.
Se eles permitirem, por que não fazer com que essa lista seja um objeto simples:
E pode ser usado como:
Onde a função Dave (). Init pode parecer:
Se houver mais pessoas do que apenas Dave
.Repair()
, mas você pode garantir que haverá apenas um Dave, altere-o parasystem.notify("register-ability", "dave:repair", this.Repair);
E chame a habilidade usando
system.notify("use-action", "dave:repair");
Não sei como são as listas que você está usando. (Em termos do sistema de tipos UnityScript, E em termos do que está acontecendo após a compilação).
Provavelmente, posso dizer que, se você tem centenas de habilidades que planeja incluir na lista (em vez de registrar e cancelar o registro, com base nos caracteres disponíveis atualmente), iteram por toda uma matriz JS (novamente, se é isso que eles estão fazendo) para verificar uma propriedade de uma classe / objeto, que corresponda ao nome da ação que você deseja executar, terá menos desempenho do que isso.
Se houver estruturas mais otimizadas, elas terão mais desempenho do que isso.
Mas em ambos os casos, agora você tem Personagens que controlam suas próprias ações (dê um passo adiante e os torne componentes / entidades, se desejar), E você tem um sistema de controle que requer um mínimo de iteração (como você está fazendo pesquisas de tabela por nome).
fonte