terça-feira, 15 de março de 2022

Floreio, minha gramática de geração procedural de texto

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.

segunda-feira, 7 de março de 2022

projeto arlequim pt.3: o fluxo de uma cena


Uma cena no jogo não tem a intenção de simular uma cena narrativa normal, não mais do que combate em video games costumam se assemelhar a cenas de combate em outras mídias. O foco é mais no game design e no fluxo da cena em direção à certos momentos significativos de mudança no estado do mundo, como dois personagens mudando a relação entre um e outro, ou um personagem ganhando uma característica nova de personalidade.

Para simplificar um problema comum de narrativas procedurais, a sequência de causalidade entre uma série de eventos, a ideia é que um evento não proceda outro de forma direita, quero dizer, com uma relação direta de causalidade. A relação entre dois eventos ocorrendo é mais lateral. Quando um evento ocorre ele altera o estado do mundo daquela cena, o próximo evento a ocorrer é influenciado por esse estado do mundo e influencia ele em retorno.

Os eventos que ocorrem em uma cena influenciam sim um ao outro, não são 100% aleatórios e desconexos, mas essa influência ocorre de forma lateral através do estado do mundo que ambos compartilham, sem que um evento leve à outro de forma direta. Esse é um tipo de causalidade mais difícil de autorar em um sistema procedural, seja por que exige um sistema com uma capacidade mais complexa de identificar padrões em uma simulação ou por que exige mais conteúdo gerado por parte do desenvolvedor.

Quando eu falo do estado do mundo me refiro a duas coisas, os personagens que fazem parte desse mundo e as características que compõe cada um, e o contexto atual da cena, os aspectos que ela acumulou até o momento.

Um ato numa cena, que não é nada mais complexo que uma ação atômica, como um personagem discutindo com outro durante uma briga, ou contado uma piada para os amigos, ou acertando um soco em um oponente, só pode ser "gatilhado" em uma cena se cumprir certos pré requisitos. Um personagem acertar outro com um soco por exemplo, pode exigir que esse personagem seja agressivo, tenha uma opinião muito negativa do seu alvo ou esteja com o humor muito bravo.

Esse evento portanto faz referência ao estado do mundo, o humor, a personalidade ou as relações de um personagem, para saber se pode ocorrer ou não, e sendo influenciado pelo estado do mundo ele também é influenciado por eventos que afetaram esse estado do mundo. Digamos que um evento passado em que um personagem A provoca um personagem B levou o personagem B a ficar exaltado e isso cumpriu os pré requisitos para que o evento do soco pudesse ocorrer. O sistema não racionaliza que a provocação levou o personagem provocado a bater no outro, o que é mais rígido e complexo de autorar, o primeiro evento simplesmente atualizou o estado do mundo permitindo que o segundo evento cumprisse seus pré requisitos.

Isso segue o princípio de Quality Based Narrative e permite que unidades narrativas sejam autoradas de forma quase auto contida sem que o desenvolvedor ou o sistema tenham que lidar com uma sequência de causalidade mega complexa. Uma unidade narrativa só especifica os pré requisitos para acontecer de forma lógica e coesa e múltiplos outros acontecimentos que levem o estado do mundo a cumprir esses requisitos podem se tornar a possível causa dessa unidade narrativa poder ocorrer.

Além de ter pré requisitos, todo ato (um evento) em uma cena também é sensibilizado para certos aspectos. Esses aspectos, cujo conjunto total eu ainda estou no processo de iterar, fazem referência ao tipo de evento ocorrendo, como conflito, amizade, interferência, introspecção, e por aí vai. Um ato tem mais chance de ocorrer se os aspectos a que ele está sensibilizado são mais prevalentes na cena. A ideia é ter um modo flexível e genérico de fazer com que eventos semelhantes influenciem a frequência um do outro por compartilharem aspectos.

Apesar dos atos não levarem uns aos outros de forma linear, eu no momento estou planejando as cenas meio que de forma a criar certas sequências, para que uma cena tenha caminhos possíveis e contrastantes que ela pode tomar dependendo do RNG e dos personagens que o jogador botar dentro dela.

Isso pode envolver coisas como certos atos exigindo que um personagem esteja com o humor em um certo estado e um ato "preparatório" que dirige o humor do personagem para aquele ponto. Dessa forma o jogador pode ver um evento alterando o humor do personagem acontecer algumas vezes e depois eventos novos que ocorrem em decorrência dessa mudança, passando a ideia de um arco narrativo?? talvez??

fluxo de atos expressivos na cena teste de discussão verbal

 

Na hora de planejar o conjuntos de atos narrativos que podem ocorrer, eu tenho organizado eles em volta de aspectos chave que simbolizam bem os múltiplos caminhos que uma cena pode seguir. Na cena de discussão por exemplo, um caminho de conflito leva os personagens discutindo a ficarem cada vez mais agressivos uns com os outros e potencialmente piorarem ainda mais sua relação, enquanto um caminho de harmonia pode levá-los a superar a inimizade ou pelo menos não piorar as coisas.

A ideia é que o jogador assista os pequenos atos acumulando esses aspectos em direções diferentes, levando a cena a se concluir em uma situação de conflito ou de harmonia por exemplo, o que foi de certa forma um pouco influenciado por esse texto sobre estados narrativos, da Emily Short. A diferença é que no momento, a conclusão da cena não acontece de forma determinista e ainda usa aleatoriedade ponderada.

Uma cena com uma grande quantidade de "conflito" não vai necessariamente se concluir com um ato de conflito, apenas possui mais chance disso. Eu venho refletindo sobre a possibilidade de experimentar com esse determinismo apenas no ato conclusivo, que é um momento importante para marcar a identidade da cena e sumarizar o que ocorreu.

Também venho refletindo sobre o algoritmo que calcula a influência dos atributos na chance de um ato ocorrer. No momento atribuir um aspecto minoritário a um ato imediatamente mina a chance dele de ocorrer, visto que esse aspecto sempre vai ser um porcentagem minúscula do contexto da cena. Estou pensando na possibilidade de testar adicionar um peso adicional aos aspectos dependendo da proporção total deles no deck de atos da cena.

Quer dizer, se o aspecto harmonia aparece apenas três vezes entre os atos de uma cena, ele terá um peso três vezes maior quando compor o contexto da cena que um aspecto que aparecer nove vezes no deck total. Isso significa que quando um dos poucos atos de harmonia acontecer, isso aumentará significativamente a chance de outros atos de harmonia.

Isso tem o risco de causar o problema oposto, dificultando a presença dos aspectos mais básicos e fundamentais da cena, que passariam a ter uma influência bem mais reduzida. Acho que isso seria mitigado pelo fato dos atos serem tratados como cartas em um baralho sendo sorteadas uma depois da outra. O jogo sorteia um ato da lista e depois roda um algoritmo de rejeição para ver se aceita que aquele ato seja executado, que é onde entra a influência dos aspectos da cena. Se a maioria dos atos no baralho são de conflito, eles ainda tem mais chance de serem sorteados mesmo que o peso do algoritmo beneficie os aspectos mais raros.

Floreio, minha gramática de geração procedural de texto

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 pa...