Basic elements
These are the basic features needed to write a story with Ink
and inkling
.
Text
Plain text is the most basic element of a story. It is written in the story text as regular lines.
#![allow(unused)] fn main() { let content = r" I opened my notebook to a blank page, pen in hand. "; }
Text is separated into paragraphs by being on different lines.
#![allow(unused)] fn main() { extern crate inkling; use inkling::{read_story_from_string, Prompt}; let content = r" My hand moved towards the canvas. The cold draft made a shudder run through my body. A dark blot spread from where my pen was resting. "; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); story.resume(&mut buffer).unwrap(); assert_eq!(buffer[0].text, "My hand moved towards the canvas.\n"); assert_eq!(buffer[1].text, "The cold draft made a shudder run through my body.\n"); assert_eq!(buffer[2].text, "A dark blot spread from where my pen was resting.\n"); }
Those three lines will be returned from inkling
as separate lines, each ending with
a newline character.
Glue
If you want to remove the newline character from in between lines, you can use the <>
marker which signifies glue. This:
#![allow(unused)] fn main() { extern crate inkling; use inkling::read_story_from_string; let content = r" This line will <> be glued to this, without creating a new paragraph. "; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); story.resume(&mut buffer).unwrap(); assert!(!buffer[0].text.ends_with("\n")); }
Becomes:
This line will be glued to this, without creating a new paragraph.
as will this, since glue can be put at either end:
#![allow(unused)] fn main() { extern crate inkling; use inkling::read_story_from_string; let content = r" This line will <> be glued to this, without creating a new paragraph. "; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); story.resume(&mut buffer).unwrap(); assert!(!buffer[0].text.ends_with("\n")); }
For these examples glue doesn’t do much, but it will be more useful once we introduce story structure features. Keep it in mind until then.
Comments
The text file can contain comments, which will be ignored by inkling
as it parses the story.
To write a comment, preceed the line with //
.
#![allow(unused)] fn main() { extern crate inkling; use inkling::{read_story_from_string, Prompt}; let content = r" The cold could not be ignored. // Unlike this line, which will be As will the end of this. // removed comment at end of line "; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); story.resume(&mut buffer).unwrap(); assert_eq!(buffer[0].text, "The cold could not be ignored.\n"); assert_eq!(buffer[1].text, "As will the end of this.\n"); }
Note that multiline comments with /*
and */
are not currently supported.
Branching story paths
To mark a choice in a branching story, use the *
marker.
#![allow(unused)] fn main() { extern crate inkling; use inkling::{read_story_from_string, Prompt}; let content = r" * Choice 1 * Choice 2 "; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); match story.resume(&mut buffer).unwrap() { Prompt::Choice(choices) => { assert_eq!(choices[0].text, "Choice 1"); assert_eq!(choices[1].text, "Choice 2"); } _ => unreachable!() } }
(The +
marker can also be used, which results in a different behavior
if the tree is visited again. More on this later.)
When inkling
encounters one or more lines beginning with this marker, the options will
be collected and returned to the user to make a choice.
After making a choice, the story proceeds from lines below the choice. So this story:
#![allow(unused)] fn main() { extern crate inkling; use inkling::{read_story_from_string, Prompt}; let content = r#" A noise rang from the door. * "Hello?" I shouted. "Who's there?" * I rose from the desk and walked over. "#; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); match story.resume(&mut buffer).unwrap() { Prompt::Choice(choices) => { assert_eq!(choices[0].text, r#""Hello?" I shouted."#); assert_eq!(choices[1].text, r"I rose from the desk and walked over."); } _ => unreachable!() } story.make_choice(0).unwrap(); story.resume(&mut buffer).unwrap(); assert!(buffer[0].text.starts_with(r#"A noise rang from the door."#)); assert!(buffer[1].text.starts_with(r#""Hello?" I shouted."#)); assert!(buffer[2].text.starts_with(r#""Who's there?""#)); }
results in this “game” for the user (in this case picking the first option):
A noise rang from the door.
1: "Hello?" I shouted.
2: I rose from the desk and walked over.
> 1
"Hello?" I shouted.
"Who's there?"
Removing choice text from output
As the previous example show, by default, the choice text will be added to the
text presented to the user. Text encased in square brackets []
will, however,
be ignored. Building on the previous example:
#![allow(unused)] fn main() { extern crate inkling; use inkling::{read_story_from_string, Prompt}; let content = r#" * ["Hello?" I shouted.] "Who's there?" "#; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); match story.resume(&mut buffer).unwrap() { Prompt::Choice(choices) => { assert_eq!(choices[0].text, r#""Hello?" I shouted."#); } _ => unreachable!() } story.make_choice(0).unwrap(); story.resume(&mut buffer).unwrap(); assert!(buffer[0].text.starts_with(r#""Who's there?""#)); }
1: "Hello?" I shouted.
> 1
"Who's there?"
Note how the choice text is not printed below the selection.
Advanced: mixing choice and presented text
The square brackets also acts as a divider between choice and presented text. Any text after the square brackets will not appear in the choice text. Text before the brackets will appear in both choice and output text. This makes it easy to build a simple choice text into a more presentable sentence for the story:
#![allow(unused)] fn main() { extern crate inkling; use inkling::{read_story_from_string, Prompt}; let content = r#" * "Hello[?"]," I shouted. "Who's there?" "#; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); match story.resume(&mut buffer).unwrap() { Prompt::Choice(choices) => { assert_eq!(choices[0].text, r#""Hello?""#); } _ => unreachable!() } story.make_choice(0).unwrap(); story.resume(&mut buffer).unwrap(); assert!(buffer[0].text.starts_with(r#""Hello," I shouted. "Who's there?""#)); }
1: "Hello?"
> 1
"Hello," I shouted. "Who's there?"
Nested dialogue options
Dialogue branches can be nested, more or less infinitely. Just add extra *
markers
to specify the depth.
#![allow(unused)] fn main() { extern crate inkling; use inkling::{read_story_from_string, Prompt}; let content = r" * Choice 1 * * Choice 1.1 * * Choice 1.2 * * * Choice 1.2.1 * Choice 2 * * Choice 2.1 * * Choice 2.2 "; let mut story = read_story_from_string(content).unwrap(); let mut buffer = Vec::new(); story.resume(&mut buffer).unwrap(); story.make_choice(0).unwrap(); match story.resume(&mut buffer).unwrap() { Prompt::Choice(choices) => { assert_eq!(&choices[0].text, "Choice 1.1"); assert_eq!(&choices[1].text, "Choice 1.2"); story.make_choice(1).unwrap(); match story.resume(&mut buffer).unwrap() { Prompt::Choice(choices) => { assert_eq!(&choices[0].text, "Choice 1.2.1"); } _ => unreachable!() } } _ => unreachable!() } }
Any extra whitespace is just for readability. The previous example produces the exact same tree as this, much less readable, example:
#![allow(unused)] fn main() { extern crate inkling; use inkling::read_story_from_string; let content_nowhitespace = r" *Choice 1 **Choice 1.1 **Choice 1.2 ***Choice 1.2.1 *Choice 2 **Choice 2.1 **Choice 2.2 "; let content_whitespace = r" * Choice 1 * * Choice 1.1 * * Choice 1.2 * * * Choice 1.2.1 * Choice 2 * * Choice 2.1 * * Choice 2.2 "; let story_nowhitespace = read_story_from_string(content_nowhitespace).unwrap(); let story_whitespace = read_story_from_string(content_whitespace).unwrap(); assert_eq!(format!("{:?}", story_nowhitespace), format!("{:?}", story_whitespace)); }