Como lidar com vários tópicos da história em um jogo de RPG?

26

Eu projetei um jogo de RPG com vários tópicos de história, o que significa que, dependendo da escolha do usuário, algumas coisas podem ou não acontecer, você pode conseguir a mesma coisa de várias maneiras, o final pode ser diferente e assim por diante.

Eu implementei um mecanismo de decisão simples, que funciona bem, mas tem uma falha enorme, no momento em que você toma uma decisão, a história é influenciada imediatamente por sua decisão, o que significa que você não pode tomar uma decisão que o afetará no futuro distante . Isso ocorre porque a história se desenrola como um galho em uma estrutura em árvore e sempre precisa saber qual nó é o próximo. Sob o capô, as decisões são implementadas usando uma fila: cada nó conhece o nó anterior e o próximo (ou, se for um nó de decisão, aguarda a entrada do usuário para definir o próximo nó)

Vi muitos jogos com mecanismos de decisão complexos e me pergunto como eles são feitos. Existe um design especial que facilita as coisas? Alguém fez algo semelhante e pode me dar uma dica sobre como lidar com isso?

ATUALIZAÇÃO 1:

Um aspecto importante é conseguir, de alguma forma, manter o código da história independente, para que possa ser manipulado a partir de um arquivo externo. Eu pretendo usar isso como um mecanismo, portanto, mesmo as opções possíveis devem vir de um arquivo externo. O código deve ser totalmente abstrato.

Além disso, estou interessado em uma solução de design, uma boa maneira de fazê-lo, como os outros fazem ou fazem.

Valentin Radu
fonte
11
Quando as decisões importantes são tomadas, basta acompanhá-las em uma variável acessível globalmente (uma matriz dessas variáveis ​​será mais fácil de gerenciar). Essas variáveis ​​podem ser referenciadas por partes posteriores do seu programa de jogo para agir ou exibir as coisas conforme apropriado. Por exemplo, o jogador decide plantar uma árvore pequena e, mais tarde, essa árvore parece muito grande - se não plantasse a árvore, essa árvore não estaria lá naquele mesmo ponto no jogo.
Randolf Richardson
Sim, é o que inicialmente pensei, no entanto, preciso que isso seja independente do código. Isso significa que, a história pode ser totalmente manipulada a partir de um arquivo externo. Portanto, tenho que encontrar uma maneira de generalizar o que você acabou de dizer e fazê-lo de maneira a não perder o controle sobre o projeto (existem algumas decisões). Atualizará a pergunta. Obrigado!
Valentin Radu
Para ser mais específico, não posso verificar if (isTree)ou manter uma isTreevar global, porque a história pode ou não ter essa escolha. Sabe o que eu quero dizer? É mais como um mecanismo de escolha que servirá várias histórias.
Valentin Radu
Além disso, isso tem outro problema, digamos que, se o usuário decidir plantar uma árvore que estabelecemos isTree=true, mais tarde ele fará outra coisa, como combater um colega de escola, que em troca vai e corta sua árvore enquanto a árvore ainda é jovem porque ele levou um chute no traseiro. Agora, temos 2 variáveis ​​que influenciam a existência da árvore isTree==true' and didFightBrat == false`. Sabe o que eu quero dizer? E a cadeia pode continuar para sempre, a existência da árvore pode ser influenciada por um número desconhecido de fatores. Sabe o que eu quero dizer?
Valentin Radu
Em seguida, armazene esses dados em um arquivo em disco. Você precisará criar duas sub-rotinas para carregar e salvar as informações e, em seguida, usar essas rotinas de cada parte do código conforme necessário.
Randolf Richardson

Respostas:

18

Você também pode generalizar a fila em um gráfico acíclico direcionado (DAG). Você pode ler sobre isso na Wikipedia. Basicamente, cada nó pode ter um ou mais nós pai dos quais "depende". Ciclos não são permitidos, ou seja, se A depende de B, B não pode depender de A (diretamente ou através de qualquer cadeia indireta de outros nós).

Cada nó está em um estado "ativo" ou "inativo" e só pode se tornar ativo se todos os seus pais já estiverem ativos. A estrutura do gráfico (quais nós existem e como eles estão conectados) faz parte dos dados do jogo, mas o estado ativo / inativo faz parte dos dados salvos do jogador.

Dessa forma, você pode modelar coisas como: ao plantar uma árvore, você marca uma tarefa "plantedTree" como ativa; depois, mais tarde no jogo, outra tarefa "treeGrown" nomeia "plantedTree" e algum outro nó (parte da história) como seus pais. Então, "treeGrown" só fica ativo quando o jogador chega a esse ponto da história, e também "plantedTree" está ativo.

Você pode incluir outros recursos, como nós que são ativados se algum dos pais for ativado ou nós ativados por um pai e desativados por outro, e assim por diante. É uma estrutura geral para criar histórias com vários segmentos interdependentes.

Nathan Reed
fonte
Uma resposta muito boa, obrigado. Na verdade, ele também resolve outros problemas, como salvar o progresso do usuário. Isto é o que eu preciso.
Valentin Radu
@NathanReed Por que isso não pode ser cíclico? Ser acíclico normalmente não é um recurso, mas um subproduto do design do gráfico. Eu não o criaria com essa intenção. Por exemplo, imagine se você quisesse que sua árvore reconhecesse as estações. Eles são por natureza cíclicos e o arco da sua história pode ser idêntico, dependendo das opções disponíveis durante uma temporada.
Tem que ser acíclico porque, se houver um ciclo, você entra em um loop infinito ao tentar descobrir se um nó no ciclo pode estar ativo, porque você verifica recursivamente todos os seus ancestrais, incluindo o próprio nó. Se você quisesse modelar algo como as estações do ano, eu não faria isso no contexto deste gráfico.
Nathan Reed
@ NathanReed Ah, desculpe, eu perdi a parte recursiva.
3

Pelo que entendi, o que você deseja não é apenas um mecanismo de decisão, mas também um mecanismo de regras. Para cada decisão, você executa um subconjunto de regras definidas por essa decisão. A execução dessas regras geralmente depende do estado de certas entidades, como o exemplo da sua árvore.

Basicamente, quando seu jogador toma uma decisão, você a procura, executa as regras e fornece o próximo conjunto de decisões disponíveis, como o normal. No entanto, suas regras são dinâmicas, pois algumas delas serão executadas apenas com base em outras regras que já foram executadas.

Um pouco mais na Wikipedia .

Na subposição Quando usar os mecanismos de regras (a ênfase é minha):

  • O problema é muito complexo para o código tradicional.
  • O problema pode não ser complexo, mas você não vê uma maneira robusta de construí-lo.
  • O problema está além de qualquer solução óbvia baseada em algoritmo.
  • É um problema complexo para resolver. Não há soluções tradicionais óbvias ou o problema não está completamente esclarecido.
  • A lógica muda frequentemente
  • A lógica em si pode ser simples, mas as regras mudam com bastante frequência. Em muitas organizações, as versões de software são raras e as regras podem ajudar a fornecer a "agilidade" necessária e esperada de uma maneira razoavelmente segura.
  • Especialistas em domínio e analistas de negócios estão prontamente disponíveis, mas não são técnicos.
  • Os especialistas em domínio costumam ter um vasto conhecimento sobre regras e processos de negócios. Eles normalmente não são técnicos, mas podem ser muito lógicos. As regras podem permitir que eles expressem a lógica em seus próprios termos. Obviamente, eles ainda precisam pensar criticamente e ser capazes de pensar lógico. Muitas pessoas em posições não técnicas não possuem treinamento em lógica formal, portanto, tenha cuidado e trabalhe com elas. Ao codificar o conhecimento de negócios em regras, você geralmente expõe falhas na maneira como as regras e processos de negócios são atualmente entendidos.

Uma coisa a observar é que, ocasionalmente, um mecanismo de regras é melhor implementado usando uma "linguagem" específica de domínio simplificada ou algo como YAML. Eu não sugeriria XML.


fonte
1

Você deve considerar que um evento não é puramente baseado na decisão do usuário. Como você notou, algum evento deve ser acrescentado se, quando um conjunto de seqüências de decisões for tomada, e outra coisa for acrescentada (como dois dias depois).

O que eu acho que você precisa é de uma maneira de modelar eventos e de acioná-lo. Enquanto o primeiro está mais limitado ao seu caso específico, o último pode ser modelado por uma máquina de estado hierárquica (HSM) que aciona direta ou indiretamente seus eventos.

Lembre-se de que uma máquina de estado sofre a maldição da dimensionalidade, que é apenas mitigada por uma estrutura hierárquica. Em breve, você entenderá que é necessário modelar o significado complexo de status usando um HMS, mas também fornecer uma maneira de consultá-lo.

Nesse cenário, você tem eventos básicos (decisões do usuário, horário, mudança de clima etc.) processados ​​pelo HSM e pelos retornos de chamada do evento básico. O HSM fornece um modelo para a "memória" e os retornos de chamada fornecem uma maneira de descrever como essa memória deve ser usada para calcular as conseqüências de uma sequência de decisões / eventos externos.

Você também pode usar um dictonário (ou alguma outra estrutura de coleta) do HMS, um para cada "aspecto" do status que você precisa calcular. Um exemplo pode ser o uso de um evento HMS relacionado e outro para as decisões que os retornos de chamada tomam para acionar eventos.

Toda essa infraestrutura serve para imitar o comportamento de um Dungeon Master humano: ele geralmente registra mentalmente a situação atual (HMS ["externo"]) devido às decisões do jogador e às condições ambientais; quando algo é anexado, ele pode tomar decisões usando seu registro mental e registrar também algum status interno da estratégia (HSM ["interno"]) ", a fim de evitar reagir de maneira semelhante se, por exemplo, alguma situação for anexada.

FxIII
fonte