Como faço para implementar o diálogo de ramificação em javascript?

11

Estou criando um tipo de jogo visual visual muito básico em JavaScript. Sou iniciante, por isso estou apenas fazendo isso por diversão e aprendizado, e devido ao mau planejamento, me deparei com um problema quando você chega a um ramo do diálogo.

Atualmente, mantenho o script do jogo em uma variável de seqüência de caracteres e divido cada cena com uma tag como "# ~" em matrizes menores, para que o script do jogo fique assim:

var script = "Hello World!#~How are you today?"
var gameText = script.split("#~");
//gameText[0]= Hello World!

Isso funciona muito bem para coisas lineares, mas como devo lidar com um ramo na árvore de diálogo? Esse método parece muito complicado, pois eu precisaria saber exatamente quanto tempo cada caminho tem e, se precisar mudar alguma coisa, seria uma dor de cabeça.

Como posso fazer isso de uma maneira mais simples? Estou tentando seguir o JavaScript baunilha, pois gostaria que o jogo funcionasse com o Web Run Time.

O cartógrafo silencioso
fonte
Este vídeo pode lhe dar algumas idéias: youtube.com/watch?v=XM2t5H7kY6Y
JCM
Recentemente, tive que desenvolver algo para isso usando o Node e optei por uma estrutura de arquivo de texto muito básica. Você pode ver o código resultante e o formato do texto em: github.com/scottbw/dialoguejs O código é GPL, fique à vontade para usá-lo. Tenho certeza de que não será difícil se adaptar a um jogo que não seja do Node JS - substitua a parte "fs.load ()" pelo Ajax.
Scott Wilson
Confira o Ink , uma linguagem de script de história de ramificação, desenvolvida pelo Inkle Studio . Existem várias integrações programáticas do Ink (Java, Javascript, C #) e muitos recursos de terceiros . A tinta também foi usada em muitos jogos comerciais. Finalmente, há um editor de desktop, o Inky , que pode verificar a sintaxe e 'reproduzir' seus diálogos de ramificação.
Big Rich

Respostas:

8

A resposta de Philipp já mostra a direção certa. Eu apenas acho que a estrutura de dados é desnecessariamente detalhada. Textos mais curtos seriam mais fáceis de escrever e ler.

Mesmo que textos mais curtos tornem o algoritmo um pouco mais complexo, vale a pena fazê-lo, porque você escreve o algoritmo apenas uma vez, mas a maior parte do seu tempo será gasta escrevendo e mantendo a história. Portanto, otimize para facilitar a parte que você passará mais tempo realizando.

var story = [
  { m: "Hi!" },
  { m: "This is my new game." },
  { question: "Do you like it?", answers: [
    { m: "yes", next: "like_yes" },
    { m: "no", next: "like_no" },
  ] },
  { label: "like_yes", m: "I am happy you like my game!", next: "like_end" },
  { label: "like_no", m: "You made me sad!", next: "like_end" },
  { label: "like_end" },
  { m: "OK, let's change the topic" }
];

Algumas explicações para este design:

A história toda é escrita em uma matriz. Você não precisa fornecer números, eles são fornecidos automaticamente pela sintaxe da matriz: o primeiro item tem o índice 0, o próximo tem o índice 1, etc.

Na maioria dos casos, não é necessário escrever o número da etapa a seguir. Suponho que a maioria das linhas de texto não sejam ramificações. Vamos fazer "o próximo passo é o item a seguir" uma suposição padrão e só fazer anotações quando for o contrário.

Para saltos, use etiquetas , não números. Então, se você adicionar ou remover algumas linhas posteriormente, a lógica da história será preservada e você não precisará ajustar os números.

Encontre um compromisso razoável entre clareza e falta. Por exemplo, sugiro escrever "m" em vez de "mensagem", porque esse será o comando usado com mais frequência de todos os tempos, portanto, encurtá-lo tornará o texto mais legível. Mas não há necessidade de reduzir as palavras-chave restantes. (No entanto, faça o que desejar. O importante é torná-lo mais legível para você . Como alternativa, você pode oferecer suporte a "m" e "message" como palavras-chave válidas.)

O algoritmo para o jogo deve ser algo como isto:

function execute_game() {
  var current_line = 0;
  while (current_line < story.length) {
    var current_step = story[current_line];
    if (undefined !== current_step.m) {

      display_message(current_step.m);
      if (undefined !== current_step.next) {
        current_line = find_label(current_step.next);
      } else {
        current_line = current_line + 1;
      }

    } else if (undefined !== current_step.question) {

      // display the question: current_step.question
      // display the answers: current_step.answers
      // choose an answer
      // and change current_line accordingly

    }
  }
}

A propósito, essas idéias foram inspiradas no Ren'Py , que não é exatamente o que você deseja (nem JavaScript, nem web), mas pode lhe dar algumas idéias legais de qualquer maneira.

Viliam Búr
fonte
Obrigado pela explicação detalhada, eu não sabia que os arrays podiam funcionar da maneira que você e Philipp mostravam; pensei que eles só podiam conter strings ou números.
The Silent Cartographer
1
Eu tenho tentado implementar sua solução e ela funciona bastante bem, embora eu ache que em alguns lugares ({ label: "like_yes"; m: "I am happy you like my game!"; next: "like_end" },)deva ter um ',' não um ';'. Além disso, o que exatamente é a coisa no aparelho encaracolado chamado? Isso é um objeto dentro da matriz? se eu quisesse mais informações sobre como usar isso, o que procuraria?
The Silent Cartographer
Sim, {...}é um objeto. Em JavaScript, objeto é uma matriz associativa de valor-chave (com alguma funcionalidade extra, não usada neste exemplo), semelhante à matriz em PHP ou Map em Java. Para obter mais informações, consulte os artigos da Wikipedia sobre JavaScript e ECMAScript e a documentação vinculada a partir daí, especialmente a documentação oficial do ECMAScript.
Viliam Búr 29/04
1
Observe que a estrutura de dados que ele recomenda aqui é basicamente JSON. Pessoalmente, eu recomendo ir até o JSON (principalmente adicionar colchetes e uma "árvore": em torno da coisa toda; como {"tree": [etc]}) e, em seguida, você pode armazenar suas árvores de diálogo em arquivos externos. Colocar seus dados em arquivos externos que seu jogo carrega é muito mais flexível e modular (por isso, essa abordagem é uma prática recomendada).
Jhocking
5

Eu recomendo que você crie uma matriz de eventos de diálogo. Cada evento é um objeto que contém o texto que o NPC diz e uma série de respostas possíveis do jogador, que por sua vez são objetos com um texto de resposta e o índice do evento que segue nesta resposta.

var event = []; // create empty array

// create event objects and store them in the array
event[0] = { text: "Hello, how are you?",
             options: [    { response: "Bad", next: 1 },
                           { response: "Good", next: 2 }
                      ]
           };
event[1] = { text: "Why, what's wrong?",
             options: [    { response: "My dog ran away", next: 3},
                           { response: "I broke up with my girlfriend", next: 4}
                      ]
           };
event[2] = { text: "That's nice",

...
Philipp
fonte
2

Você deve usar uma abordagem diferente. O JavaScript suporta matrizes e objetos. Por que não usar um por entrada, economizando toda a divisão e também facilitando a edição / leitura do texto real?

Se você quiser, pode dar uma olhada em algum protótipo que fiz durante algumas horas para o # 1gam . A fonte é livre para ser usada na GPLv3 (eu estou perfeitamente bem se você não se apegar à GPL, se estiver usando apenas como inspiração. Mas me avise assim que o jogo terminar). Só não espere escrever demais ou algo assim. ;)

Para dar uma breve explicação sobre como o código funciona, ignorando as coisas da animação CSS e coisas assim:

  • var data contém essencialmente toda a história com todas as opções possíveis, etc.
  • Cada "local" (ou página / entrada) é identificado por um ID. O primeiro ID da lista é starto segundo cwait, etc.
  • Cada local contém dois elementos obrigatórios: uma legenda e o texto real. Os links para decisões são escritos em alguma marcação simples, tomando o formulário [target location:display text].
  • Toda a "mágica" está acontecendo por dentro navigate(): Esta função faz com que os links de marcação sejam clicados. É um pouco mais longo, porque eu também estou lidando com algum texto estático para becos sem saída lá. A parte importante são as duas primeiras chamadas para replace().
  • As últimas entradas opcionais definem novas cores de plano de fundo para combinar, suportando o clima geral do jogo.
  • Em vez de definir essas cores, você também pode adicionar links apontando para outros locais (observe que isso não é tratado pelo meu código; é apenas uma idéia para demonstrar isso):

    'start': ['Waking up', 'You wake...', 'cwait:yell for help', 'cwait: wait a bit', 'clook: look around']

Mario
fonte