8
\$\begingroup\$

I'm currently building a choose your own adventure game. Now it's easy enough to have one outcome to every choice and create a linear flow, but is there a good algorithm for having all previous selections affect the next outcome? I could obviously store every previous selection and have big 'IF' statements to decide but I wondered if there was a better way?

Part of me wonders if every choice should have a 'score' and then I use this (maybe with a threshold) to determine what the next action should be and each action gets added to the score.

I'm primiarly doing this in swift/SpriteKit but I think it's more about the concept then the code at this point.

In response to Josh's comment below:

I suppose I'm still in the conceptual phase at the moment, but each 'page' would either be a custom object or a json file. I was thinking about your answer (now removed) and maybe having each ENUM option as a bit. Then each page could have a 'score'. Then using the previous options selected work out what bits are set and that determines what page. I guess I just wondered if there was an existing solution to this problem before I began to almost help decide how I should format the 'story'

Rough guess at format :

  { "text":"You arrive in the dungeon and there are 2 doors",
   "options":[
    1: "Go left",
    2: "Go Right"
    ],
   "score" : 0 // first page
  }
  {"text" "You went left and meet a dragon",
  "options":[
    0: "Game over, you were eaten" // something to handle game over
    ],
   "score" : 1 
  }
  {"text" "You meet a mushroom who tells you the princess is in another castle",
  "options":[
    4: "Give up, game over", // something to handle game over
    8: "Jump down the pipe"
    ],
   "score" : 2 
  }

Thanks

\$\endgroup\$
  • \$\begingroup\$ How do you currently representing each "page" of your adventure? Are they all just hard coded functions in Swift? Are you reading the text/images and choices available for each page from a data file? If so, what's the file look like? \$\endgroup\$ – Josh Jul 20 '16 at 5:42
  • \$\begingroup\$ @JoshPetrie updated with some more info \$\endgroup\$ – TommyBs Jul 20 '16 at 6:01
  • \$\begingroup\$ The underlying problem is that there is simply no elegant way to represent non-linear narrative with a linear medium like sourcecode. The ideal method would IMO be to have an UML-like visual editor where story-points are represented as boxes and the possible narrative flows as arrows. But for most smaller projects it would likely be more work to implement such an editor than to just hand-write a mess of spaghetti code full of if's, else's, switch's and case's hoping you will be finished before losing your sanity. \$\endgroup\$ – Philipp Jul 20 '16 at 9:35
  • \$\begingroup\$ @Philipp Isn't a UML diagram exactly what an object graph is? You could probably look into object graph serialization approaches to find good ways of representing a graph of objects in a linearized fashion. \$\endgroup\$ – uliwitness Jul 24 '16 at 11:04
  • \$\begingroup\$ @uliwitness There are different kinds of UML diagrams. The closest to a dialog tree is an activity diagram. I experimented with various tools to generate code from UML diagrams once and was quite disappointed. You usually get some quite messy code which isn't even functional yet and needs some manual filling out of stub methods. \$\endgroup\$ – Philipp Jul 24 '16 at 12:04
12
\$\begingroup\$

A lot of Adventure/RPG games handle this in two ways (there may be more I'm not aware of).

Use of flags

The first one is to set a flag if something happened (usually some bitmask). This is fine if you don't have many things to track. In the room/encounter there may be a check against a flag that alters something. In your example could add a rule:

{"text" "You meet a mushroom who tells you the princess is in another castle",
"options":[
  4: "Give up, game over", // something to handle game over
  8: [64:"Jump down the pipe", "Exit stage left"]
  ],
 "score" : 2
 "setflag" : 32
}

Where the 2nd option only appears if flags & 64 is set. You could also implement the setflag option if the scene appears, or a particular choice was made.

Use of items

The second option, is to add items to the player's inventory. These are the "this item is needed to complete a quest" you see often in games. These basically function as portable 'flags'. This can be implemented two ways (or a combination):

  • The item is the 'flag': Have the scene or dialog check if the player has a specific object in their inventory. This is much like the 'check for a flag' mechanic as described above. The upside is that it may be much easier for you to design encounters as you're checking against familiar named objects.

  • Items have properties that work as a flag. For example in The Witcher 3, the player obtains an object that can dispel illusionary walls. That item itself could be a 'flag' but it could also be that the object has a property can_dispel_illusions. The upside here is that you could implement multiple objects that can be used in the same scenario. Thus a scene/dialog/door could check if the player has 'something' in their inventory that has the property can_dispel_illusions.

This gives you the possibility to give the player the illusion of choice; if the player didn't encounter the witch in scene one, thus missing the 'wand of dispel' the player could get a 'ring of dispel' later from some shady merchant- preventing an unwinnable state of the game.

If you don't plan to give the player a "real" inventory, the quest items could go to a list -hidden from the player.

example:

{"items":[
  "Wand of Dispel": { "properties": ["can_dispel_illusions","illumination"]}
]}

and:

{"text" "The corridor ends in a dead end. The wall feels cold when you touch it.",
"options":[
  4: "Turn back", 
  8: {"can_dispel_illusions": "Check if the wall is real."} 
  ],
 "score" : 2 
}

{"text" "The witch hands you an object. She observes what you're going to do next.",
"options":[
  14: "Leave the witch's house", 
  22: "Look at the object."} 
  ],
 "giveitem" : "Wand of Dispel" 
}
\$\endgroup\$
2
\$\begingroup\$

One solution I've seen that elaborates on your approach is to have various attributes with associated scores. Some choices result in modifying the one or more of these scores. The scores in turn may modify the choices available to the player.

For instance, early in the story, there may be some option combat encounters. If the player engages in combat, the bravery attribute would increase, whereas fleeing would cause it to decrease. Later on, a particular story branch might only be available if bravery score was above or below a given threshold. To add more depth, some decisions should affect more than one score. For instance, picking pockets might increase a stealth score and decrease an honesty score.

Generally, I've seen this approach used in simulation type interactive fiction. You can hear Dan Fabulich from Choice Of Games discuss this solution on this episode of the Game Design Round Table.

The benefits of this approach are:

  • each decision doesn't need to provide a unique ending, reducing the total number of endings required
  • there can be more than one way to reach a given ending, allowing for more variety in rewarding play styles

Some drawbacks are:

  • if decisions hinge on single attributes, it can be shallow & formulaic
  • conversely, if decisions are too complex, balancing the game / story becomes more difficult
\$\endgroup\$
1
\$\begingroup\$

This is the big problem story-driven games face: Combinatorial explosion. As such, if you look at e.g. Bioware or Telltale games, you'll find that the most common approach seems to still be to try to restrict the story to being more linear, and keeping consequences from actions limited to their chapters.

The approach Bioware seems to be using splits each story up into separate threads or sub-arcs. The arcs are pretty much independent from each other, but happen simultaneously and may all contribute to the ending. That way, you don't have to model each combination of arcs, you just alternate chapters between arcs, and consequences are limited to the particular sub-arc you're dealing with right now, or how to end an arc. (or how to end a character's arc. If you no longer need that NPC, you're often given the choice of killing them, arresting them, or setting them free, for example, giving you choice, but limiting the consequences to the rest of the story)

The ending of the entire story then only needs to know how each arc ended, and tie up the whole narrative in a neat little bow.

How would you model that in a program?

This simply pushes the problem down one level (because each sub-arc needs to track differences like a simple story would), but basically you'd have an array of flags (e.g. a bit field, or a list of items with flags representing the ability to effect certain outcomes) for each sub-arc.

Also, when you're telling your ending, you usually take the same approach of alternating when you tell the player about the consequences: Each sub-arc's result gets its own paragraph/conversation node, simplifying your conditionals greatly.

\$\endgroup\$
0
\$\begingroup\$

It may take a bit of time, but developing your own tree (like a behavior tree or dialog tree, but for pages) might be beneficial. And you could use one of those as a model. Something like this:

How do dialog trees work?

This would allow you to tailor it for your specific needs, but also let the code handle most of the work.

\$\endgroup\$
0
\$\begingroup\$

What if you used a rule engine type approach? When a player interacts with an NPC, the game would run a stripped down rule engine which would start with flags that had been created by past choices/behavior and check it against a set of rules defined by you in advance. Something like Drools is probably too big and ambitious, not to mention slow, but a limited scope rule engine could theoretically give you better performance and a lot of flexibility.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.