paint-brush
Um sistema de habilidades mais complexo, mas um fluxo de trabalho de desenvolvimento de jogo mais suave – como?por@pastaman
1,150 leituras
1,150 leituras

Um sistema de habilidades mais complexo, mas um fluxo de trabalho de desenvolvimento de jogo mais suave – como?

por Vladimir Popov9m2024/02/08
Read on Terminal Reader

Muito longo; Para ler

O sistema de habilidades dos War Robots passou por uma evolução significativa, passando de uma estrutura simplista para um modelo sofisticado. O novo sistema capacita os designers de jogos e reduz a dependência dos programadores, resultando em habilidades mecânicas mais diversificadas e experiências de jogador enriquecidas.
featured image - Um sistema de habilidades mais complexo, mas um fluxo de trabalho de desenvolvimento de jogo mais suave – como?
Vladimir Popov HackerNoon profile picture


Olá! Sou Vladimir Popov, desenvolvedor cliente do projeto War Robots. No momento em que este artigo foi escrito, os War Robots já existiam há vários anos e dezenas de novos mechs apareceram no jogo durante esse período. Naturalmente, as variadas habilidades dos robôs são importantes, pois, sem elas, os robôs perdem a sua singularidade o que torna o jogo mais interessante.


Neste post, compartilharei como funciona o sistema de habilidades de jogo em nosso jogo – e como ele evoluiu. E, para tornar as coisas mais acessíveis, explicarei as coisas em termos simples e sem muitos detalhes técnicos.


Como as habilidades costumavam ser implementadas

Primeiro, vamos mergulhar no histórico do projeto e observar uma implementação mais antiga que não está mais sendo usada.


Anteriormente, as habilidades eram projetadas de uma forma muito trivial: elas tinham um componente que era anexado ao robô. Esta foi uma construção onde o programador descreveu detalhadamente como a habilidade funciona: tanto seu fluxo quanto como ela interage com outras habilidades. Toda a lógica é descrita dentro de um componente, e o designer do jogo pode simplesmente anexá-lo ao robô e configurar os parâmetros conforme necessário. Vale ressaltar que não foi possível alterar o fluxo de habilidades – os designers do jogo só puderam alterar parâmetros e tempos.


Uma habilidade antiga só poderia existir em dois estados: ativo e inativo, e cada estado poderia ter sua ação atribuída a ela.




Vejamos um exemplo da habilidade “Jammer”, que o robô “Stalker” possuía anteriormente; funcionou assim:


  1. Se a habilidade estiver ativa, uma animação é exibida e o robô entra no estado Jammer; neste estado, outros não podem mirar no robô ativando a habilidade.
  2. Se a habilidade estiver inativa, nada acontece.
  3. Ao tentar ativar uma habilidade, verificamos se mais de n segundos se passaram desde a última ativação.
  4. A desativação ocorreu automaticamente após m segundos.


Durante muito tempo, esta funcionalidade foi suficiente para nós, mas com o tempo tanto os designers de jogos como os programadores já não estavam satisfeitos com esta abordagem: era difícil para os programadores suportar estas capacidades porque o código se tinha tornado monstruoso; isto envolvia uma cadeia de legados muito longa, onde cada situação tinha que ser descrita. Além disso, faltava flexibilidade aos designers de jogos – para fazer qualquer alteração em uma habilidade, eles tinham que solicitar modificações aos programadores, mesmo que uma habilidade adjacente tivesse a mesma funcionalidade.


Entre no novo sistema de habilidades

Então, percebemos que algo precisava mudar. Portanto, desenvolvemos um novo sistema, onde cada habilidade era representada como um conjunto de diversos objetos relacionados. A funcionalidade foi dividida em habilidades de estado e componentes de estado.


Como funciona? Toda habilidade tem um objetivo principal. Este objeto central conecta outros objetos de habilidade com o mundo exterior e vice-versa; também toma todas as decisões principais.


Pode haver qualquer número de estados. Essencialmente, o estado nesta iteração não é muito diferente dos estados ativo/inativo da versão antiga, mas agora pode haver qualquer número deles e seu propósito se tornou mais abstrato. Notaremos que uma habilidade só pode ter um estado ativo por vez.


A principal inovação em relação ao sistema antigo foram os componentes: um componente descreve alguma ação e cada estado pode ter qualquer número de componentes.


Como funcionam as novas habilidades? Uma habilidade só pode estar em um dos estados; o objeto principal é responsável por trocá-los. Os componentes que se vinculam a um estado reagem à ativação/desativação do estado e, dependendo disso, podem começar a realizar alguma ação ou parar de executá-la.


Todos os objetos tornaram-se personalizáveis; um designer de jogos pode misturar estados e componentes como desejar, compondo assim uma nova habilidade a partir de blocos pré-instalados. Agora, os programadores só precisam entrar na imagem para criar um novo componente ou estado, o que torna a escrita de código muito mais fácil: eles trabalham com pequenas entidades, descrevem alguns elementos simples e não constroem mais a habilidade sozinhos – os designers de jogos fazem isso agora.


O fluxo ficou assim:

  1. O objeto principal ativa o primeiro estado
  2. O estado ativa todos os seus componentes
  3. O estado determina o momento em que a habilidade é trocada para outro estado
  4. O objeto principal desativa o estado anterior
  5. O estado anterior desativa seus componentes
  6. O objeto principal ativa um novo estado
  7. O novo estado ativa seus componentes


Posteriormente, este procedimento é repetido continuamente. Para facilitar o uso, um estado não serve apenas como um contêiner de componentes, mas também determina quando mudar para outro estado e solicita que o objeto principal faça a mudança. Com o tempo, isso ainda não foi suficiente para nós, e o diagrama de habilidades foi transformado no seguinte:




O objeto principal, o estado e os componentes permaneceram em seus lugares, mas novos elementos também foram adicionados.


A primeira coisa que chama a atenção é que adicionamos condições a cada estado e componente: para os estados, elas definem requisitos adicionais para sair do estado; para componentes, eles determinam se o componente pode executar sua ação.


O contêiner de cobrança contém cobranças, recarrega-as, interrompe a recarga se necessário e fornece cobranças para uso dos estados.


Um temporizador é usado quando vários estados devem ter um tempo de execução comum, mas seu próprio tempo de execução não está definido.


É importante observar que todos os objetos de habilidade são opcionais. Tecnicamente, para poder funcionar, são necessários apenas um objeto principal e um estado.


Agora, embora não existam tantas habilidades inteiramente construídas sem o envolvimento dos programadores, o desenvolvimento em geral tornou-se visivelmente mais barato, porque os programadores agora só precisam escrever coisas muito pequenas: por exemplo, um novo estado ou dois componentes – o resto é reutilizado.


Vamos resumir os componentes de nossas habilidades:


  • O objeto principal executa as funções de uma máquina de estados. Ele fornece aos estados e componentes informações sobre o mundo e fornece ao mundo informações sobre habilidades. O objeto principal serve como um elo entre estados, componentes e partes de serviço da capacidade: cargas e temporizadores externos.


  • O estado escuta os comandos de ativação e desativação do objeto principal e, consequentemente, ativa e desativa componentes, e também solicita que o objeto principal mude para outro estado. O estado determina quando é necessário mudar para o próximo; para isso, utiliza sua condição interna: se o jogador clicou no botão de habilidade, se passou um certo tempo desde a ativação do estado, e assim por diante, e condições externas ligadas ao estado.


  • O componente escuta comandos de ativação e desativação do estado e executa alguma ação: discreta ou de longo prazo. As ações podem ser completamente diferentes: podem causar dano, curar um aliado, ativar uma animação e assim por diante.


  • A condição verifica em que estado o elemento desejado está e relata isso ao estado ou componente. As condições podem ser complexas. Um estado não solicita uma transição para outro estado se a condição não for atendida. O componente também não executa uma ação se a condição não for atendida. As condições são uma entidade opcional; nem toda habilidade as possui.


  • O contêiner de cobrança retém as cobranças, recarrega-as, interrompe a recarga quando necessário e fornece cobranças aos estados. É usado em habilidades de múltiplas cargas, quando é necessário permitir que o jogador o use várias vezes, mas não mais do que n vezes seguidas.


  • O cronômetro é utilizado quando vários estados têm uma duração comum, mas não se sabe quanto tempo durará cada um deles. Qualquer estado pode iniciar um cronômetro por n segundos. Todos os estados relevantes assinam o evento de término do cronômetro e fazem algo quando ele termina.


Agora vamos voltar ao diagrama de habilidades. Como sua funcionalidade mudou?


  1. No início do jogo, o objeto principal seleciona o primeiro estado e o ativa
  2. O estado ativa todos os seus componentes
  3. O componente verifica se a condição foi atendida e só então executa uma ação
  4. O estado começa a verificar a condição de transição para outro estado
  5. Se a condição for atendida e a condição adicional vinculada a ela for satisfeita, o estado solicita que o objeto principal seja transferido para outro estado
  6. O objeto principal desativa este estado e ativa outro
  7. Todo o procedimento é repetido


Os estados podem usar cobranças como condição adicional de transição. Se tal transição ocorrer, o número de cobranças diminui. Os estados também podem usar um cronômetro comum. Neste caso, o tempo total para sua execução será determinado por um cronômetro, e cada estado individualmente pode durar qualquer tempo.


UIs de habilidade

Não reinventamos completamente a roda para as novas interfaces de habilidade.


O objeto principal possui sua própria UI. Ele define alguns elementos que devem estar sempre na UI e que não dependem do estado atualmente ativo.


Cada estado tem seu próprio par na UI, e a UI do estado é exibida somente quando seu estado está ativo. Ele recebe dados sobre seu estado e pode exibi-los de uma forma ou de outra. Por exemplo, os estados de duração geralmente têm uma barra e um texto na interface do usuário que exibe o tempo restante.


No caso em que o estado está aguardando um comando externo para continuar uma habilidade, sua UI exibe um botão e pressioná-lo envia o comando para o estado.





Exemplos de habilidades

Veremos como as habilidades funcionam usando exemplos específicos; primeiro, vamos dar uma olhada em um robô chamado “Inquisidor”. Temos quatro estados que se sucedem – acima dos estados você pode ver sua exibição na IU. Para dois deles, vemos também os componentes que lhes pertencem; os outros dois estados simplesmente não possuem componentes.


Aqui está o fluxo da habilidade:

  1. Tudo começa com o estado “WaitForClick”. Neste momento, a habilidade não faz nada; apenas espera por comandos.


  2. Assim que tal comando é recebido, o objeto principal muda de estado. O próximo estado ativo é “WaitForGrounded”.


  3. Este estado possui alguns componentes e, portanto, quando ativado, o robô salta e reproduz um som e uma animação. Entre outras coisas, enquanto o estado está ativo, o robô é afetado pelo efeito Jammer, que proíbe mirar no robô.


  4. Quando o robô pousa, sua habilidade passa para o próximo estado.


  5. Este estado possui três componentes: o já familiar Sound e Jammer, além do Shake, que faz a câmera tremer para todos os jogadores em um raio de n .


  6. Como este estado tem Duration , ele funciona por n segundos, então a habilidade passa para o próximo estado.


  7. O último estado também vem com uma Duração, mas não possui nenhum componente: está em um tempo de espera normal.


  8. Após a conclusão, a habilidade retorna ao primeiro estado.





Outro exemplo é “Fantasma”. É muito parecido com o Inquisidor, mas existem algumas nuances:


  1. Começamos com WaitForClick.


  2. Em seguida, a Duração, na qual o teletransporte é instalado, as estatísticas do mech são alteradas e o som e a animação são reproduzidos.


  3. Depois disso: DurationOrClick, no qual as estatísticas do mech são alteradas, a animação e os efeitos são reproduzidos.


  4. Se for feito um clique, passamos para outra Duração, na qual o mech se teletransporta, as estatísticas mudam e a animação, efeitos e sons são reproduzidos.


  5. Após esse estado (ou após o tempo expirar para DurationOrClick), passamos para Duration.


A principal diferença aqui é que vemos estados com ramificação: DurationOrClick vai para o estado A se o tempo especificado tiver passado, ou para o estado B se o jogador já pressionou o botão de habilidade.



Conclusões

Embora pareça que o nosso sistema evoluiu de algo simples para algo bastante complexo, esta mudança simplificou a vida tanto dos programadores como dos designers de jogos. A assistência dos programadores é agora necessária principalmente ao adicionar pequenos componentes, enquanto o último grupo de membros da equipa ganhou maior autonomia e pode agora reunir de forma independente novas capacidades a partir de estados e componentes existentes. Como outro bônus, ao mesmo tempo, os jogadores também receberam lucro na forma de habilidades mais diversas e complexas dos mechs.