Visão global:
Muitos jogos com estatísticas do tipo RPG permitem "buffs" dos personagens, variando de "Causar 25% de dano extra" a coisas mais complicadas, como "Causar 15 de dano aos atacantes quando atingidos".
As especificidades de cada tipo de buff não são realmente relevantes. Estou procurando uma maneira (presumivelmente orientada a objetos) para lidar com buffs arbitrários.
Detalhes:
No meu caso particular, eu tenho vários personagens em um ambiente de batalha baseado em turnos, então imaginei buffs vinculados a eventos como "OnTurnStart", "OnReceiveDamage" etc. Talvez cada buff seja uma subclasse da classe abstrata Buff principal, em que apenas os eventos relevantes estão sobrecarregados. Cada personagem pode ter um vetor de buffs atualmente aplicado.
Essa solução faz sentido? Eu certamente posso ver dezenas de tipos de eventos sendo necessários, parece que criar uma nova subclasse para cada buff é um exagero e não parece permitir nenhuma "interação" do buff. Ou seja, se eu quisesse implementar um limite para aumentar os danos, de modo que, mesmo se você tivesse 10 buffs diferentes, todos com 25% de dano extra, você faria apenas 100% a mais em vez de 250% a mais.
E há situações mais complicadas que idealmente eu poderia controlar. Tenho certeza de que todos podem apresentar exemplos de como os buffs mais sofisticados podem interagir potencialmente entre si de uma maneira que, como desenvolvedor de jogos, eu talvez não queira.
Como um programador C ++ relativamente inexperiente (geralmente utilizo C em sistemas embarcados), sinto que minha solução é simplista e provavelmente não tira o máximo proveito da linguagem orientada a objetos.
Pensamentos? Alguém aqui já projetou um sistema de buff bastante robusto antes?
Editar: em relação às respostas:
Selecionei uma resposta principalmente com base em bons detalhes e em uma resposta sólida à pergunta que fiz, mas a leitura das respostas me deu mais algumas dicas.
Talvez sem surpresa, os diferentes sistemas ou sistemas aprimorados parecem se aplicar melhor a determinadas situações. O sistema que funciona melhor para o meu jogo dependerá dos tipos, variações e número de buffs que pretendo aplicar.
Para um jogo como Diablo 3 (mencionado abaixo), onde praticamente qualquer equipamento pode mudar a força de um buff, os buffs são apenas estatísticas do personagem, o sistema parece uma boa idéia sempre que possível.
Para a situação baseada em turnos em que estou, a abordagem baseada em eventos pode ser mais adequada.
De qualquer forma, ainda espero que alguém ache uma bala mágica sofisticada "OO", que permita aplicar +2 de distância de movimento por buff por turno , causando 50% do dano recebido ao buff do atacante , e um teletransporte automaticamente para um bloco próximo quando atacado de 3 ou mais blocos de distância em um único sistema sem transformar um bônus de força +5 em sua própria subclasse.
Eu acho que a coisa mais próxima é a resposta que marquei, mas o chão ainda está aberto. Obrigado a todos pela contribuição.
fonte
Respostas:
Esta é uma questão complicada, porque você está falando sobre algumas coisas diferentes que (atualmente) são agrupadas como 'buffs':
Eu sempre implemento o primeiro com uma lista de efeitos ativos para um determinado personagem. A remoção da lista, seja com base na duração ou explicitamente, é bastante trivial, portanto não abordarei isso aqui. Cada efeito contém uma lista de modificadores de atributo e pode aplicá-lo ao valor subjacente através da multiplicação simples.
Em seguida, envolvo-o com funções para acessar os atributos modificados. por exemplo.:
Isso permite aplicar efeitos multiplicativos com bastante facilidade. Se você também precisar de efeitos aditivos, decida em que ordem os aplicará (provavelmente o último aditivo) e percorra a lista duas vezes. (Eu provavelmente teria listas de modificadores separadas no Effect, uma para multiplicativa e outra para aditivo).
O valor do critério é permitir que você implemente "+ 20% vs morto-vivo" - defina o valor UNDEAD no efeito e passe apenas o valor UNDEAD para
get_current_attribute_value()
morto- quando você estiver calculando uma rolagem de dano contra um inimigo morto-vivo.Aliás, eu não ficaria tentado a escrever um sistema que aplique e aplique valores diretamente ao valor do atributo subjacente - o resultado final é que é muito provável que seus atributos se afastem do valor pretendido devido a erro. (por exemplo, se você multiplicar algo por 2, mas depois limitar, quando você o dividir por 2 novamente, será mais baixo do que começou.)
Quanto aos efeitos baseados em eventos, como "Cause 15 de dano aos atacantes quando atingidos", você pode adicionar métodos à classe Effect para isso. Mas se você deseja um comportamento distinto e arbitrário (por exemplo, alguns efeitos para o evento acima podem refletir danos, alguns podem curá-lo, pode teleportá-lo aleatoriamente, seja qual for), você precisará de funções ou classes personalizadas para lidar com isso. Você pode atribuir funções aos manipuladores de eventos sobre o efeito; basta chamar os manipuladores de eventos para quaisquer efeitos ativos.
Obviamente, sua classe Effect terá um manipulador de eventos para cada tipo de evento, e você pode atribuir funções de manipulador a quantas forem necessárias em cada caso. Você não precisa subclassificar Effect, pois cada um é definido pela composição dos modificadores de atributo e manipuladores de eventos que ele contém. (Provavelmente também conterá um nome, uma duração etc.)
fonte
Em um jogo em que trabalhei com um amigo de uma turma, criamos um sistema de buff / debuff para quando o usuário fica preso em grama alta e ladrilhos de aceleração e o que não é, e algumas coisas menores, como sangramentos e venenos.
A ideia era simples e, enquanto a aplicávamos no Python, era bastante eficaz.
Basicamente, aqui está como foi:
Agora, como realmente aplicar buffs do mundo é uma história diferente. Aqui está minha comida para pensar.
fonte
Não tenho certeza se você ainda está lendo isso, mas luto com esse tipo de problema há muito tempo.
Eu projetei vários tipos diferentes de sistemas afetados. Vou examiná-los brevemente agora. Tudo isso é baseado na minha experiência. Não pretendo saber todas as respostas.
Modificadores estáticos
Esse tipo de sistema depende principalmente de números inteiros simples para determinar quaisquer modificações. Por exemplo, +100 a Max HP, +10 a atacar e assim por diante. Este sistema também pode lidar com porcentagens também. Você só precisa garantir que o empilhamento não fique fora de controle.
Eu nunca realmente armazenei em cache os valores gerados para esse tipo de sistema. Por exemplo, se eu quisesse exibir a saúde máxima de alguma coisa, geraria o valor no local. Isso impedia que as coisas fossem propensas a erros e apenas mais fáceis de entender para todos os envolvidos.
(Eu trabalho em Java, o que segue é baseado em Java, mas deve funcionar com algumas modificações para outras linguagens.) Esse sistema pode ser feito facilmente usando enumerações para os tipos de modificação e depois números inteiros. O resultado final pode ser colocado em algum tipo de coleção que possui pares de chave e valor ordenados. Serão pesquisas e cálculos rápidos, portanto o desempenho é muito bom.
No geral, ele funciona muito bem com apenas modificadores estáticos. No entanto, o código deve existir nos locais apropriados para que os modificadores sejam usados: getAttack, getMaxHP, getMeleeDamage e assim por diante.
Onde esse método falha (para mim) é uma interação muito complexa entre os buffs. Não existe uma maneira muito fácil de interagir, exceto por guiá-lo um pouco. Ele tem algumas possibilidades de interação simples. Para fazer isso, você deve fazer uma modificação na maneira como armazena os modificadores estáticos. Em vez de usar uma enumeração como chave, você usa uma String. Essa String seria o nome do Enum + variável extra. 9 vezes em 10, a variável extra não é usada; portanto, você ainda mantém o nome da enumeração como chave.
Vamos dar um exemplo rápido: se você quiser modificar o dano contra criaturas mortas-vivas, poderá ter um par ordenado como este: (DAMAGE_Undead, 10) O DAMAGE é o Enum e o Undead é a variável extra. Portanto, durante o seu combate, você pode fazer algo como:
De qualquer forma, funciona bastante bem e é rápido. Mas falha em interações complexas e em ter código "especial" em todos os lugares. Por exemplo, considere a situação de "25% de chance de se teletransportar na morte". Este é um "bastante" complexo. O sistema acima pode lidar com isso, mas não com facilidade, pois você precisa do seguinte:
Então isso me leva ao meu próximo:
O melhor sistema de buff complexo
Uma vez tentei escrever um MMORPG 2D sozinho. Este foi um erro terrível, mas eu aprendi muito!
Reescrevi o sistema afetado 3 vezes. O primeiro usou uma variação menos poderosa do que foi dito acima. O segundo foi o que eu vou falar.
Esse sistema tinha uma série de classes para cada modificação, assim como: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. Eu tinha um milhão desses caras - até coisas como TeleportOnDeath.
Minhas aulas tinham coisas que fariam o seguinte:
Aplicar e remover se explicam (embora em coisas como porcentagens, o efeito acompanhe o quanto aumentou o HP para garantir quando o efeito se dissipou, apenas removeria a quantidade adicionada. demorei muito tempo para me certificar de que estava certo. Ainda não tive um bom pressentimento.).
O método checkForInteraction era um trecho de código horrivelmente complexo. Em cada uma das classes afeta (ie: ChangeHP), ele teria código para determinar se isso deve ser modificado pelo efeito de entrada. Por exemplo, se você tivesse algo como ...
O método checkForInteraction lidaria com todos esses efeitos. Para fazer isso, cada efeito em TODOS os jogadores por perto tinha que ser verificado! Isso ocorre porque o tipo de afeto que eu lidei com vários jogadores ao longo de uma área. Isso significa que o código NUNCA TINHA quaisquer declarações especiais como acima - "se acabamos de morrer, devemos verificar se há teletransporte na morte". Este sistema trataria automaticamente corretamente no momento certo.
Tentar escrever esse sistema levou dois meses e explodiu de cabeça várias vezes. No entanto, era REALMENTE poderoso e poderia fazer uma quantidade insana de coisas - especialmente quando você leva em consideração os dois fatos a seguir para habilidades no meu jogo: 1. Eles tinham intervalos de alvo (ou seja: único, auto, apenas grupo, PB AE auto). , PB AE alvo, AE alvo e assim por diante). 2. As habilidades podem ter mais de um efeito sobre elas.
Como mencionei acima, este foi o segundo do terceiro sistema de afetação para este jogo. Por que me afastei disso?
Este sistema teve o pior desempenho que eu já vi! Era muito lento, pois tinha que fazer muita verificação para cada coisa que acontecia. Tentei melhorá-lo, mas considerou um fracasso.
Então chegamos à minha terceira versão (e outro tipo de sistema de buffs):
Classe afetada complexa com manipuladores
Portanto, é praticamente uma combinação dos dois primeiros: podemos ter variáveis estáticas em uma classe Affect que contém muita funcionalidade e dados extras. Em seguida, basta chamar manipuladores (para mim, praticamente alguns métodos de utilidade estática em vez de subclasses para ações específicas. Mas tenho certeza de que você poderia usar subclasses para ações, se quisesse também) quando quisermos fazer alguma coisa.
A classe Affect teria todos os itens bons e interessantes, como tipos de destino, duração, número de usos, chance de executar e assim por diante.
Ainda teríamos que adicionar códigos especiais para lidar com as situações, por exemplo, teleportar na morte. Ainda teríamos que verificar isso manualmente no código de combate e, se existisse, obteríamos uma lista de efeitos. Esta lista de afetos contém todos os afetos atualmente aplicados no jogador que lidou com o teletransporte na morte. Depois, olhamos cada uma delas e verificamos se ela foi executada e foi bem-sucedida. Se fosse bem-sucedido, chamaríamos o manipulador para cuidar disso.
A interação pode ser feita, se você quiser também. Teria apenas que escrever o código para procurar buffs específicos nos players / etc. Por ter um bom desempenho (veja abaixo), deve ser bastante eficiente fazer isso. Só precisaria de manipuladores mais complexos e assim por diante.
Portanto, ele tem muito desempenho do primeiro sistema e ainda muita complexidade como o segundo (mas não tanto). Pelo menos em Java, você pode fazer algumas coisas complicadas para obter o desempenho da quase primeira nos MOST casos (por exemplo: ter um mapa de enumeração ( http://docs.oracle.com/javase/6/docs/api/java) /util/EnumMap.html ) com Enums como as chaves e ArrayList de afeta como os valores.Isto permite que você veja se você tem afetos rapidamente [já que a lista seria 0 ou o mapa não teria a enumeração] e não possui para iterar continuamente as listas de afetos do jogador sem motivo. Não me importo de repetir os afetos se precisarmos deles neste momento. Otimizarei mais tarde se isso se tornar um problema.
Atualmente, estou reabrindo (reescrevendo o jogo em Java, em vez da base de código FastROM em que ele estava originalmente) meu MUD que terminou em 2005 e recentemente me deparei com como quero implementar meu sistema de buff? Vou usar esse sistema porque funcionou muito bem no meu jogo com falha anterior.
Bem, espero que alguém, em algum lugar, ache algumas dessas idéias úteis.
fonte
Uma classe diferente (ou função endereçável) para cada buff não é um exagero se o comportamento desses buffs for diferente um do outro. Uma coisa seria ter buffs de + 10% ou + 20% (que, é claro, seria melhor representado como dois objetos da mesma classe), outra seria a implementação de efeitos totalmente diferentes que exigiriam código personalizado de qualquer maneira. No entanto, acredito que é melhor ter maneiras padrão de personalizar a lógica do jogo, em vez de deixar cada buff fazer o que bem entender (e possivelmente interferir entre si de formas imprevistas, perturbando o equilíbrio do jogo).
Sugiro dividir cada "ciclo de ataque" em etapas, onde cada etapa tem um valor base, uma lista ordenada de modificações que podem ser aplicadas a esse valor (talvez limitado) e um limite final. Cada modificação possui uma transformação de identidade como padrão e pode ser influenciada por zero ou mais buffs / debuffs. As especificidades de cada modificação dependeriam da etapa aplicada. Você decide como o ciclo é implementado (incluindo a opção de uma arquitetura orientada a eventos, como você está discutindo).
Um exemplo de ciclo de ataque pode ser:
O importante a ser observado é que, quanto mais cedo no ciclo um buff é aplicado, mais efeito ele terá no resultado . Portanto, se você quiser um combate mais "tático" (onde a habilidade do jogador é mais importante que o nível do personagem), crie muitos buffs / debuffs nas estatísticas básicas. Se você quiser um combate mais "equilibrado" (onde o nível é mais importante - importante nos MMOGs para limitar a taxa de progresso), use apenas buffs / debuffs mais tarde no ciclo.
A distinção entre "Modificações" e "Buffs" que mencionei anteriormente tem um propósito: as decisões sobre regras e equilíbrio podem ser implementadas no primeiro, de modo que quaisquer alterações nelas não precisam refletir mudanças em todas as classes do último. OTOH, os números e tipos de buffs são limitados apenas pela sua imaginação, pois cada um deles pode expressar o comportamento desejado sem ter que levar em consideração qualquer interação possível entre eles e os outros (ou mesmo a existência de outros).
Então, respondendo à pergunta: não crie uma classe para cada Buff, mas uma para cada (tipo de) Modificação, e amarre a Modificação ao ciclo de ataque, não ao personagem. Os buffs podem ser simplesmente uma lista de tuplas (modificação, chave, valor) e você pode aplicar um buff a um personagem simplesmente adicionando / removendo-o do conjunto de buffs do caractere. Isso também reduz a janela de erro, já que as estatísticas do personagem não precisam ser alteradas quando os buffs são aplicados (portanto, há menos risco de restaurar uma estatística para o valor errado depois que um buff expirar).
fonte
Não sei se você ainda está lendo, mas eis como estou fazendo agora (o código é baseado no UE4 e C ++). Depois de refletir sobre o problema por mais de duas semanas (!!), finalmente encontrei o seguinte:
http://gamedevelopment.tutsplus.com/tutorials/using-the-composite-design-pattern-for-an-rpg-attributes-system--gamedev-243
E eu pensei que, bem, encapsular um único atributo dentro de classe / estrutura não é uma má idéia, afinal. No entanto, lembre-se de que estou aproveitando muito o sistema de reflexão de código do UE4, portanto, sem alguns retrabalhos, isso pode não ser adequado em todos os lugares.
De qualquer forma, comecei a partir do atributo wrapping em uma única estrutura:
Ainda não está terminado, mas a ideia base é que essa estrutura monitore seu estado interno. Os atributos podem ser modificados apenas por Efeitos. Tentar modificá-los diretamente não é seguro e não é exposto aos designers. Estou assumindo que tudo, que pode interagir com atributos, é Effect. Incluindo bônus simples de itens. Quando um novo item é equipado, um novo efeito (junto com a alça) é criado e adicionado ao mapa dedicado, que lida com bônus de duração infinita (aqueles que devem ser removidos manualmente pelo jogador). Quando um novo efeito está sendo aplicado, um novo identificador para ele é criado (identificador é apenas int, envolvido com struct) e, em seguida, esse identificador é passado ao redor como um meio de interagir com esse efeito, além de acompanhar se o efeito é Ainda ativo. Quando o efeito é removido, seu identificador é transmitido para todos os objetos interessados,
A parte realmente importante disso é o TMap (TMap é um mapa de hash). FGAModifier é uma estrutura muito simples:
Ele contém o tipo de modificação:
E Value, que é o valor final calculado, vamos aplicar ao atributo.
Adicionamos um novo efeito usando a função simples e, em seguida, chamamos:
Essa função deve recalcular toda a pilha de bônus, cada vez que o efeito é adicionado ou removido. A função ainda não está concluída (como você pode ver), mas você pode ter uma ideia geral.
Minha maior reclamação no momento é lidar com o atributo Damaging / Healing (sem envolver o recálculo da pilha inteira), acho que resolvi isso um pouco, mas ainda exige mais testes para ser 100%.
Em qualquer caso, os Atributos são definidos assim (+ macros irreais, omitidas aqui):
etc.
Também não tenho 100% de certeza sobre como lidar com o CurrentValue do atributo, mas deve funcionar. Do jeito que estão agora.
De qualquer forma, espero que isso salve algumas pessoas no cache principal, sem ter certeza se essa é a melhor ou até boa solução, mas eu gosto mais do que rastrear efeitos independentemente de atributos. Tornar cada atributo de rastreamento em seu próprio estado é muito mais fácil nesse caso e deve ser menos propenso a erros. Existe essencialmente apenas um ponto de falha, que é a classe bastante curta e simples.
fonte
Eu trabalhei em um pequeno MMO e todos os itens, poderes, buffs etc. tinham 'efeitos'. Um efeito era uma classe que tinha variáveis para 'AddDefense', 'InstantDamage', 'HealHP' etc. etc. Os poderes, itens, etc. lidariam com a duração desse efeito.
Quando você lança um poder ou coloca um item, ele aplica o efeito ao personagem pela duração especificada. Então o ataque principal, etc cálculos levariam em conta os efeitos aplicados.
Por exemplo, você tem um bônus que adiciona defesa. Haveria no mínimo um EffectID e Duration para esse buff. Ao convertê-lo, ele aplicaria o EffectID ao caractere pela duração especificada.
Outro exemplo para um item teria os mesmos campos. Mas a duração seria infinita ou até que o efeito seja removido removendo o item do personagem.
Este método permite que você itere sobre uma lista de efeitos atualmente aplicados.
Espero ter explicado esse método com bastante clareza.
fonte
Estou usando ScriptableOjects como buffs / spells / talentos
using UnityEngine; using System.Collections.Generic;
enumeração pública BuffType {Buff, Debuff} [System.Serializable] classe pública BuffStat {public Stat Stat = Stat.Strength; flutuação pública ModValueInPercent = 0.1f; }
BuffModul:
fonte
Esta foi uma pergunta real para mim. Eu tenho uma ideia sobre isso.
Buff
lista e um atualizador lógico para os buffs.Buff
classe.Dessa forma, pode ser fácil adicionar novas estatísticas de jogadores, sem alterar a lógica das
Buff
subclasses.fonte
Eu sei que isso é bastante antigo, mas foi vinculado em um post mais recente e tenho algumas ideias sobre o assunto que gostaria de compartilhar. Infelizmente, não tenho minhas anotações comigo no momento, então tentarei dar uma visão geral do que estou falando e editarei nos detalhes e em alguns exemplos de código quando tiver na frente. mim.
Primeiro, acho que, de uma perspectiva de design, a maioria das pessoas está muito interessada em saber que tipos de buffs podem ser criados e como eles são aplicados, além de esquecer os princípios básicos da programação orientada a objetos.
O que eu quero dizer? Realmente não importa se algo é um buff ou um debuff, ambos são modificadores que apenas afetam algo de maneira positiva ou negativa. O código não se importa com qual é qual. Na verdade, não importa se algo está adicionando estatísticas ou multiplicando-as, esses são apenas operadores diferentes e, novamente, o código não se importa com qual é.
Então, onde eu vou com isso? Que projetar uma boa classe de buff / debuff (leia-se: simples, elegante) não é tão difícil, o difícil é projetar os sistemas que calculam e mantêm o estado do jogo.
Se eu estivesse projetando um sistema de buff / debuff, aqui estão algumas coisas que eu consideraria:
Algumas especificidades para quais tipos de buff / debuff devem conter:
Isso é apenas um começo, mas a partir daí você está apenas definindo o que deseja e agindo usando o seu estado normal de jogo. Por exemplo, digamos que você queira criar um item amaldiçoado que reduz a velocidade de movimento ...
Desde que eu coloque os tipos adequados, é simples criar um registro de buff que diz:
E assim por diante, e quando eu crio um buff, atribuo a ele o BuffType of Curse e tudo depende do mecanismo ...
fonte