¿Cómo manejar múltiples hilos de historia en un juego de rol?

26

Diseñé un juego de rol que tiene múltiples hilos de historia, lo que significa que, dependiendo de la elección del usuario, algunas cosas pueden suceder o no, puede lograr lo mismo de varias maneras, el final puede ser diferente, etc.

Implementé un motor de decisión simple, que funciona bien pero tiene un gran defecto, en el momento en que tomas una decisión, la historia está influenciada inmediatamente por tu decisión, lo que significa que no puedes tomar una decisión que te afectará en el futuro lejano. . Esto se debe a que la historia se desarrolla como una rama en una estructura de árbol, y siempre necesita saber qué nodo es el siguiente. Bajo el capó, las decisiones se implementan utilizando una cola: cada nodo conoce el nodo anterior y el siguiente (o si es un nodo de decisión, espera la entrada del usuario para establecer el siguiente nodo)

Vi muchos juegos que tienen motores de decisión complejos, y me pregunto, ¿cómo se hacen? ¿Existe un diseño especial que haga las cosas realmente fáciles? ¿Alguien hizo algo similar y me puede dar una pista sobre cómo abordar esto?

ACTUALIZACIÓN 1:

Un aspecto importante es lograr mantener de alguna manera el código de la historia independiente, para que pueda ser manipulado desde un archivo externo. Planeo usar esto como un motor, por lo que incluso las posibles opciones tienen que venir de un archivo externo. El código tiene que ser totalmente abstracto.

Además, estoy interesado en una solución de diseño, una buena manera de hacerlo, cómo lo hacen o lo hacen otros.

Valentin Radu
fuente
1
Cuando se toman las decisiones importantes, simplemente realice un seguimiento de ellas en una variable accesible globalmente (una matriz de estas variables será más fácil de administrar). Estas variables pueden ser referenciadas por partes posteriores de su programa de juego para actuar o mostrar cosas según corresponda. Por ejemplo, el jugador decide plantar un árbol pequeño, y luego ese árbol parece muy grande; si no plantaron el árbol, entonces ese árbol no estaría allí en absoluto en ese mismo punto posterior del juego.
Randolf Richardson
Sí, eso es lo que pensé inicialmente, sin embargo, necesito que esto sea independiente del código. Eso significa que, la historia se puede manipular completamente desde un archivo externo. Entonces, tengo que encontrar una manera de generalizar lo que acabas de decir y hacerlo de tal manera que no pierda el control sobre el proyecto (hay bastantes decisiones). Se actualizará la pregunta. ¡Gracias!
Valentin Radu
Para ser más específico, no puedo verificar if (isTree)o mantener una isTreevar global porque la historia puede tener o no esa opción. ¿Ya tu sabes? Es más como un motor de elección que servirá múltiples historias.
Valentin Radu
Además, esto tiene otro problema, digamos que si el usuario decide plantar un árbol que establecemos isTree=true, sin embargo, más tarde, hace algo más, como pelear contra un compañero de escuela, quien a cambio va y corta su árbol mientras el árbol aún es joven. porque le patearon el trasero. Ahora, tenemos 2 variables que influyen en la existencia del árbol isTree==true' and didFightBrat == false`. ¿Ya tu sabes? Y la cadena puede continuar para siempre, la existencia del árbol puede verse influenciada por un número desconocido de factores. ¿Ya tu sabes?
Valentin Radu
Luego almacene esos datos en un archivo en el disco. Deberá crear dos subrutinas para cargar y guardar la información, y luego usar esas rutinas de cada parte del código según sea necesario.
Randolf Richardson

Respuestas:

18

También puede generalizar la cola en un gráfico acíclico dirigido (DAG). Puedes leer sobre esto en Wikipedia. Básicamente, cada nodo puede tener uno o más nodos principales de los que "depende". Los ciclos no están permitidos, es decir, si A depende de B, B no puede depender de A (directamente o mediante cualquier cadena indirecta de otros nodos).

Cada nodo está en un estado "activo" o "inactivo", y solo se permite que se active si todos sus padres ya están activos. La estructura del gráfico (qué nodos hay y cómo están conectados) es parte de los datos del juego, pero el estado activo / inactivo es parte de los datos guardados del jugador.

De esa manera, puede modelar cosas como: cuando planta un árbol, marca una tarea "árbol plantado" como activa; luego, más adelante en el juego, otra tarea "treeGrown" nombra tanto "plantedTree" como algún otro nodo (parte de la historia) como sus padres. Entonces, "treeGrown" solo se activa cuando el jugador llega a ese punto en la historia, y también "plantedTree" está activo.

Puede incluir otras funciones, como nodos que se activan si alguno de sus padres se activa, o nodos que son activados por un padre y desactivados por otro, y así sucesivamente. Es un marco bastante general para crear historias con múltiples hilos interdependientes.

Nathan Reed
fuente
Una muy buena respuesta, gracias. En realidad, también resuelve otros problemas que tengo, como guardar el progreso del usuario. Esto es lo que necesito.
Valentin Radu
@NathanReed ¿Por qué no podría ser cíclico? Ser acíclico generalmente no es una característica, sino un subproducto del diseño gráfico. No lo crearía con esa intención. Por ejemplo, imagina si quisieras que tu árbol reconozca las estaciones. Son por naturaleza cíclicos, y su arco de historia podría ser idéntico dependiendo de las opciones disponibles durante una temporada.
Tiene que ser acíclico porque si hay un ciclo, entras en un ciclo infinito cuando intentas averiguar si un nodo en el ciclo puede estar activo, porque revisas recursivamente todos sus antepasados, que incluyen el nodo en sí. Si quisiera modelar algo como las estaciones, no lo haría en el contexto de este gráfico.
Nathan Reed
@NathanReed Ah, lo siento, me perdí la parte recursiva.
3

Por lo que entiendo, lo que quieres no es solo un motor de decisión, sino también un motor de reglas. Para cada decisión, ejecuta un subconjunto de reglas definidas por esa decisión. La ejecución de esas reglas a menudo depende del estado de ciertas entidades como su ejemplo de árbol.

Básicamente, cuando su jugador toma una decisión, busque esa decisión, ejecute las reglas y luego proporcione el siguiente conjunto de decisiones disponibles de manera normal. Sin embargo, sus reglas son dinámicas ya que algunas de ellas solo se ejecutarán en función de otras reglas que ya se han ejecutado.

Un poco más en Wikipedia .

De su subtítulo Cuándo usar los motores de reglas (el énfasis es mío):

  • El problema es demasiado complejo para el código tradicional.
  • El problema puede no ser complejo, pero no puede ver una forma sólida de construirlo.
  • El problema está más allá de cualquier solución obvia basada en algoritmos.
  • Es un problema complejo de resolver. No hay soluciones tradicionales obvias o el problema no se entiende completamente.
  • La lógica cambia a menudo
  • La lógica en sí misma puede ser simple, pero las reglas cambian con bastante frecuencia. En muchas organizaciones, las versiones de software son raras y las reglas pueden ayudar a proporcionar la "agilidad" que se necesita y se espera de una manera razonablemente segura.
  • Los expertos en el dominio y los analistas de negocios están fácilmente disponibles, pero no son técnicos.
  • Los expertos en dominios suelen tener una gran cantidad de conocimiento sobre las reglas y los procesos comerciales. Por lo general, no son técnicos, pero pueden ser muy lógicos. Las reglas pueden permitirles expresar la lógica en sus propios términos. Por supuesto, todavía tienen que pensar críticamente y ser capaces de pensar lógicamente. Muchas personas en puestos no técnicos no tienen capacitación en lógica formal, así que tenga cuidado y trabaje con ellas. Al codificar el conocimiento del negocio en reglas, a menudo expondrá agujeros en la forma en que se entienden actualmente las reglas y los procesos del negocio.

Una cosa a tener en cuenta es que, en ocasiones, un motor de reglas se implementa mejor utilizando un "lenguaje" específico de dominio simplificado, o algo así como YAML. No sugeriría XML.


fuente
1

Debe tener en cuenta que un evento no se basa únicamente en la decisión del usuario. Como notó, algún evento debe agregarse cuando se toman un conjunto de secuencias de decisiones y luego se agrega algo más (como dos días después).

Lo que creo que necesita es una forma de modelar eventos y una forma de activarlo. Mientras que el primero está más limitado a su caso específico, el segundo puede ser modelado por una máquina de estado jerárquico (HSM) que activa sus eventos de forma directa o indirecta.

Tenga en cuenta que una máquina de estados sufre la maldición de la dimensionalidad que solo es mitigada por una estructura jerárquica. Pronto comprenderá que necesita modelar el significado complejo del estado utilizando un HMS, pero también proporcionar una forma de consultarlo.

En este escenario, tiene eventos básicos (decisiones del usuario, tiempo, cambios en el clima, etc.) que son procesados ​​tanto por HSM como por devoluciones de llamadas de eventos básicos. El HSM proporciona un modelo para la "memoria" y las devoluciones de llamada proporcionan una forma de describir cómo debe usarse esa memoria para calcular las consecuencias de una secuencia de decisiones / eventos externos.

También puede terminar usando un dictonario (o alguna otra estructura de recopilación) de HMS, uno para cada "aspecto" del estado que tiene que calcular. Un ejemplo puede ser el uso de un evento HMS relacionado y uno para las decisiones que toman las devoluciones de llamada para desencadenar eventos.

Toda esta infraestructura sirve para imitar el comportamiento de un Dungeon Master humano: generalmente toma un registro mental de la situación actual (HMS ["externo"]) debido a las decisiones del jugador y las condiciones ambientales; cuando algo se agrega, puede tomar decisiones utilizando su registro mental y también registrar algún estado de estrategia interna (HSM ["interno"]) para evitar reaccionar de manera similar si, por ejemplo, se produce alguna situación.

FxIII
fuente