Creating my own StoryInterface

Twine Version: 2.10.0
SugarCube version: 2.37.3

Hi all,

I am trying to create a custom StoryInterface but for reasons unknown to me it does not appear to work.

When I put the following in the StoryInterface passage (taken from SugarCube v2 Documentation):

<div id="passages"></div>

The screen blanks and no passage content is shown. When I try:

<div id="story" role="main">
	<div id="passages"></div>
</div>

Then I get the error:

What am I missing?

1 Like

Sounds like “story” is a reserved ID in Sugarcube?

As explained in the 1st paragraph of the StoryInterface special Passage’s documentation…

The markup is contained within a <div id="story" role="main"> element

So when you place something like the following within your project’s StoryInterface…

<div id="passages"></div>

…a HTML structure like the following is generated when a Passage is visited…

<div id="story" role="main">
    <div id="passages" aria-live="polite">
        <div id="passage-start" data-passage="Start" class="passage">
            Contents of the current Passage being visited, which in my test project was named Start
        </div>
    </div>
</div>

note: an example of the first two elements of the above structure is shown in the documentation directly after the <div id="passages"></div> minimal working example.

The most likely reason you saw a “blank” screen when your project was run, via the Play or “Test” related options if you’re using the Twine 2.x application, was that there was no content in the 1st visited Passage.

Are you using the Twine 2.x application?
If so, what content is in the Passage that has been assigned the “Green Rocketship” icon?
If not, then what content is in the Passage whose Name has been assigned to the "start" property of your Twee Notation project’s StoryData special Passage.

1 Like

Hi Greyelf,

Thanks for enlightening me. I did not realise the wrapper bit is something provided by SugarCube itself. When removing the custom StoryInterface the page was also blank! I guess the JavaScript code is to blame.

I tried to put a canvas on a passage and the javascript to draw on it in the JavaScript section, but it did not work. So I tried to use a custom StoryInterface instead.

Should I put JavaScript on the passage containing the canvas element instead? I tried that before but that does not always seem to work…

Kind Regards,

1 Like

Any HTML elements that are generated from the processing of the contents of the Passage being visited are added to a buffer until the entire contents of the Passage has finished being processed, at which point the element in that buffer are injected into the Document Object Model (DOM) of the page.

This means that JavaScript directly executed within a visit Passage may not be able to find any DOM elements it looks for, because those elements aren’t in the DOM yet

So if the visited Passage is creating the <canvas> element, then the execution of any JavaScript that needs to locate that element needs to either:

  • target the buffer, which can be done via some of the Passage Transition related Navigation Events.
  • be delayed until after the DOM has been updated, and the <<done>> macro can be used to do such a delay.

Without knowing more specifics about the Passage’s content, and what the JavaScript is doing, it is difficult to give a more concise answer.

I have currently the following (stripped down to the minimum to show what I am trying to achieve):

A StoryInit passage where I do my initialization:

<<set $room = 0>> /* current room */
<<set $action = -1>> /* no previous action */
<<set $exitNames = ['north','east','south','west']>>
<<set $roomExits = []>> /* -1 : no exit */
<<set $roomExits.push([-1,1,3,-1])>>
<<set $roomExits.push([-1,-1,2,0])>>
<<set $roomExits.push([1,-1,-1,3])>>
<<set $roomExits.push([0,2,-1,-1])>>
<<set $roomNames = ['room 0','room 1','room 2','room 3']>>

A PassageHeader passage containing the canvas element:

<canvas id="mycanvas" width="100" height="100" style="border: 1px solid red;"></canvas>

A Room passage where I want to display the passage text (a room description and how the player got there etc):

<<if $action >= 0>>You go $exitNames[$action].

<</if>>You are in $roomNames[$room].

A PassageFooter passage containing the navigation buttons:

<<if $roomExits[$room][0] >= 0>>
<<button $exitNames[0] Room>>
<<set $room = $roomExits[$room][0]>>
<<set $action = 0>>
<<=setup.setRoom($room)>>
<</button>>
<</if>>
: // the above repeated for the directions 1..3
<<done>><<= requestAnimationFrame(setup.draw)>><</done>>

And finally I added my JavaScript functions in the Story Javascript page:

let rcx = [1/4,3/4,3/4,1/4];
let rcy = [1/4,1/4,3/4,3/4];
let room = 0; // state.active.variables.room

setup.setRoom = function(r) {
  room = r;
}

setup.drawCell = function(t,w,h,i) {
  const cx = rcx[i];
  const cy = rcy[i];
  const lx = (cx-1/8)*w;
  const ly = (cy-1/8)*h;
  const dx = 1/4*w;
  const dy = 1/4*h;
  if (room == i) {
    t.fillStyle = 'red';
  } else {
    t.fillStyle = 'white';
  }
  t.fillRect(lx,ly,dx,dy);
  t.strokeStyle = 'white';
  t.strokeRect(lx,ly,dx,dy);
}

setup.draw = function (timestamp) {
  const c = document.getElementById("mycanvas");
  const t = c.getContext("2d");
  const w = c.width;
  const h = c.height;
  t.clearRect(0,0,w,h);
  t.beginPath();
  t.moveTo(rcx[3]*w,rcy[3]*h);
  for (let i = 0; i < 4; i++) {
    t.lineTo(rcx[i]*w,rcy[i]*h);
  }
  t.strokeStyle = 'white';
  t.stroke();
  for (let i = 0; i < 4; i++) {
    setup.drawCell(t,w,h,i);
  }
  requestAnimationFrame(setup.draw);
}

Then when I run the story I get this:

Navigation works (i.e. clicking east, gets me to room 1 and the canvas now shows room1 (top right square) highlighted). But I run into the following challenges:

  1. According to the sugarcube docs I should be able to refer to a story variable (e.g. $room) by using state.active.variables.room but this gives me an error. I use a setRoom function for now, but I want the canvas to stay in sync with the story directly (now if the player navigates back to an earlier room by using the history buttons in the sidebar, the canvas does not get updated because setRoom is never called).
  2. When the passage does not fit on the screen, I can scroll but then I either lose the map at the top or the navigation buttons at the bottom. I would prefer the scrolling part to only apply to the passage text (the room passage).

That should be State, not state. It’s case sensitive.

(You also don’t need to use State.active.variables. Just State.variables works fine.)

Gotcha! I was doing a search and found posts mentioning state instead of State. I should have gone to the official SugarCube v2 docs instead…