Mastodon Verification Link Orx Narrative System Draft – Sam Seltzer-Johnston

Mar 10, 2017

Orx Narrative System Draft


I’ve gotten distracted, once again, in making other systems. Since I haven’t hit a performance need for shader-based tilemaps, I’m putting it on the backburner. Bad habit, but my overall goal is to have a toolset for development with Orx. There are 2-3 other systems I have an immediate need for.

That is to say, I had an idea for a game that doesn’t have a tilemap. I’ll make a post about that later if it ends up taking off. For now I’d like to outline some systems I’d like to pursue, and draft out one of them here.

The most important ones are a narrative system and a turn-based gameplay system. Both are sorta intended to be used in an FSM which makes me wonder if they’ll both get generalized. The reason I’ve chosen these is because a lot of the game ideas I have require such tools. Particularly the narrative system since I’m interested in making games sorta like Sword & Sworcery or Lone Survivor. This also presents a 4th tangent of contributing some text-rendering features to the Orx Project. Oy.

Anyway, narrative system. A bit of the struggle has been figuring out usage code. I want a nice open-ended way of integrating this with any game-loop. Here are some qualities I’d like to satisfy.

  • Arbitrary Text sources (i.e. speaker)
  • Arbitrary Prompts, which allow branching narrative
  • Narrative Graph, in the sense that prompts could create cyclic patterns
  • Simple-to-read config
  • Control Flow is up to the user
  • Open to interpretation

Here’s one idea.

[PlayerFlowerInteraction]
Player ObservesFlower          = "It's a flower." # "Hmm..."
Player PromptHello             = ? "Say Hello?"
                               # : Yes # -> Player GreetFlower
                               # : No  # "Pssh, flowers can't talk" # -> Exit
                               # : Maybe? # "I dunno about this..."
Flower ThinksToSelf            = "I guess I'll say hello first." -> Flower GreetPlayer 
Player GreetFlower             = "Hello flower"
Flower GreetPlayer             = "Hello human"

Here’s how I imagine the above example playing out.

  • Player says “It’s a flower.”
  • Player says “Hmm…”
  • Player is prompted with “Say Hello?”
  • If Player chooses Yes
    • Player says “Hello flower”
    • Flower says “Hello human”
    • No more properties are available, so dialogue terminates
  • If Player chooses No
    • Player says “Pssh, flowers can’t talk”
    • Jumps to Exit (which doesn’t exist) and causes dialogue to terminate
  • If Player chooses Maybe?
    • Player says “I dunno about this…”
    • Flower says “I guess I’ll say hello first.”
    • Flower says “Hello human”
    • No more properties are available, so dialogue terminates

Wow, that expressed a lot of behaviour with a relatively small configuration, didn’t it?

There are a number of concepts to note here.

  • Path - The resulting evaluation of series of items and properties. By default this is formed by concatenating the Items of a series of Properties, but can be modified by Jumps and Options.
  • Item - Text, Prompt, Option, or Jump.
  • Property - A line of a config section. Formed by a Source, Identifier, and a series of one or more Items. These properties translate to a handle for accessing these parts.

    Ex: Player ObservesFlower = Item1 # ... # ItemN

  • Source - Context idicating where the text is coming from.

    Ex: Player

  • Identifier - Something to differentiate Properties with the same Source. The user may wish to take advantage of this for additional context or debugging.

    Ex: ObservesFlower

  • Text - Available for the user to display as they wish.

    Ex: "It's a flower."

  • Prompt - Same as Text, but indicates that the remainder of this Property is made up of Options/sub-Paths.

    Ex: ? "Say Hello"

  • Option - Indicates a choice to be made in response to a Prompt. An Option is followed by a sub-Path formed by zero or more non-Prompt, non-Option items. An Option sub-Path ends when another Option begins, or when the end of the Property is reached. Each Option sub-Path is terminated by the next Option, end of Property, or a Jump. All Items after a Jump are ignored until the next Option. If an Option sub-Path is not terminated by a Jump, it will jump to the next Property by default when its sub-Path runs out of items.

    Ex: : Yes # Item1 # ... # ItemN

  • Jump - Specifies which property to go to next. They always point to a Property name. Unless it is part of an Option sub-Path, all remaining items on the current Property are ignored. If it is part of an Option sub-Path, all remaining items on the sub-Path are ignored. If left empty, or if an invalid Property is specified, it terminates the Path entirely. Jumps make it possible to end up in an infinite narrative loop.

    Ex: -> Player GreetFlower

There are a few features not yet represented here that would probably be nice.

  • Text Input as an alternative to Options
  • Flagging Options as disabled
  • Removing/replacing Jumps

Thankfully, I might not need to. Config can be modified in memory. So long as I restrain myself in terms of how stateful this system is, the user could do all sorts of things to manipulate narrative flow. Adding, removing, and modifying Items would be the basic stuff. What’s really important is that the user could use their own special identifiers to interpret items in other ways. This opens the door to all sorts of dynamic behaviour. You could even procedurally generate entire dialogues. Being able to design things this way is one of the reasons I love Orx.

So with that, we come to drafting an API to navigate this information.

Here’s some pseudocode.

state = State("PlayerFlowerInteraction")

-- Function to be used in game loop
function handleSpeech()
  -- Make sure there's still a path to traverse
  handle = state:currentHandle() or state:nextHandle()
  if (handle == nil)
    return
  end
  item = state:currentItem() or state:nextItem()
  if (item == nil)
    return
  end

  debugPrint(handle.source, handle.identifier)

  -- Handle prompts/options
  if (state.markedInPrompt)
    if (not state.markedAsPrompted)
      output("Prompt: " + item.text)
      while (option = state:nextOptionItem())
        output("  Option: " + option.text)
      end
      state:markPrompted()
      return
    else
      answer = input()
      if (not state:isValidOption(answer))
        return
      end
      debugPrint(handle.source + " chooses " + answer)
      state:selectOption(answer)
      state:nextItem()
    end
  end

  -- Handle item types (option is not included here since that is handled by prompts above)
  if (item.isPrompt)
    state:markInPrompt()
    debugPrint("Prompting with " + item.text)
  else if (item.isText)
    output(handle.source + ": " + item.text)
  else if (item.isJump)
    debugPrint("Following jump -> " + item.text)
    state:nextHandle()
  end
end

Sort of Lua, but not.

It’s important to note that these functions do not do any displaying. They just form the backbone of an access API with which a user may choose how to display it. Nearly all of this is arbitrary, and control flow is up to the user. What they do with source, identifier, and items is entirely up to them. This is basically a loosely defined FSM that’s tuned for narrative flow.


Newer

Comments aren't enabled.
You're welcome to contact me though.