Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Optional Timed Text (SugarCube 2/Twine 2)

edited January 2017 in Help! with 2.0
Hi there!

Currently I'm setting up text to automatically read out on a timed delay. However, it occurs to me that waiting for text to pop up may be frustrating for faster readers, who may prefer to see the whole passage just pop up instantly.

I'm using the <<timed>> macro with the actual time as a variable which the player will be able to set to either 1s, 2s or 3s. However, there's no way to set it to 0 or an effective 0. I think the minimum is 40ms, but that causes a very unflattering visual effect of text appearing in a downwards swipe, not all of it to pop up at once. (Like it would if there was no <<timed>> macro stalling its appearance).

Currently I've got this in the CSS
.macro-timed-insert {
transition-duration: 1s;
}

This in my StoryInit
<if $textspeed is "">><<set $textSpeed to "3s">>

And I'm doing something like this:
First non-delayed sentence.
<<timed $textSpeed transition>>
Second sentence.
<<next>>
Third sentence.
<<next>>
Fourth Sentence

Some Passage
<</timed>>

It looks pretty dramatic for certain passages, which is why I really want to find a way to have my cake and eat it too.

I considered wrapping the whole thing in an <<if>> statement and then having an <<else>> version with the same thing above, minus the timed effects, but a guy I learned code off once said "if you're starting to duplicate things, you're probably doing it wrong."

Tl;dr: I'm looking for a way in Sugarcube 2 that I can insert a timed macro to slowly read text, but with the option to remove its effects entirely so people who want to read all the text right away can do that.

Comments

  • Please use the code tag when posting code—it's C on the editor bar.

    DairynGM wrote: »
    This in my StoryInit
    <if $textspeed is "">><<set $textSpeed to "3s">>
    Unless you're using <<remember>>, and you really shouldn't be, that is unlikely to be doing whatever you think it is. At the time the StoryInit special initialization passage is executed, all variables will be undefined, so $textSpeed could never be the empty string—unless you're using <<remember>>.

    Also, I don't know if they're transcription errors, however, your <<if>> is missing an opening angle bracket and within its conditional you misspelled $textSpeed as $textspeed.

    How, exactly, are you currently handling player modification of $textSpeed and is it, or should it be, stateful? I actually do not recommend using story variables for option settings as it bloats the story history to no good effect—the Setting API and settings object exist for that purpose.

    DairynGM wrote: »
    Tl;dr: I'm looking for a way in Sugarcube 2 that I can insert a timed macro to slowly read text, but with the option to remove its effects entirely so people who want to read all the text right away can do that.
    That's not easily doable with <<timed>> as its currently written. It's not really difficult to write a custom solution to do exactly what you want, but the text speed situation should be settled first.
  • BTW, I owe you some thanks for bringing this topic up.

    Looking into this helped me find a bug the current <<timed>> and <<repeat>> implementations that triggers when transitions are requested with near minimum delays. It's not related to what you want to accomplish, but I might not have found it for who knows how long if not for thinking about this.

  • Unless you're using <<remember>>, and you really shouldn't be, that is unlikely to be doing whatever you think it is.

    63999543.jpg

    Whoops. I put it in there so I didn't have to run past a passage where it is set, and I could just run into a bug text in any passage with my variable <<timed>> in it and see it at work. Guess that's not an option, though.
    Also, I don't know if they're transcription errors, however, your <<if>> is missing an opening angle bracket and within its conditional you misspelled $textSpeed as $textspeed.

    First one is transcription error, second is actual error. Thanks for the catch! :smile:
    How, exactly, are you currently handling player modification of $textSpeed and is it, or should it be, stateful? I actually do not recommend using story variables for option settings as it bloats the story history to no good effect—the Setting API and settings object exist for that purpose.
    Hadn't quite got around to adding the section where users could manipulate this setting. So far I've just been seeing if I can make it work before allowing players to mess with it.

    I'm honestly not that familiar with the Setting API and settings object. Been reading up since your post. Not sure what you mean by bloating the story history. Is this something I should be watching out for...? It's going to be a long game.

    Another thing I'm not super familiar with is the term "stateful" (so noob. ;_;) I presume stateful means that it needs to remember a variable. If so, yes. I think.
    That's not easily doable with <<timed>> as its currently written. It's not really difficult to write a custom solution to do exactly what you want, but the text speed situation should be settled first.

    Whatever gets me the end result. <<Timed>> just seemed like the best way to achieve it, but given it only does half of what I need, I agree that I probably won't be able to achieve my end goal with this particular macro.
    Looking into this helped me find a bug the current <<timed>> and <<repeat>> implementations that triggers when transitions are requested with near minimum delays.

    Glad to help! Even if unintentionally. :smile:
  • DairynGM wrote: »
    Whoops. I put it in there so I didn't have to run past a passage where it is set, and I could just run into a bug text in any passage with my variable <<timed>> in it and see it at work. Guess that's not an option, though.
    I, honestly, have no idea what you just said. :(

    DairynGM wrote: »
    Hadn't quite got around to adding the section where users could manipulate this setting. So far I've just been seeing if I can make it work before allowing players to mess with it.
    I only asked because I didn't know if you were using the default UI. The `Setting` API sets up the Settings dialog and manages the `settings` object. Some people like the dialog, some prefer to roll their own UI.

    I can throw together an example project later today. Showing both an example setting and the code needed to achieve the effect you want with it.

    DairynGM wrote: »
    […] Not sure what you mean by bloating the story history. Is this something I should be watching out for...? It's going to be a long game.
    Story variables are a permanent part of every moment within the story history from the moment they're introduced onward—unless they're unset. Adding unnecessary story variables makes the history larger—both in memory and its serializations.

    DairynGM wrote: »
    Another thing I'm not super familiar with is the term "stateful" (so noob. ;_;) I presume stateful means that it needs to remember a variable. If so, yes. I think.
    Basically, yes.
  • I've attached the demo I mentioned to this post.
  • I, honestly, have no idea what you just said. :(

    Whoops. :( Um, here's what I was thinking:

    • To slow down text, I needed to use the <<Timed>> macro in a passage.
    • To vary the speed in which text was shown, I would need to use a variable in the <<Timed>> macro.
    • To set said variable, I would need to do so in a passage.
    • Since StoryInit runs first, this would be the optimal passage to set it in.
    • ... And all of this was really roundabout because your solution is infinitely better!
    I only asked because I didn't know if you were using the default UI. The `Setting` API sets up the Settings dialog and manages the `settings` object. Some people like the dialog, some prefer to roll their own UI.

    I wasn't aware you could play with these things, so I was using the default UI. o_o SugarCube is so fun and flexible.
    Story variables are a permanent part of every moment within the story history from the moment they're introduced onward—unless they're unset. Adding unnecessary story variables makes the history larger—both in memory and its serializations.

    Whoa. Right. Mental note to hold back on the story variables then, or unset them once I'm done with them. I'm using a lot of variables to hold things together at the moment. E.g. If someone talks to an NPC, I set a variable so they are ticked off as 'talked to', so the dialogue is not the same when they talk to them again.

    Is there a more efficient way to approach the above scenario rather than a <<set $talkedToJoe is true>>?
    I've attached the demo I mentioned to this post.

    Oh my god. Thank you so much for taking the time out to do that. This solves all my problems. O_O You are the best.
  • Um, I'm loathe to ask for more help, since you've already given me a ton. But I'm curious as to if there's a way to cause text to always delay at = 0 for passages already viewed (e.g. In the history), even if the settings.textDelay is set to 1s/2s/3s.

    The reasoning is this. If I were to create a passage with delayed text and the player were to return to this passage, it would read out very slowly for a second time. While the first time, the text is new and the slow reading is dramatic, having it slowly read out text you've already seen would be somewhat painful.

    The reason being is that with this game, I reuse some passages a lot. E.g. You're talking to an old man, and there may be different conversation topics. No matter which topic you pick, it loops back to the same starting spot. That means you're viewing the same passage quite a few times.
  • DairynGM wrote: »
    Whoa. Right. Mental note to hold back on the story variables then, or unset them once I'm done with them. I'm using a lot of variables to hold things together at the moment. E.g. If someone talks to an NPC, I set a variable so they are ticked off as 'talked to', so the dialogue is not the same when they talk to them again.
    I don't want to oversell that. I'm not saying don't use story variables, just to keep in mind that they aren't free—i.e. don't store the Encyclopædia Britannica in story variables.

    One mistake that I see people making fairly often is to store data which should be immutable in story variables, when data of that nature is better placed within the setup object or possibly somewhere on window object.

    Another is using story variables to hold temporary values and then not unsetting them when they're no longer needed. Temporary variables were added to SugarCube v2 a while ago to help solve that problem.

    DairynGM wrote: »
    Is there a more efficient way to approach the above scenario rather than a <<set $talkedToJoe is true>>?
    That's a fairly standard thing to do. I suppose if a conversation was fixed to a particular passage and visit count, you could use the visited() function to check for it instead.

    DairynGM wrote: »
    […] I'm curious as to if there's a way to cause text to always delay at = 0 for passages already viewed (e.g. In the history), even if the settings.textDelay is set to 1s/2s/3s.
    Yes. Open the Story JavaScript and make the following edit.

    FIND:
    if (delay > 0) {
    

    REPLACE WITH:
    if (delay > 0 && visited() === 1) {
    
  • edited January 2017
    Thank you! That bit of Javascript code is exactly what I was looking for. ^_^ This has saved me an unbelievable amount of messing around.

    Visited() does seem like a better choice, but the long term plan is to add a new game plus feature. My current approach was to track all $talkedToX variables in a spreadsheet, then when the time came, deliberately unset all of these upon starting a new game plus. That way I could have 'Achievement' variables or carry over certain things without the slate being completely cleared.

    That said, if I erased the story history and opted to use Visited() instead, that would probably be cleaner. I guess it's whether or not removing the story history clears *everything* (meaning a New Game + where you might reset levels but keep money couldn't be done) or if removing the history only does just that, removing the history. Might be one of those rare cases <<remember>> is useful, but I get the sense to be wary of it.

    Seems like a bit early in the game to be thinking of this extra feature stuff, but I like to make sure the foundation is solid for later incorporations.
  • Resetting the story history resets everything. The only exceptions would be story variables which were persisted via <<remember>> or values which were persisted in some other fashion. As to the visited family of functions, they work by examining the story history, so they're not terribly useful with an empty history.

    As far as a New Game+ feature. The best way to accomplish that would probably depend on when you wanted the player to be able to activate it.
  • The idea was to allow players to activate New Game + only after they've finished the game, not at any time through a playthrough.

    But after reading about the history kept in SugarCube, I think it only keeps up to 100 passages by default, and you want it less than that for longer games? I can see a way I could get saved history down to 1, since I've disabled the back and forward features and I'm not going to need people to navigate around the history.

    Since the game has 400k words, it's probably going to generate a lot more than 100 passages of history. So if I rely on Visited() to indicate you've spoken to an important NPC before, you go off and view 100 passages, then return to talk to that NPC... it'll lose any indication you've spoken to them before. Assuming all this, it's probably smarter to rely on variables to indicate if you've spoken to them.
  • DairynGM wrote: »
    The idea was to allow players to activate New Game + only after they've finished the game, not at any time through a playthrough.
    If it's only available at the end, then you'd probably be better off storing the data you wanted to keep in a special single-use external area for the purpose, calling Engine.restart(), and having some JavaScript code which would detect the new game data upon startup and perform the necessary actions. It probably sounds more complicated than it is—I can trow together a demo.

    Also, to be clear. I'm really only talking about carrying data forward into a new game+ at the player's request. Simply having persistent achievements, or other stats, could be handled via <<remember>> without any need for what I'm thinking of.

    DairynGM wrote: »
    But after reading about the history kept in SugarCube, I think it only keeps up to 100 passages by default, and you want it less than that for longer games?
    Length alone isn't a significant issue specifically because of the expiry. The primary source of concern is the size of the variable store within each history moment. Even then, I wouldn't worry about it too much as you can always seamlessly lower the number of navigable moments kept within the history if it ever does become an issue.

    DairynGM wrote: »
    I can see a way I could get saved history down to 1, since I've disabled the back and forward features and I'm not going to need people to navigate around the history.
    Reducing the number of navigable moments kept within the history is fairly simple:
    Config.history.maxStates = 1;
    


    DairynGM wrote: »
    Since the game has 400k words, it's probably going to generate a lot more than 100 passages of history. So if I rely on Visited() to indicate you've spoken to an important NPC before, you go off and view 100 passages, then return to talk to that NPC... it'll lose any indication you've spoken to them before.
    That's incorrect, actually. SugarCube v2's State object does keep track of expired moments, it simply doesn't keep them within the navigable portion of the history.

    Think of it as having two histories: a navigable one which tracks all associated data (passage name, variable store, etc) and an expired one which only tracks the passage name.

    The expired portion of the history allows the visited family of functions to work regardless of how many moments have expired from the navigable portion.

    As a historical note. There was a period in v2's development (v2.0.0–v2.3.3) where the story history did not keep track of expired moments, leading to the very issue of the visited family of functions not being entirely reliable. That oversight was corrected in v2.5.0.


    DairynGM wrote: »
    Assuming all this, it's probably smarter to rely on variables to indicate if you've spoken to them.
    Even keeping in mind that the visited family of functions do not have the issue you thought they did, they do have a couple of limitations in this specific instance which complicates matters:
    1. If you ever decide to move where the player meets an NPC to another passage, then you have to updated all of the instances of where you check on that.
    2. If you ever decide to have an NPC who is conditionally available, then it could either be difficult or impossible to use the visited family of functions to know if they've been met.
    So, personally, I'd suggest story variables as it's simply easier from a development standpoint.

    TIP: I'd suggest keeping your variable names as short as makes sense, as they are a part of the variable store—e.g. $metJoe vs $talkedToJoe. I do not want to oversell this, you should definitely use descriptive names. I'm simply saying, don't go crazy with the descriptions.
  • FIFFIF
    edited January 2017
    Is there a reason to keep the variable names short? Does it speed up responsiveness? Just wondering if there was a technical reason for it @TheMadExile
  • Because they're part of each moment's variable store—i.e. the names themselves take up space. That's true for any story format.

    Again. Do not read too much meaning into this, you should most definitely use descriptive names. Just use some common sense. For example:
    → GOOD
    $metSteve
    
    → NOT SO MUCH
    $metAndHadAReallyEngagingConversationWithSteveThePostman
    
    Basically, when being descriptive with names in your code, and you should be, brevity is also something to keep in mind.
  • LOL. Okay. Thank you.
  • edited January 2017
    It probably sounds more complicated than it is—I can throw together a demo.

    It would be extremely helpful, but only if you've got the time or the code lying around. I don't want to impose too much, since you've already helped me more than I can even articulate. :)

    Then again, every demo posted here is helpful for the next guy like me to stumble along, so it's never just helping one person, which is kind of nice.
    Reducing the number of navigable moments kept within the history is fairly simple:
    Config.history.maxStates = 1;
    

    And that would be a good preemptive measure to reduce any memory / processing problems later on?

    Think of it as having two histories: a navigable one which tracks all associated data (passage name, variable store, etc) and an expired one which only tracks the passage name.

    That does make a lot of sense. And there only being one history was the concern I had. Thank you for clearing this up!
  • edited January 2017
    DairynGM wrote: »
    It would be extremely helpful, but only if you've got the time or the code lying around. I don't want to impose too much, since you've already helped me more than I can even articulate. :)
    Attached to this post. Hopefully, I haven't made it incomprehensible. I tried to stick to native markup as much as possible, however, there is still a fair bit of pure JavaScript used. Besides the passages you'll see on the map, there's code in the Story JavaScript.

    It's a very small demo, only showing how you'd do something like carrying over a value into the NG+—coins in this case. That should give you the idea, hopefully.

    Obviously, feel free to ask questions.

    DairynGM wrote: »
    And that would be a good preemptive measure to reduce any memory / processing problems later on?
    Yes. Reducing the navigable history to a single moment reduces all resource usage to the minimum level attainable.

    That said, no one should worry too much about that sort of thing in SugarCube v2. Most projects will never need to worry about resource usage. For the rest, as I noted previously, they can always reduce the number of moments within the navigable history if it ever does become an issue.

    In your case, I would probably suggest pulling the trigger, since you aren't letting players navigate the history anyway. There's no point in keeping more than one moment if your players will never be able to make use of them.
  • edited January 2017
    It's a very small demo, only showing how you'd do something like carrying over a value into the NG+—coins in this case. That should give you the idea, hopefully.

    Obviously, feel free to ask questions.

    Thank you so much! I've examined the demo. I get most of it, but I do have two questions about it.

    1. Why was a value to increase the coin number in StoryInit instead of a passage? I kind of figured it would initialize as 0 if ndef, then you'd add to it via <<set $coins += 50>> in a passage during gameplay, then it would carry it over to a new game based on what you'd accumulated. I'm probably missing something.

    2, It also said /* Values not modified by a NG+. */ in the StoryInit passage. Does that mean that I should for some reason be keeping values in my StoryInit that won't be affected by NG+? The positioning in the StoryInit doesn't seem to be important, from what I can see, since there aren't any sections marked off by code.
    In your case, I would probably suggest pulling the trigger, since you aren't letting players navigate the history anyway. There's no point in keeping more than one moment if your players will never be able to make use of them.

    Done!

  • DairynGM wrote: »
    1. Why was a value to increase the coin number in StoryInit instead of a passage? I kind of figured it would initialize as 0 if ndef, then you'd add to it via <<set $coins += 50>> in a passage during gameplay, then it would carry it over to a new game based on what you'd accumulated. I'm probably missing something.
    The StoryInit special passage is where you should be initializing any variable which will be used at the start of your project. The value of 50 was meant to be the default value, which normally you'd simply do as:
    <<set $coins to 50>>
    
    I could not do that in the demo as it carries over the value of $coins from the previous playthrough. When starting a NG+, the NewGamePlus special passage is executed before StoryInit, meaning $coins will be initialized to some value before your normal initialization code in StoryInit. To handle the fact that $coins may or may not have a value before the code in StoryInit runs, which is supposed to give the player an initial 50 coins, you have to do something like what you see in the demo:
    /* If $coins does not already have a value from a NG+, set it to 0.*/
    <<if ndef $coins>><<set $coins to 0>><</if>>
    
    /* Give the player an initial 50 coins or, in the case of a NG+, an additional 50 coins. */
    <<set $coins += 50>>
    
    I could have written it thus—maybe this would have been clearer:
    <<if ndef $coins>>
    	/*
    		Normal Start: Give the player an initial 50 coins.
    	*/
    	<<set $coins to 50>>
    <<else>>
    	/*
    		NG+ Start: Add the initial 50 coins the player should receive to their
    		existing amount, which was carried over from their last playthrough.
    	*/
    	<<set $coins += 50>>
    <</if>>
    


    DairynGM wrote: »
    2, It also said /* Values not modified by a NG+. */ in the StoryInit passage. Does that mean that I should for some reason be keeping values in my StoryInit that won't be affected by NG+? The positioning in the StoryInit doesn't seem to be important, from what I can see, since there aren't any sections marked off by code.
    I'd assumed that you would not be carrying over every value from one playthrough to the next, as that's not exactly a common thing to do, thus a section for values that aren't modified by a NG+. What you end up doing is up to you.

    As far as the positioning, I thought it was fairly clear. A section at the top commented with Values modified by a NG+ and a section below that commented with Values not modified by a NG+. You don't have to keep them ordered that way, you don't have to keep them separate, you don't even have to have values that aren't touched by a NG+ at all. Again, whether or not you do is up to you.

    It's a demo. It made sense when I was writing it that you would not want to carry every single value in your project over in some way for a NG+. Don't read too much into it.
  • 1. Ahhh. What I was missing was the idea that in some games, people get an initial starting gold, and the idea that you'd get this starting gold on top of your regular gold in a NG+. Thank you for taking the time to explain that! I was missing something.

    2. So it's just keeping things tidy. That's what I thought, but I thought I'd ask in case I ended up entering values not modified by a NG+ in a really inefficient way when there was a more efficient way available. :) And yes, seems I was reading too much into it.

    Thank you again for the demo and your patience explaining all this to me.
  • BTW, nice avatar. I've really got to get around to playing MajiKoi. It's in my backlog once I'm done with Labyrinth of Grisaia.
Sign In or Register to comment.