Gostaria de falar um pouco da ferramenta de geração procedural de texto por gramática de substituição que eu venho desenvolvendo na Unity para usar nos meus projetos atuais de narrativa procedural. Eu gostaria de eventualmente tornar ela disponível para os outros e queria falar um poucos da funcionalidade caso alguém se interesse por testar ela antes deu estar segura para liberar ela para geral.
Caso alguém não saiba, falando em termos gerais, uma gramática formal de substituição permite que você gere texto proceduralmente ao substituir coisas por outras coisas, normalmente partindo de categorias mais amplas para coisas mais específicas, como substituir "animal" por "cavalo", "cachorro", "jacaré", etc. Essas opções em si podem ter termos que devem ser substituídos por outros até que você eventualmente fez todas as substituições possíveis e chegou ao resultou final do texto expandido.
Digamos que você começa com o termo [início] e esse termo pode ser substituído pelas opções "eu não gosto de [animal]" ou "eu adoro esse [animal]". O sistema escolhe a segunda opção por exemplo e substitui o termo "início" por ela. Agora você tem a frase "eu adoro esse [animal]" em que o termo [animal] pode ser substituído por "gato", "cachorro", "passarinho" e por aí vai. Se a primeira opção tivesse sido escolhida, as opções fornecidas para o termo [animal] seriam as mesmas, já que é a mesma categoria.
Um exemplo abaixo utilizando a ferramenta na Unity:
a frase que inicia a geração com um único termo substituível, [animal] |
um resultado possível da geração |
as opções de substituição para o termo "animal" |
Outros resultados utilizando outras frases iniciais com a mesma categoria [animal].
outras opções de frase inicial |
Eu não tenho muita prática explicando esse processo de forma teórica, então quem quiser saber mais sobre o funcionamento de uma gramática em termos gerais pode assistir essa talk da GDC pela Kate Compton em que ela fala de múltiplos métodos de procgen, incluindo gramáticas, e esse vídeo do Coding Train em que ele explica mais do algoritmo em si de gramáticas de geração de texto. Ambos os vídeos infelizmente estão em inglês.
A função de uma gramática é permitir a geração de texto declarando categorias que podem ser substituídas por outras e possivelmente reutilizadas ou recontextualizadas múltiplas vezes, como o caso da categoria [animal]. Você declara possibilidades e possivelmente tags ou condições para essas possibilidades serem escolhidas e o texto se adapta ou se varia na hora que ele é gerado.
Gramáticas podem ser usadas para se variar o texto sendo apresentado ao jogador a fim de evitar repetição, para se lembrar de certas coisas que o jogador fez, desde o nome que ele escolheu até ações que ele tomou, alterar o tom e a atmosfera do texto, ou o tempo verbal e a época em que se passa e por aí vai.
Você pode ter múltiplas opções inteiras de frase, parágrafo ou até um texto completo e escolher entre uma e outra se quiser mudar algum desses parâmetros, mas ao quebrar esse texto em partes menores, cada uma com suas próprias possibilidades de expansão, cada parte do texto pode expressar algo diferente que você precisa sem que um texto inteiro tenha que ser feito para cada combinação de demandas.
Se você precisa passar um texto para o passado, ao mesmo tempo mudar o tom das descrições e definir a personalidade de cada personagem, os verbos podem escolher opções diferentes com tempos verbais diferentes, enquanto os adjetivos escolhem opções que definem a atmosfera das coisas e os diálogos dos personagens escolhem opções apropriadas para a personalidade de cada um.
Tem duas coisas que gosto na técnica da gramática de substituição e que tornam ela minha técnica favorita de geração procedural. Para começar você tem um controle muito forte da estrutura geral. Você começa do topo e vai decidindo níveis cada vez menores conforme você expande as categorias até chegar a possivelmente pequenos detalhes. A todo momento é possível ter um bom controle da estrutura geral de cada um desses níveis de detalhe.
Além disso, gramáticas te permitem uma gradação completa entre coisas muito controladas e autorais e coisas completamente procedurais e possivelmente surpreendentes e descontroladas. Você pode fazer uma gramática que substitui poucas coisas em frases quase estáticas, ou escolhe entre grandes porções de texto, até gramáticas em que cada palavra foi uma escolha entre muitas. Ter essa possibilidade no mesmo algoritmo e poder empregar qualquer um dos extremos num mesmo texto te dá muita liberdade e não te prende a um nível só de proceduralidade por conta da sua escolha de técnica ou ferramenta.
Essa gramática eu venho desenvolvendo com o objetivo de permitir uma recontextualização muito robusta de cenas genéricas em jogos de narrativa procedural em que não se sabe ao certo que personagens estarão ocupando que papel na cena na hora de escrever ela, ou até os próprios personagens são procedurais também. Dessa forma uma cena pode ser pensada para tomar múltiplas direções de estrutura, atmosfera, tom, escolha de palavras ou descrições de locais e pessoas dependendo de quem está tomando parte nela ou onde ela está ocorrendo.
Com isso quero dizer que meu objetivo com essa ferramenta é menos criar variação por si só e mais poder fazer a cena refletir aspectos diferentes do mundo quando ela ocorre que não podiam ser previstos quando ela foi escrita, permitindo que o texto reflita as ações do jogador ou de IAs e dê mais agência para ambos.
Alguns exemplos de geração de outro exemplo básico um pouco mais variado que o anterior:
Não que a gramática não possa ser usada para algo mais simples. Eu já usei ela num teste de joguinho de combate em que a descrição das habilidades eram geradas na hora com os dados da skill naquele momento. Dessa forma eu podia mudar os atributos de uma habilidade e a descrição dela era atualizada automaticamente substituindo os números de dano ou cooldown por exemplo.
Esse é um uso mais funcional e determinista, em que o algoritmo não toma direções criativas ou variadas. Porém, se esse for ser a única demanda de um projeto, existem ferramentas disponíveis de gramática de substituição mais simples que a minha que fazem isso sem precisar lidar com todo o engodo extra necessário para coisas mais complexas.
Para permitir essa recontextualização, a ferramenta oferece a possibilidade de se passar muitos dados para o algoritmo de gramática, essencialmente variáveis, que podem oferecer informação sobre os personagens, cenas ou locais narrados a fim de tornar o texto bem específico para cada contexto diferente.
Também é possível receber informações de volta sobre o texto que foi gerado, para que isso possa ser guardado ou refletido em outras áreas do jogo. Se o texto gerou um lugar novo por exemplo, ou o nome de um personagem, isso pode ser registrado para que o resto do jogo possa fazer menção à essas coisas. Logo o objetivo é poder continuamente passar e receber informação do texto gerado para que ele possa refletir o que ocorre no jogo e para que o jogo depois possa reconhecer o que foi narrado.
Além disso a ferramenta também conta com uma funcionalidade de tags que permite que o conteúdo seja comentado e que o algoritmo use isso para pesar as opções de substituição de forma diferente. Na hora de fazer a geração de texto você pode passar uma lista de tags que serão usadas como prioridade na escolha de conteúdo, ou passar tags que não podem ser selecionadas, além da possibilidade de customizar com precisão a chance de escolha de cada conteúdo com o uso de curvas.
Se alguém estiver interessado em testar a ferramenta, que eventualmente vai ser um plugin para Unity, pode entrar em contato comigo. Gostaria bastante de poder testar ela botando em uso por outras pessoas antes de lançar de forma mais ampla.
Quem quiser ler ou assistir mais material sobre gramáticas de substituição, eu sugiro essa apresentação da Emily Short sobre um dos projetos mais interessantes que eu já vi usando esse ferramenta e esse artigo do desenvolvedor do jogo Voyageur.