Documentation:SXG Twine Story

From UBC Wiki

Introduction

CEDaR Space
CEDaR logo.png
About CEDaR Space
CEDaR is a collaborative new media space at UBC Vancouver designed to facilitate community-led innovation of relational technologies.

Contact us through: CEDaR.space@ubc.ca

Associated Pages

https://wiki.ubc.ca/CEDaR_Space


This documentation is dedicated to technical/development documentation of this project using Twine. The goal of this documentation is to explain the systems and the framework that was made from this project for anyone who would like to replicate the project or build upon the existing elements. Therefore, anyone is welcome to create their version of a Twine game using our framework and system. Although requesting for access is required as this is a community-based project. For documentation on the collaboration and processes of community-based research, please refer to this google doc. Otherwise, happy Twining!

Getting Started

Clone this Github repository

Once the repository is cloned, you will have the files listed below:

  • package-lock.json
  • kwitseleqprototypeStyles.css
    • This is a replica of the stylesheet that is used within the story as backup in case something goes horribly wrong and the styles are lost
  • Images
    • This folder stores the assets that are used throughout the story
  • kwitseleqprototype.html
    • This is the build file from Twine 1. This file can be ignored if not working on Twine 1.
    • Note: This build file is still running on the local server. If you wish to publish this online, please connect it to a host.
  • kwitseleqprototypesugarcube.html
    • This is the build file from Twine 2 using Sugarcube 2.36.1 story format. This is the build file that is previously published.
    • Note: This build file is currently hosted by gh-pages.
  • kwitseleqTwineGame.html
    • This is the build file from Twine 2 using Sugarcube 2.36.1 story format. This is the build file that is currently published.
    • Note: This build file is currently hosted by gh-pages.
  • scripts.js
    • This is the javascript file that contains all the systems implemented to run globally throughout the story. For more information on the system, please refer to The Systems for Feature Implementation below.
  • kwitseleqprototype
    • This is a file that one can import as Twee source code in Twine 1. This file can be ignored if not working on Twine 1.
Import Stories

Import the File to Start Editing

Go to your Twine 2 app or your Twine 2 account if you are using the web version and select “Import from File” under the "Library" option to upload kwitseleqprototypesugarcube.html (the older version) or kwitseleqTwineGame.html (the current version). Twine will give you an option to replace the file if it has been uploaded before. Otherwise, it will import the file as a separate story.

To make a different story but keep the structure and the systems from this repository, it is suggested to create your own repository and paste in this repository folder or fork this repository.

Note: Do not push any changes to this repository.

The Github use in this case is to track/keep the iterations on the story file as well as act as a storage for the images used in this game. Since everything is kept inside of Twine, the most important file (aside from any external references such as scripts.js) to keep track of in this case are the html files as it contains all the information of the story.

If you do not wish to clone the repository and would like to start on your own, the rest of this documentation will cover the basics of making a story in Twine as well as references.

The Structure of Twine

This project is built on Twine 2. Twine 2 offers a web version, and also a downloadable app version. Both versions work to build a story, import a story, and delete a story, etc.

If you are using Twine 1, and wish to migrate a Twine 1 story into Twine 2. Please refer to this forum and follow the steps if a migration is needed.

Changing the Story Format

The SXG Twine Story is currently using Sugarcube v2.36.1 story format and version. Therefore, the rest of this documentation will solely be explained based on Sugarcube v2.36.1 syntax.

Change the Story Format

To change the story format (refer to the image on the right for additional reference):

  • Simply click on "Story" in the menu bar, and under the "Story" there will be options under this menu option.
  • Select "Details."
  • At this point, a window will pop up on the bottom right of the screen
  • In the Story Format dropdown, select the desired story format to change to.

Note: The syntax each story format uses differ and Twine 2 does not convert the syntax for you automatically. Therefore, change the story format at your discretion.

In addition, there are also resources such as a Sugarcube 2 documentation and the Twine Cookbook that elaborates on the variety of macros and functions that are built-in.

Passages in Twine

Starting with a New Story

If one is not using the story that was build to make this game and is starting with a completely new story, by default, the story format will be set to Harlowe 3.3.3 and given a Start passage. The start passage is where the game will begin. As one populates more passages, there will be flexibility to change where the game starts.

To change the Start passage in a Story:

  • Select the passage you wish to have as the start
  • Under the "Passage" menu option, there will be an option called "Start Story Here"
  • Select "Start Story Here"

Make a Passage in Twine!

Adding a New Passage

To make a passage in Twine, simply click on the "+ New" button at the menu bar (refer to the image on the right).

When the passage is made, double click to see what's inside the passage. It will be blank and titled "Untitled Passage." One can name the passages whatever they want, however, the passage names can not be repeated (meaning each passage needs to have its own unique name).

The passages accepts plain text, HTML elements and attributes, or any Twine Macros directly in the passages. In the case of SXG Twine Story specifically, both plain text and HTML elements were used.

For more information on how to make a passage, what you can put in a passage, etc. please visit this documentation or the Twine Cookbook.

Link the Passages

Passages on their own are not connected to each other, including the Start passage. To make the game continuous (jumping from passages to passages), we need to link them.

Ways to Link Passages

There are a couple of different ways to link passages together in Sugarcube v2. One could link the passages using link markup( [[link markup]] ) or link macros(<<link>> macro). Either one will do the work, however there is a slight difference. Link macros have the ability to contain link markups and everything a link markup could contain. Link macros can also wrap around other macros and make them links. For example, link macros can wrap around script macros and its Javascript codes so that the code will execute when the link is clicked. However, if you like to see which passage connects to which in the story/edit file, you have to wrap link markups around the link macro in order for the arrows to show. The link macros will still work without the arrows showing. This is just a note for preferences.

Using Buttons to Link Passages

By default, the link macros will display as hyperlink text in the game/story itself. If one wishes to have them all as buttons, one could use the button macro (<<button>> macro). For documentation on how to use the button macro and make them links, please refer to this documentation.

Style Your Passages

Note: This section require prior knowledge to CSS and HTML. For more information on what HTML is, please refer to this documentation. For more information on what CSS is, please refer to this documentation. There are also many other resources online that provides tutorials on CSS and HTML. In order to achieve the styles that is desired, it is strongly recommended to look into and learn CSS and HTML.

Adding a Tag to a Passage

Add a Tag to the Passage

Each passage in Twine is assigned an ID and a class upon creation systemically by Twine. These ID's and classes are usually named and formatted by Twine according to the passage name and can be used to as targets for styling. In addition to styling using ID's and classes, one could assign tags to each passage. Tags can serve couple of purposes: for styling and for visual reference if visually grouping of the passages is needed while editing the story.

Story Stylesheet

To add a tag to a passage (refer to the image on the right for additional reference):

  • Double click into the passage desired to add the tag to
  • At the top of the passage that is opened, there are couple of options given
    • Select "+ Tags"
  • Name the tag and select a color for the tag if desired

Naming convention of a tag:

  • No spaces between words
    • One can use a dash or an underscore to represent spaces
  • Uppercase, lowercase, and special characters are allowed

As mentioned previously, these tags can be used to for styling mainly for background images of a passage. Please refer to the section below for how to implement background images in a specific passage.

Adding Background Images to a Passage

There are several ways that background images can be added to a passage. As discussed above, one could use ID's, classes or tags. Since styling with ID's and classes can already be understood with fundamental knowledge of CSS and HTML, this section will only discuss how to add background images to passages using tags.

Once the tag has been added, we can add some styles to the story stylesheet. The story stylesheet can be found under the menu option "Story" as "Stylesheet" (refer to the image on the right).

This is where one would put in their custom CSS code.

To use a tag as a selector:

  • Since the goal is to make it a background image, we need to add body to the selector first
  • The syntax to use a tag as a selector is [data-tags~= "your_tag_name"]

An example of a complete code to the style would be:

body [data-tags~="ExampleTag"] {

background: url("image.url OR path to image");

}

To complete the styling of the actual background image itself, regular CSS syntax can take care of it.

For information on how to use image hosted and stored on Github using gh-pages to use in Twine as urls, please refer to this section of the documentation.

For information on how to use Twine as a storage for images, please refer this documentation.

Add an Image to a Passage

Above, we've discussed how to insert an image as the background for a passage. To just add in an image to a passage, one could use the image markup([img[Image]]) or an img HTML element (<img>). Either way will work just fine and of course, both ways have the flexibility to allow one to attach links to images. The image markup hyperlink above will explain how to do so using image markups. For making an HTML image element linkable, one could wrap the <<link>> macro around the image element to achieve that.

Identify What You're Targeting with Your Styles

Twine's information architecture is very complicated as the story gets compiled either in test play or as a build file. As a tip and a best practice to editing the styles of a story or a passage, it's best to use the inspector from your browser.

The inspector is a built-in browser tool that allows one to take a closer look at the elements in a browser page. The inspector is available and built-in on most browser engines such as Google Chrome, Safari, and Firefox.

To access the inspector on your desktop device (laptops/computers):

  • Right click on any browser page
  • Select "Inspect" from the options given
  • At this point a window will appear displaying the HTML elements on the given page
  • Selecting an HTML will give you all of the CSS styles associated with the element

Note: Any changes you make from the inspector will NOT be saved. The inspector just helps you "preview" what happens when you make a style change or an attribute change to the selected element.

The Systems for Feature Implementation

Before explaining the main systems, please note that all of these systems are established in the scripts.js file in the repository and called inside the Twine story. The code will only be working in the build file since the scripts.js file needs to be in close relation to the file that is calling the functions/global variables in. Therefore, it is absolutely essential to build the story before testing out any code.

However, if it is preferred to write javascript within the story itself, one could access it by clicking on the story title on the bottom left of the page and selecting “Edit Story Javascript”.  

Another essential note to keep in mind is the way variables and functions should be named in the scripts.js file and also calling them in Twine. In Twine 2 in the story format of Sugarcube 2, variables can be declared by using the <<set>> macro and a dollar sign to declare a variable like so:

<<set $variable to 0>>

However, javascript does not declare variables the same way as any developers skilled in javascript know. To have Twine 2 in the story format of Sugarcube 2 recognize the variable or a function that you are calling without having to declare that it is javascript simply add “SugarCube.State.variables.<the variable name>” like so:

SugarCube.State.variables.variable

To declare functions, simply add “SugarCube.setup.<function name>” like so:

SugarCube.setup.functionName()

To start, declare

<<run importScripts("./<your script file name.js>")>>

or whichever javascript file that will be used in the Start passage in Twine to initialize the file.

Note: This is a built-in macro in Twine 2 in the story format of Sugarcube 2 therefore it is not javascript. Once that is set up, please refer to the rest of the systems below.

Inventory

Starting with the javascript side in the scripts.js file, since the inventory is created as a list of images instead of a list of strings, an object is created to store the image url of each corresponding inventory object like so:

SugarCube.State.variables.objects = {

"Item1":"imageUrl1",

"Item2":"imageUrl2"

...

}

Another variable that is created is an empty array that will be used to append the corresponding and selected inventory item listed in each passage like so:

SugarCube.State.variables.inventory = [];

There are two functions that use these global variables:

SugarCube.setup.addInventoryImage() and SugarCube.setup.removeInventoryImage()

SugarCube.setup.addInventoryImage() appends the image, more precisely the image urls of the selected inventory items into the inventory array and returns them as images in the inventory modal in the story.

SugarCube.setup.removeInventoryImage() drops the image, more precisely the image urls of the selected inventory items that are currently stored in inventory and removes it from the array. Note: This function removes one item at a time.

Example of how these functions are implemented are shown below:

SugarCube.setup.addInventoryImage = function(){

  for (let i=0; i< SugarCube.State.variables.inventory.length; i++){

      console.log('inventory image running');

      var imgElement = document.createElement('img');

      imgElement.setAttribute('src', SugarCube.State.variables.objects[SugarCube.State.variables.inventory[i]])

      jQuery(SugarCube.Dialog.body()).append(imgElement);

      console.log(SugarCube.State.variables.inventory[i]);

 }

}

SugarCube.setup.removeInventoryImage= function(){

  for (let i=0; i< SugarCube.State.variables.inventory.length; i++){

      let imgNode = document.createElement("img");

      imgNode.setAttribute("src", SugarCube.State.variables.objects[SugarCube.State.variables.inventory[i]]);

      imgNode.addEventListener("click", function(){SugarCube.State.variables.inventory.splice(i,1) && jQuery(SugarCube.Dialog.close())});

      jQuery(SugarCube.Dialog.body()).append(imgNode);

  }

}

Moving onto implementing these codes inside Twine 2 Sugarcube 2 itself. First, we need to push/append the selected items into the empty inventory array we started with before, which is SugarCube.State.variables.inventory. Therefore, we have used the <array>.push() method that Sugarcube 2 provides to perform the appending into the array like so:

[[Example Passage][$inventory.push(“Example Passage”)]]

In the case that you need the same passage link to perform two different tasks upon selection, you can use the <<link>> macro and attach javascript inside with the <<script>> macro like so:

<<link Example Passage>>

<<script>>

SugarCube.State.variables.inventory.push("Example Passage");

SugarCube.setup.decreaseRelation("Group2");

<</script>>

<</link>>

Now let’s move onto making the modal and calling the previously made functions in javascript inside the modal. Sugarcube 2 provides a built in modal by using the <<link>> macro and some javascript inside it like so:

<<link "Open dialog!">>

 <<script>>

   Dialog.setup("Dialog");

   Dialog.wiki("Text within the dialog window");

   Dialog.open();

 <</script>>

<</link>>

Sugarcube 2 also has documentation on how these Dialog() methods work. In the case of making the inventory, several of them are used on the javascript side and also the Twine 2 Sugarcube 2 side.

SugarCube.setup.addInventoryImage() is called in Twine 2 Sugarcube 2 like so:

<<link "See Inventory">>

<<script>>

Dialog.open();

Dialog.setup("Inventory");

if (Dialog.isOpen()){setup.addInventoryImage()};

<</script>>

<</link>>

SugarCube.setup.removeInventoryImage() is called in Twine 2 Sugarcube 2 like so:

<<link "Remove Inventory Item">>

<<script>>

Dialog.open();

Dialog.setup("Inventory");

if (Dialog.isOpen()){setup.removeInventoryImage()};

<</script>>

<</link>>

Inventory Example

The completed inventory system should look like this (Refer to the image on the right):

Note: The styles of the modal is inherited from what the modal style came with

Relationship Points

Relationship points is another implementation that enhances or diminishes the conversations in the story. It is not a mandatory system to implement in every story but since the SXG Interactive Story is heavily community based, the team thought it would be nice to implement and emphasize relationship building within the game.

Note: Relationship points are only visually and systemically integrated in the previous version of the game. The current version did not integrate relationship points. However, the system itself is created in the scripts.js file. Therefore, it can still be integrated within the current version if desired. To integrate it with the current version, please follow the instruction in this section.

Relationship Points Map

The idea behind using the relationship points is to trigger different responses from the characters that players are having conversations with based on their satisfaction level with the player’s response. Since there are six closely knit communities in SXG, the team also thought it would be worthwhile to sprinkle relationship points throughout all six communities where the interaction with one character could affect player’s interaction with another character in another community. For example: If the player had a great conversation with Character 1 from Community 1, they will already have a great impression on Character 2 from Community 2 because of that previously built relationship in Community 1.

Let’s take a look at how relationship points are mapped out and given (Refer to the image on the right):

The map above explains which choices, in Twine 2 term, which passage links upon player’s selection will grant them that relationship point. In this story, the team chooses to use passage links to determine whether or not relationships will be granted because the team want it to be based on conversations and choices. There are, of course, other ways to incorporate relationship points or point accumulations of any kind within Twine. It is up to creators to determine where and how they see the points system fit into their story.

Now that we have discussed how it is planned, let’s take a look at how it is implemented as a system. Keeping in mind that these systems are all created in scripts.js and called within Twine, let’s start with the javascript side.

First, a global object is created to store groups of characters, or character names and given 0 as a value to start with like so:

SugarCube.State.variables.relations = {

  "Group1": 0,

  "Group2": 0,

  "Group3": 0,

  "Group4": 0

...

}

It is not mandatory to start these property values at 0, however, in the use case of this story, they needed to start at 0.

Next, functions to increase or decrease the current value in a given property are implemented and are set to increase or decrease the value by an increment of 1 depending on player’s selection. Once again, the increment for increasing or decreasing the points are up to the needs of the creators and it does not have to increase or decrease by 1. The functions are named and implemented like so:

SugarCube.setup.increaseRelations() and SugarCube.setup.decreaseRelations ()

SugarCube.setup.increaseRelation = function(name) {

  if(SugarCube.State.variables.relations[name] != undefined) {

  SugarCube.State.variables.relations[name] = SugarCube.State.variables.relations[name] + 1

}

console.log(SugarCube.State.variables.relations);

}

SugarCube.setup.decreaseRelation = function(name) {

  if(SugarCube.State.variables.relations[name] != undefined && SugarCube.State.variables.relations[name] > 0) {

  SugarCube.State.variables.relations[name] = SugarCube.State.variables.relations[name] - 1

}

console.log(SugarCube.State.variables.relations);

}

Now that the javascript side is established, let’s take a look at how it is implemented in Twine. In Twine, it is important to take a look at the story structure that is built against the relationship map that is previously made for planning to see where the functions should be called. In the case of this story, the functions are called based on the choices that the player selects. Therefore, the functions are called like so:

<<link Example Passage>>

<<script>>

SugarCube.State.variables.inventory.push("Caramel");

SugarCube.setup.decreaseRelation("Group2");

<</script>>

<</link>>

In the case and needs of this story, since the passage link needs to do two tasks upon selection, the <<link>> macro is used so these functions and methods can be called within the link. This is only needed if there is more than one task that a passage link needs to execute.

Google Maps Integration into Twine

Note: An example of Google Maps integration only exists in the current version of the game.

To start, one would need to obtain a Google Maps API key with Google. Anyone can get an API key for Google Maps as long as one has a Google account. To get an API key, please visit this page. Once the API key is obtained, one can use the API key to access and create a Google Map on any platform.

For this project, only a static map is needed. Therefore, this project used the Maps Javascipt APIfrom Google.

The system implemented for the map lives in two places: Mainly in scripts.js and inside Twine passages that need to use the map.

In scripts.js:

First, use the API key for the map to request for the use of the map. The usual way of specifying to an html file as to where connections are supposed to made is this:

<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"></script>

However, there is no way to insert this script element in an html file since all we have are a script file and Twine passages to make the insertion. Therefore, to make the connection to Google Maps API using an API key will have to be through Vanilla Javascript like so:

let scriptTag=document.createElement("script");
scriptTag.setAttribute("async", true);
scriptTag.setAttribute("defer", true);
scriptTag.setAttribute("src", "[https://maps.googleapis.com/maps/api/js?key=AIzaSyDgQFYfJnsJYNA5FI6kWNxaqzivch1Unm0&callback=YOUR https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEYcallback=YOUR_FUNCTION_NAME] ");
document.querySelector("head").append(scriptTag);

Basically, a script element is created using Javascript and the attributes and value to the attributes are appended to this element. Then this script element filled with attributes gets appended to the body of the html file.

Then, write a function to create your map like so:

window.googleMap = undefined;
function initMap(){
   window.googleMap = new google.maps.Map(document.getElementById("map"),{
   center: { lat:40.76, lng: -73.983 },   // latlng at New York as default
   zoom: 13,
   mapTypeId: "satellite",
});
   window.googleMap.setTilt(45);
}
window.initMap = initMap;

The function in this case is named initMap() but one could name it whatever. However, this must be the corresponding function name inside the src value that is given to the map in the previous step.

window.googleMap is to specify what should be in the window. First, before the function is executed, it's set to undefined. Then, once the function executes, window.googleMap becomes the map element and fills the window. window.googleMap needs to be undefined first because we only want it at specific passages, not all of the Twine passages. Setting it undefined will make sure that all passages from the beginning doesn't have a map fill the screen. Then, we can call the initMap() function whenever we need the map to show up by calling the function inside the applicable passages.

Inside the map function, we have the options to set the map. For more information on the options available to set on the map, please visit this Google Maps documentation.

So far, all the code discussed above guides how to create a map in Javascript. What is yet to be done is telling where and what the map should be once it lands inside the html file. To proceed with the progress so far, once again some html elements will have to be made in Vanilla Javascript to put it all together like so:

let mapTag = document.createElement("div");
mapTag.setAttribute("style", "display:none;");
mapTag.setAttribute("id", "map");
document.querySelector("body").append(mapTag);

Similar to the script element example above, here we've created a div element to specify where and what the map is inside the html file.

Note: The ID created in this case needs to correspond to the ID given to the map inside the map function.

Putting all the components we've made so far together in order in the scripts.js file would look like this:

let mapTag = document.createElement("div");
mapTag.setAttribute("style", "display:none;");
mapTag.setAttribute("id", "map");
document.querySelector("body").append(mapTag);
window.googleMap = undefined;
function initMap() {
   window.googleMap = new google.maps.Map(document.getElementById("map"),{
   center: { lat:40.76, lng: -73.983 }, // latlng at New York as default
   zoom: 13,
   mapTypeId: "satellite",
   });
   window.googleMap.setTilt(45);
}
window.initMap = initMap;
let scriptTag=document.createElement("script");
scriptTag.setAttribute("async", true);
scriptTag.setAttribute("defer", true);
scriptTag.setAttribute("src", "https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=YOUR_FUNCTION_NAME");'''
document.querySelector("head").append(scriptTag);

The result of the map would look like this (refer to the image on the right).

Google Maps Example Result

In the Twine passages that use the map:

Due to the way the code was written from the example above, inside the mapTag div element, the style has been set to "display: none;". This means that when the html files compiles in the build file, the mapTag div element exists in every Twine passage, but it's not visible. This is because we only want the map to be visible when the game approaches applicable passages. Therefore, at those applicable passages, we want to set the style of the mapTag div element to be visible like so:

<<script>>

document.getElementById("map").setAttribute("style", "display:block;");

<</script>>

Note: We still have to set the element's style with Javascript here in Twine because we want to target the mapTag element in these specific passages. For more information on how to execute Javascript code inside a Twine passage, please visit this documentation.


At this point, the map should be displaying according like the Google Maps Example Result image above at the passage the style was set to. However, one thing to note here is at adjacent passages, there must be another code that sets the style back to invisible. For example, we have just navigated from Passage Start to Passage A and Passage A is the passage where we want the map. Then we have an option to head into Passage B from Passage A. At Passage B, if we decide to go back to Passage Start, the map will be showing when it wasn't showing before. This is because all the passages that lead to the passages where the map is visible does not automatically remember that the map was supposed to be set to invisible after you visited the passages with the map set to visible. To remedy that, we have to set the map style to those passages that lead to the passages with a visible map like so:

<<script>>

document.getElementById("map").setAttribute("style", "display:none;");

<</script>>

Customizing the Map

Google Maps has an extensive documentation on ways the map could be styled, used, manipulated, etc. The documentation also comes with default methods, functions and events that the Maps API comes with. Here are all of the methods available to use from the Maps Javascript API.

Adding Markers to the Map

In this game, we've added markers to the map by creating a system that generates markers and plugged the system inside the initMap() function in scripts.js.

First, we made an object variable called window.markers (object declaration from line 146 - 208 in scripts.js). Inside this object, we've created keys that are named after the location that the marker corresponds to. Inside these keys, we have properties that pertain to each of the keys. These properties are the options to the markers. It is suggested to name your keys the same as what the corresponding passage would be on Twine.

Next, we made a for/in loop that iterates through window.markers and generate a marker upon finding each key like so:

for (let key in window.markers){

window.markers[key].marker = new google.maps.Marker({

position:window.markers[key].coords,

map:null,

label:{

text:window.markers[key].label,

className:"marker-label"

}

})

}

One thing to note so far while looking at this for loop is that the map is set to null. This is because in our case of the markers, we only want specific ones to show up when they should. Therefore, at this step, we're not specifying the map for each marker that gets generated, or else all the markers will just show up on the map at all times. Therefore, to add the generated markers onto the map when we want it to, we'll have to set the map to those corresponding makers in the passages we'd like them to show up.

First, we'll navigate and edit the passage we'd like the marker to appear at.

Then, inside that passage, we'll put in some javascript like so (and of course, since it's javascript, we'll have to use the <<script>> macro to declare it:

<<script>>

window.markers["key_name"].marker.setMap(window.googleMap);

<</script>>

This line above sets the corresponding marker to the map at the specific passage.

Finally, the last step is to ensure that the same marker that has just had the map set don't remain on the map at other passages when it's not supposed to. This will be the same process as hiding the map from above, where we need to set the map back to null again at adjacent passages like so:

<<script>>

for (let key in window.markers){

window.markers[key].marker.setMap(null)

}

<</script>>

Adding Click Events to Markers

All the markers generated from Google Map's API have click events already attached to them. However, this only means that you can click on the markers, it doesn't necessarily mean that something will happen once you click on the markers. Therefore, to specify what clicking on the markers will do, we can add a click event to the markers. Google Maps has generated its own unique eventListener to the markers called addListener(). The only event we're interested in the case of this game is a click event, but for other available events to use from Google Maps, please refer to this documentation.

In the game specifically, we have already set up markers with positions unique to each passage with visible map, and each of the markers are designed to be clickable to move users forward to the next passage, so the click on the markers in this case will have to bring us to a passage. Each marker with the unique position needs to bring users to a unique passage. These markers events need to attach to individual markers at the passages that the markers positions are made unique. Luckily, we already have a system that generates each marker from keys that correspond to the passage name. Therefore, all we need to do is attach the click event to the for/in loop we've created from above so we know that every time a marker is generated, a click event will also be attached to that marker. The click event is added on top of the for/in loop and executed like so:

for (let key in window.markers){

window.markers[key].marker = new google.maps.Marker({

position:window.markers[key].coords,

map:null,

label:{

text:window.markers[key].label,

className:"marker-label"

}

})

window.markers[key].marker.addListener("click",()=>{

SugarCube.Engine.play(key);

})

}


Because this eventListener is a custom-made listener from Google, so we have to use Javascript compatible methods from Twine to specify the event. The method that has proven to work well with Google Maps' event is Engine.play(). The usage here with Engine.play() is simple, we will specify the passage that we want to lead the users to on click of the markers.

This is the step where naming the key corresponding to the passage name is suggested because it eliminates the need for us to give each key another additional property that has the passage name. From here, there are no further steps needed to complete the click events inside Twine since we know that we only need the click events when there are markers added to the map, and the markers added to the map are being controlled already within the corresponding Twine passages.

Data Management

The data of this story is stored in three different places: Github, Twine and Google Drive.

  • On the Twine side, the language pieces and specific contents about the community are stored and compiled in the html files.
    • If you are building a story from scratch on Twine and is not using the below two tools for storage of the story/game images, then you can ignore the two tools below.
  • On the Github side, the images are stored and hosted on gh-pages.
  • On the Google Drive side, the drive contains all the images the team has ever received and used at one point from the community collaborators.
    • The images on Google Drive is separated and grouped into versions. The current version of the game uses images from the "Assets for Current Version (v3)" folder in the drive. The images in this folder are named using this file naming protocol .
    • The images from the previous 2 versions of the game can be found in the "Archive" folder.

One will have to request access to access any of the assets through either of these storage listed above. Before elaborating more on how the images are stored, let’s discuss how the images were obtained and organized.

Obtaining and Organizing the Images

This project uses images of the locations within the communities. With that in mind, it is strongly suggested to advise and give discretions to the community members about how the images will be used and how public or private the images will be before, during and after the process of the project. It is an essential conversation to have with the community members to ensure information of their community is protected to the degree they desire. In the case of this project, the images provided were uploaded onto Google Drive or emailed based on request.

After the images are obtained from the community, it is also advised to be transparent about the organization process of the images. In the case of this project, folders within the Google Drive were created and the community members were advised to upload the images from each community in their prospective folders.

The maintenance process of these images were the same as above; as images were given, they were uploaded into their prospective folders on Google Drive and deleted when necessary.

Image Upload and Hosting on gh-pages

After the images have been populated and organized on Google Drive, here are the steps that are taken to upload, host and use the images in the story.

Note: You must have gh-pages set up on your Github repository in order to do this.

  1. Upload/place the images that are needed for the story from your local/wherever the images are stored on your computer into the repository “images” folder
  2. Make a commit and push the changes locally
  3. Check if the commit went through on origin
  4. Pull all your recent changes locally

At this point, the images that were uploaded should be hosted on gh-pages.

How to Use the Uploaded and Hosted Images from gh-pages

Every gh-pages come with their own unique URL. In the case of SXG Twine Story, here is the general start to the URL:

https://cedarspace.github.io/SXGTwineStory/<FILE NAME>

The URL always begins with the organization or the username followed by the repository name. Therefore, to use the images, one would simply just replace the last part of the URL with the path to the image files.

For example, if one had uploaded an image file named “my_image_file.jpg” , the URL to get the hosted images inside the SXG Twine Story repository would be

https://cedarspace.github.io/SXGTwineStory/images/my_image_file.jpg

All the images used in the current story are external links referenced in the story. The team decided to use external links instead because of issues with the file size capacity and how much every push from local to origin Github can afford. However, if the file sizes are not too big, and the quality of the images are not affected after being compressed, one could still be storing their image files inside the Twine story itself.

Build the Game

Build Options

Building the game/story is very simple. Before we discuss that, let's discuss what the difference is between "Test", "Play", and "Publish to File."

First, to find the options above, simply select the "Build" menu options. Then, the mentioned options will appear (refer to the image on the right).

Test can be thought of as the "preview" mode of your game. It allows you to check what a passage looks like from any passage, regardless if it starts from the Start passage.

Play is a fully rendered version of the game/story that one makes. It does NOT render the passage that you've selected to check. It will begin from the Start passage.

Publish to File is the compiled HTML file/version of the Play option. It can be viewed as an exported project or a saved project that no longer needs Twine to render. So the exported file can stand alone as a playable game/story that is accessible by opening the file.

You do not need Twine to open the file unless you're interested in seeing the passages in edit mode. In that case, please refer to this section for how to import a build file into Twine 2.

All of the options above do NOT create a sharable version of the game, meaning if you share the link to distribute, no one will be able to open the link. To distribute a Twine game/story, one needs to connect the HTML file to a host so it can be accessed publicly. One can easily find a host by subscribing to a hosting website and purchase a domain. This documentation will not be covering how that is done, although part of the documentation has already provided that the compiled HTML file can be hosted through gh-pages on Github. But of course, access to gh-pages is only available through a subscription purchase of a higher level membership in Github.

Current Build

Updated until Jan 31 2022 for kwitseleqTwineGame.html

With the new Google Maps integration in this version. Several components from the previous build version have been forfeited (please refer to the table below and the table in Previous Build section to view the forfeited components). The goal of this iteration is to focus more mainly on the places and for the users to understand where each of the locations and communities are in relation to their own community. The systems from the previous build still exists, although they are not necessarily integrated within this version. Moreover, this version of build emphasized on having a "world" to interact with, therefore, the stories in the game are incomplete. For stories made specific to each locations within a community, please refer to [this documentation].

Note: You will have to request for access to view the contents of this documentation as it does include community-protected contents.

Communities that are Built

Components Build for Specific Aspects in a Community
Structure Styling Images
Aitchelitz Done Done Done
Leq'á:mel Done Done Done
Tzeachten Done Done Done
Yeqwyeqwí:ws Done Done Not Done - Missing Yakweawioose Longhouse
Skowkale Done Done Done
Skawahlook Done Done Done

For more detailed information on where each components references are stored, please refer to the section below for specification.

Note: The images from all the previous builds are stored in the Archive folder within the Google Drive.

Previous Build

Updated until August 31st 2022 for kwitseleqprototypesugarcube.html

Communities that are Built

The following table describes the overall structure completion of each community by category of components that are needed for each community.

Components Build for Specific Aspect in a Community
Structure Plot Styling Images Inventory Relationship Points Mapped? Audible Animal Character?
Aitchelitz Done Not Done May need more with plot addition Done Done No Yes
Leq'á:mel Done Done Done Done Done Yes Yes
Tzeachten Done Not Done May need more with plot addition Done Done No Yes
Yeqwyeqwí:ws Done Done Done Done Done Yes In as "Snake" right now
Skowkale Done Not Done May need more with plot addition Done Done No No
Skawahlook Done Not Done May need more with plot addition Done Done No Yes

To find the Structure for each community, please resort to the Miro board for details.

To find the Plot for each community, please resort to the Miro board for details.

To find the Styling for each community, please resort to the .css file in the project repository or the stylesheet inside the Twine game file itself.

  • Note: Some of the passages have inline styling additional to the styles inherited from the stylesheet

To find the Images for each community, please resort to the images folder in the project repository or this Google Drive.

  • Note: The repository and the google drive should contain the same images but not all images are used in the actual game itself.

To find the Inventory items, please resort to the Miro board for details.

To find the Relationship Points Map for each community, please resort to the Miro board for details.

  • Note: Relationship points updated to this date are accumulated and made to make an impact on the level the players end up with. For example: If the player has gotten inventory items but did not get relationship points, then they would end up with level 2 ending.

To find the Audible Animal Characters for each community, please resort to the .js file in the project repository for everything related to the audio and images folder in the project repository or this Google Drive for the audio icon.

  • Note: The audio files are currently hard coded using the API that a developer, Yuval has created to give us access to the language dictionary database he built in collaboration with a linguistics team.
  • Here is the API link: "https://stoloshxweli.org/units/api/v1/words/?eng="
  • To complete the search, fill in the vocabulary of interest at the end of the url
    • For example: to look for “Sturgeon” the url to use would be:
      • "https://stoloshxweli.org/units/api/v1/words/?eng=sturgeon"
  • To look up the a vocabulary, simply use command line or terminal and type in:

curl -s <the url>

  • Note: Only vocabularies and simple phrases are available in the database as of the date this document is written. For other needs of the database, please consult the linguistics team