Updating readme

This commit is contained in:
David Masad
2021-01-30 14:37:36 -05:00
parent 83088139e5
commit 67b76897d0
5 changed files with 358 additions and 21 deletions

View File

@ -1,10 +1,73 @@
# Story Manager
# StoryManager
A lightweight storylet manager for Twee and Sugarcube.
StoryManager is a lightweight add-on for Twine + SugarCube implementing parameterized storylets. It's intended for people who are comfortable using JavaScript along with Twine, and want to use it to manage data for their interactive fiction.
## How to use it
Add `storymanager.js` (and optionally `storymanager-widgets.tw`) to your Twine project. In JavaScript, add some story data if needed, and then add a storylet with a name, some tags (optionally), and a generator function that returns a list of instantiated storylet objects, like this:
```javascript
State.variables.locations = ["Earth", "Mars", "Ganymede"];
State.variables.currentLocation = "Deep space";
StoryManager.storylets["Go somewhere"] = {
name: "Go somewhere",
tags: ["in space"],
generate: function() {
let storylets = [];
for (let loc of State.variables.locations) {
if (loc == State.variables.currentLocation) continue;
let storylet = {
passage: "Orbit", // Name of the passage the storylet links to
description: "Jump to " + loc, // Storylet link text
planet: loc // Data associated with this storylet
};
storylets.push(storylet);
}
return storylets;
}
}
```
Then in Twine, write one or more passages associated with your storylet:
```
:: Orbit
<<set $currentLocation = $currentStorylet.planet>>
You orbit around $currentLocation.
[[Explore the surface]] or [[Jump]] somewhere else.
:: Explore the surface
You take your shuttle down to the surface of $currentLocation.
Return to [[Orbit]]
```
And finally, you need to have a passage where you query for available storylets and display them:
```
:: Jump
You prep your ship to jump.
<<set _possibleStorylets = window.SM.getStorylets()>>
<<ShowStoryletLinks _possibleStorylets>>
```
(This example uses the `<<ShowStoryletLinks>>` widget from `storymanager-widgets.tw`)
You can see this complete example in the `examples\` folder ([twee]() or [playable HTML]()).
### What is a parameterized storylet?
Storylets are pieces of content for a story or game that become available based on some combination of the current state, player choice, and random chance. Parameterized storylets are storylets where some details are intended to be filled in with data from the current story state or some underlying world model. For example, your game might have different characters in different locations, each with a different piece of information to share. You can create a single dialog parameterized storylet, with the character as a parameter. Using StoryManager (or, frankly, your own storylet system) your game could look up which characters are present at the game's current location and offer one dialog storylet for each.
There are a bunch of great resources for learning more about storylets in general. Emily Short has written a [bunch](https://emshort.blog/2019/11/29/storylets-you-want-them/) of [blog](https://emshort.blog/2016/04/12/beyond-branching-quality-based-and-salience-based-narrative-structures/) [posts](https://emshort.blog/2019/01/06/kreminski-on-storylets/) discussing storylet-based game design. Max Kreminski's [survey paper](https://mkremins.github.io/publications/Storylets_SketchingAMap.pdf) is where I got the *parameterized storylet* terminology from. Cat Manning and Joshua Grams have a [great talk on YouTube](https://www.youtube.com/watch?v=JRKqDlAauTQ) on using storylets, built around Joshua's own Tiny-QBN system for Twine. And all of those resources should point you to plenty more if you want to learn all about storylets and how to use them.
### Why another storylet manager?
If you want to do storylets in Twine and Sugarcube, you should first check out [Tiny-QBN](https://github.com/JoshuaGrams/tiny-qbn), which is more mature than this one and requires much less messing around with JavaScript. As of version 3.2.0, the Harlowe story format also has [storylets as a first-class feature](https://twine2.neocities.org/#macro_storylet).
I have a few Twine hobby projects in various stage of completion, and I found myself implementing world-models and procedural generation in JavaScript and trying to tie them to passages. Instead of writing ad-hoc systems per game, I decided to try and make one unified storylet manager -- hence this project. The JavaScript that goes into the StoryManager tool itself is pretty simple (seriously, [look at it]()), but I'm hoping someone else might find this useful too.
## Feature list
@ -18,4 +81,6 @@ A lightweight storylet manager for Twee and Sugarcube.
- [X] Widget for displaying storylet links
- [ ] Make the widget into a macro
- [ ] Weighted random choice
- [ ] Explore replacing storylet generators returning arrays with the `yield` keyword? **Pro:** produces cleaner code; **Con:** requires users to understand `yield` and remember to use the function * notation.
- [ ] Explore replacing storylet generators returning arrays with the `yield` keyword? **Pro:** produces cleaner code; **Con:** requires users to understand `yield` and remember to use the function * notation.
- [ ] Add storylet code to passages (as comments, a-la Tiny-QBN?)
- [ ] CSS styling (probably to go with widgets/macros?)

View File

@ -100,12 +100,7 @@ var saveAs=saveAs||navigator.msSaveBlob&&navigator.msSaveBlob.bind(navigator)||f
<div id="init-lacking">Your browser lacks required capabilities. Please upgrade it or switch to another to continue.</div>
<div id="init-loading"><div>Loading&hellip;</div></div>
</div>
<!-- UUID://BE18C022-A213-466C-8DD1-DCCD5CB1DF48// --><tw-storydata name="At the Duchess&#39;s Party" startnode="2" creator="Tweego" creator-version="2.1.0+9ea2fab" ifid="BE18C022-A213-466C-8DD1-DCCD5CB1DF48" zoom="1" format="SugarCube" format-version="2.30.0" options="debug" hidden><style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css"></style><script role="script" id="twine-user-script" type="text/twine-javascript">/* twine-user-script #1: "storymanager.js" */
// Choose one of an array
var randomChoice = function(vals) {
return vals[Math.floor(Math.random() * vals.length)];
};
<!-- UUID://BE18C022-A213-466C-8DD1-DCCD5CB1DF48// --><tw-storydata name="At the Duchess&#39;s Party" startnode="2" creator="Tweego" creator-version="2.1.0+9ea2fab" ifid="BE18C022-A213-466C-8DD1-DCCD5CB1DF48" zoom="1" format="SugarCube" format-version="2.30.0" options="" hidden><style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css"></style><script role="script" id="twine-user-script" type="text/twine-javascript">/* twine-user-script #1: "storymanager.js" */
// Set up the general narrative manager
// -----------------------------------------------------------------------
@ -114,11 +109,17 @@ StoryManager.storylets = {};
StoryManager.getAllStorylets = function(tag=null) {
let allStorylets = [];
let storylet, storylets, boundStorylet;
for (let key in this.storylets) {
let storylet = this.storylets[key];
storylet = this.storylets[key];
if (tag === null || ("tags" in storylet && storylet.tags.includes(tag))) {
let storylets = storylet.generate();
for (let i in storylets) allStorylets.push(storylets[i]);
// TODO: If using yield, this part will change
storylets = storylet.generate();
for (let i in storylets) {
boundStorylet = storylets[i];
if (!("priority" in boundStorylet)) boundStorylet.priority = 0;
allStorylets.push(storylets[i]);
}
}
}
return allStorylets;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,51 @@
:: StoryTitle
Simple Space Example
:: StoryData
{
"ifid": "2EE3728E-B079-4539-9A9C-97CD8474B4C5"
}
:: Story JavaScript [script]
Config.passages.nobr = true; // No unspecified linebreaks.
State.variables.locations = ["Earth", "Mars", "Ganymede"];
State.variables.currentLocation = "Deep space";
StoryManager.storylets["Go somewhere"] = {
name: "Go somewhere",
tags: ["in space"],
generate: function() {
let storylets = [];
for (let loc of State.variables.locations) {
if (loc == State.variables.currentLocation) continue;
let storylet = {
passage: "Orbit", // Name of the passage the storylet links to
description: "Jump to " + loc, // Storylet link text
planet: loc // Data associated with this storylet
};
storylets.push(storylet);
}
return storylets;
}
}
:: Start
You find yourself in $currentLocation. You should probably [[jump | Jump]].
:: Jump
You prep your ship to jump.
<<set _possibleStorylets = window.SM.getStorylets()>>
<<ShowStoryletLinks _possibleStorylets>>
:: Orbit
<<set $currentLocation = $currentStorylet.planet>>
You orbit around $currentLocation.
[[Explore the surface]] or [[Jump]] somewhere else.
:: Explore the surface
You take your shuttle down to the surface of $currentLocation.
Return to [[Orbit]]

View File

@ -1,9 +1,4 @@
// Choose one of an array
var randomChoice = function(vals) {
return vals[Math.floor(Math.random() * vals.length)];
};
// Set up the general narrative manager
// -----------------------------------------------------------------------
var StoryManager = {};
@ -11,11 +6,17 @@ StoryManager.storylets = {};
StoryManager.getAllStorylets = function(tag=null) {
let allStorylets = [];
let storylet, storylets, boundStorylet;
for (let key in this.storylets) {
let storylet = this.storylets[key];
storylet = this.storylets[key];
if (tag === null || ("tags" in storylet && storylet.tags.includes(tag))) {
let storylets = storylet.generate();
for (let i in storylets) allStorylets.push(storylets[i]);
// TODO: If using yield, this part will change
storylets = storylet.generate();
for (let i in storylets) {
boundStorylet = storylets[i];
if (!("priority" in boundStorylet)) boundStorylet.priority = 0;
allStorylets.push(storylets[i]);
}
}
}
return allStorylets;