inkle / ink

inkle's open source scripting language for writing interactive narrative.
http://www.inklestudios.com/ink
MIT License
3.99k stars 483 forks source link

[Question] Break execution to not call all methods when Continue is called. #526

Open ChaseLewis opened 5 years ago

ChaseLewis commented 5 years ago

So I'm wanting to try to use Ink for a part of my game where there will be dialogue in a visual novel type context.

For this I imagine most of my code will be very declarative.

EXTERNAL SetCharacterName(x)
-> start

=== start ===
{SetCharacterName("Chase")}
Hello, Jeremy!
//I'd like to put a pause here until I call Continue again
{SetCharacterName("Jeremy")}
Hello, Chase!
->END

Ink wants to parse all this at once. Is there a way to essentially tell Ink to break? Super new, but I'm looking through the tutorial and am unsure how to tell Ink to not call both SetCharacterName immediately upon reaching start.

Edit: Seems the way this is supposed to be done is tags. I still think it might be nicer to have a declarative way to pause moving forward. It would give users a more powerful way to declare things for playing stuff like animations and such. You could hook-up whatever functions you wanted. Tags also are not nearly as flexible.

clembu commented 5 years ago

The only way to stop on continue is to have a line of content that means, some text and a end of line token.

So if you want to have external function calls on a continue step, and no content, you need to have some content that you can ignore.

So you could have a convention like, for example, "any line that is exactly __DUMMY__ is safely ignorable and should be used to support story steps only containing logic and tags"

You could also have a format like EXEC COMMAND that could be used to write an ink file like this:

= dialog_stitch
EXEC SET NAME "John"
EXEC PLAY ANIM "shrug"
Heh. Whatever.

But that would mean, unless you're smart about it in your game and automatically call continue again when needed, the player would have to click to see the name, then click to see the animation, then click again to see the text. Doesn't make me want to play that. You're better off using the tags approach and be smart on your game logic.

Plus, ink tags are declarative.

= dialog_stitch
# name John
# anim shrug
Heh. Whatever.

looks declarative enough.

ChaseLewis commented 5 years ago

Thanks for the response. I don't mind doing my own scripting to an extent, but when there is a nice built in methodology like EXTERNAL functions it seems odd I need to script the scripting language to call methods correctly. Not just write methods to call and bind them to my Story. When the only reason that is required is the non-sequential execution of methods. Also, my issue is that when I call continue it executes ALL functions in the dialogue even past the text where the last continue statement ended.

My example:

EXTERNAL SetCharacterName(x)
-> start

=== start ===
{SetCharacterName("Chase")}
Hello, Jeremy!
//I'd like to put a pause here until I call Continue again
{SetCharacterName("Jeremy")}
Hello, Chase!
->END

My example though executes like it was written like:

EXTERNAL SetCharacterName(x)
-> start

=== start ===
{SetCharacterName("Chase")}
{SetCharacterName("Jeremy")}
Hello, Jeremy!
Hello, Chase!
->END

So when I call continue it sets the name to Chase then sets it to Jeremy before returning the line "Hello, Jeremy!". While I get the language has some ways around this (tags), external function appear to not execute in a sequential. I don't mind writing some scripting, but when the language has such a great feature like external binded functions that really cuts into their usefulness and instead I have to write a scripting parsing for my scripts to work around it. Users wouldn't HAVE to write awkward tag scriptors if methods executed sequentially. Especially in Unity where adding a method is so easy having this execution be sequential would be super useful.

That just appears fundamentally incorrect. I see stuff in the documentation mentioning this so I realize the issue is known, but this quirk really hurts what otherwise appears to be a super useful technology. I've helped work on 1 VN translation project before and the languages there were pretty similar to this, expected to have to write some markup into the language for text animation and such but this quirk I think increases the complexity of using Ink.

In the VN project I worked on ';' meant everything would be executed until that point or it reached a choice. Since you might want several lines of dialogue in a text box, this has more potential then just helping out sequential execution of methods.

clembu commented 5 years ago

Your understanding of external functions is biased and wrong, here. I suggest you read the runtime guide again, to familiarize yourself again with what they are and what they are not. Namely, they should not have side effects on the game side.

This is because of the way Ink executes the story. For each call to Story.Continue, Ink reads the rest of the program until it finds an end of line, executing every bit of logic on the way, and then does it again to check if there are any glue at the beginning of the next line, in which case it doesn't print an end of line in the text Continue returns. Until a better way to handle glue is found (which would still require a look-ahead), this is how it works. External functions could not be excluded from this "look ahead" step because of their potential use in logic lines where results of such external calls would influence the story flow.

clembu commented 5 years ago

Please also see this comment: https://github.com/inkle/ink/issues/253#issuecomment-272395950

ChaseLewis commented 5 years ago

Yeah, I mentioned I saw that it 'shouldn't have side effects on the game side' in the guide. Just was curious if the project would be open to removing that. At least with Unity where the functions are typed this looks like it can be done. For untyped languages you'd need a new keyword since you don't know if the value returns text or not without knowing the functions return type. At least if you use 'glue' to continue forward rather than terminating the continue on a stopping character.

I'll probably do a fork to accomplish this. I think it would be very powerful for the scenario editor to have more game play control of simple things from within their scripting platform. Then doing stuff like playing audio, setting the background, etc becomes very, very easy to do. Inky seems to be tied to other technologies so maybe it doesn't make much sense here.

Just wanted to see how the project responded to such a request as it would make it much more powerful imo. Your work is impressive and I really like it, thank you for responding 👍

clembu commented 5 years ago

Haha I'll let Inkle get their thanks, I'm not part of the team, just an enthusiast from the community :)

joningold commented 5 years ago

Externals are for querying the game state. If you want to react to the game state use variable observers instead (for instance, set an ink variable called “character” internally and observe it to respond to it.)

Or else: use tags. They’ll do exactly what you need here, without the need to rewrite the engine.

On Thu, 30 May 2019 at 7:35 pm, Clément Busschaert notifications@github.com wrote:

Haha I'll let Inkle get their thanks, I'm not part of the team, just an enthusiast from the community :)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/inkle/ink/issues/526?email_source=notifications&email_token=AAHDJI74HQGRY2XJBEOO5DLPYAM73A5CNFSM4HQ5CFO2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWTETJA#issuecomment-497437092, or mute the thread https://github.com/notifications/unsubscribe-auth/AAHDJI6IVADI2DHJQJR7KXDPYAM73ANCNFSM4HQ5CFOQ .

-- Jazz, in Three-Four Time: A Novel http://www.amazon.co.uk/dp/B004K1EYIE

ChaseLewis commented 5 years ago

Hm, i'll try the internal variable monitoring. See if that pauses everything as one would hope. Still feels restrictive in comparison, but might work better than tags.

joningold commented 5 years ago

It’s a trade-off between execution pausing and glue; but glue you can’t do any other way.

Marking up functions as “pausing” functions is on our radar as an idea; it’s not ideal either, as it’s very subtle and will make people wonder why:

Text {sub_with_an_external_somewhere_in_its_stack()} more text

... doesn’t produce the single line they expect it to (and that it will in inky, but not in an export.)

The “best” solution we’ve come up with is another type on external call called an ACTION or something which explicitly means stop - do something in game code - move on, and doesn’t look like a function. We haven’t added this yet because you can already do it by parsing the text output of the game. (And this is what we do in Heavens Vault; where

START SHOT: CameraHighUp

Is produced as text, parser in game code and then executed rather than printed.

FWIW tagging lines is what most people doing VN setups have done because it’s script-like.

-- Jazz, in Three-Four Time: A Novel http://www.amazon.co.uk/dp/B004K1EYIE

zyq0825 commented 4 years ago

@joningold The ACTION is exactly what I'm looking for... Would you add to the documentation some day? I don't know how to write a parser myself...đŸ˜„

tlitookilakin commented 4 years ago

One possible solution would also be to use tags to set a kind of "safe to execute" flag, since line tags won't actually change until Continue() is called... something like this: blah blah blah #SAFE_EXTERNAL ~ external_function() and then check the current tags inside the function.