inkle / ink

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

procedurally generated choice text #493

Open darbotron opened 5 years ago

darbotron commented 5 years ago

Hi

I have a very specific thing and edge-casey thing I'm trying to do with choices.

It feels like it should be possible and it almost works but not quite & I'm wondering if I'm just missing something tiny, or whether it's impossible...

I uses a custom markup to insert commands inline into ink's output text which is then used in-game to drive various systems - things like tracking who is currently talking, making characters' heads turn toward whoever is talking, setting animation triggers etc.

Anyway, the system I have hits an edge case when dealing with commands inside option text. It's entirely possible to work around it, but the specific ordering of the commands and ink script operators is super precise and I'd like to automate it so that I can reduce the risk of our writer introducing bugs

Consider the following:

// commands are processed at the point the text containing them is read by the game
// this means commands in option text are processed as soon as the game sees the option text rather 
// than when the options are chosen
//
// our commands have the syntax: command:param1@param2...@paramN>>
// any command without the ':' is assumed to be a "SpeakTo"
// @ is a separator the 'D1' and 'ST' are IDs of specific characters
// D1@ST>> means "D1 speaking to ST"
// ST>>means "ST speaking"
//
// -> NEXT -> is a "utility tunnel" which inserts a default "next..." choice which we will probably 
// eventually handle automatically for UX reasons, maybe with text which appears over time or 
// something. whatever. :)

// in this case the game acts as if ST is talking before they choose the options
D1@ST>> Hello? -> NEXT ->       
    + ST>> Hi. Is Mr. Tong in?
    + ST>> Good morning. We're here to see Mr Tong.
    - -> NEXT ->
    D1>> I'm afraid he's out right now.

// it's work-aroundable by doing this ...
D1@ST>> Hello? -> NEXT ->               
    + [Hi. Is Mr. Tong in?] ST>> Hi. Is Mr. Tong in?
    + [Good morning. We're here to see Mr Tong.] ST>> Good morning. We're here to see Mr Tong.
    - -> NEXT ->
    D1>> I'm afraid he's out right now.

// or, by doing this ...
D1@ST>> Hello?        
    + Hi. Is Mr. Tong in?
    + Good morning. We're here to see Mr Tong.
    - ST>> -> NEXT ->
    D1>> I'm afraid he's out right now.

// but neither of these are ideal - one duplicates loads of text, increasing the chance of mistakes; the 
// other puts the command in a super unintuitive place

Anyway I thought maybe I could do the first workaround ( the one with the text duplication problem) programatically - either with a function or a thread:

// generate a spoken option so it works with the in-game ui
=== function SpokenOption( postChoiceCommand, optionText )
    ~return "[<>" + optionText + "<>] <>" +postChoiceCommand +"<>" + optionText

    // the <> are necessary or the { SpokenOption(..) } is treated as a condition to include the option
    // however - I guess because it's inserting a string at run time rather than compile time
    // this generates the option text "[Hi. Is Mr. Tong in?] ST>>Hi. Is Mr. Tong in?"
    D1@ST>> Hello? -> NEXT ->        
    +<>{SpokenOption( "ST>>", "Hi. Is Mr. Tong in?" )}
    +<>{SpokenOption( "ST>>", "Good morning. We're here to see Mr Tong." )}
    - -> NEXT ->

// generate a spoken option so it works with the in-game ui
=== ThreadSpokenOption( postChoiceCommand, optionText, ->ReturnTo )
    + [{optionText}]{postChoiceCommand}{optionText} ->ReturnTo

    // this generates the correct option text and works, but is totally non-scaleable
    // as the divert target used by the thread would need to be unique for every branch it was used for..
    D1@ST>> Hello? -> NEXT ->        
    <- ThreadSpokenOption( "ST>>", "Hi. Is Mr. Tong in?", ->OptionReturn )
    <- ThreadSpokenOption( "ST>>", "Good morning. We're here to see Mr Tong.", ->OptionReturn )
    -(OptionReturn) -> NEXT ->

I guess what I really need is a sort of text insertion macro thing like the C++ pre-processor?

#define SpokenOption( postChoiceCommand, optionText )\
    "[" #optionText "]" #postChoiceCommand "" #optionText

or is there some other way of achieving this which I missed?

As I was typing this I also realised I can fix my specific issue by just processing commands in option text when the option is selected rather than when the option text is processed so I guess it's moot, but I still think something allowing this sort of thing would be a very helpful feature

joningold commented 5 years ago

So:

The option printed on screen is "What's going on here"; on choosing, the dialogue is given to El, and directed at Six.

... in a more compact way. At the moment, that's not something we're considering because, while the current use of []'s is a bit specific, at least its straightforward - cases like this should probably just be handled by game-code transforming the text as you want it to. (And you could invent your own markup for this, if you wanted it; as the >> you're using basically is.)

On Fri, Nov 9, 2018 at 2:52 PM darbotron notifications@github.com wrote:

Hi

I have a very specific thing and edge-casey thing I'm trying to do with choices.

It feels like it should be possible and it almost works but not quite & I'm wondering if I'm just missing something tiny, or whether it's impossible...

I uses a custom markup to insert commands inline into ink's output text which is then used in-game to drive various systems - things like tracking who is currently talking, making characters' heads turn toward whoever is talking, setting animation triggers etc.

Anyway, the system I have hits an edge case when dealing with commands inside option text. It's entirely possible to work around it, but the specific ordering of the commands and ink script operators is super precise and I'd like to automate it so that I can reduce the risk of our writer introducing bugs

Consider the following:

` // commands are processed at the point the text containing them is read by the game // this means commands in option text are processed as soon as the game sees the option text rather // than when the options are chosen // // our commands have the syntax: command:param1@param2...@paramn https://github.com/paramn>> // any command without the ':' is assumed to be a "SpeakTo" // @ is a separator the 'D1' and 'ST' are IDs of specific characters // D1@ST>> means "D1 speaking to ST" // ST>>means "ST speaking" // // -> NEXT -> is a "utility tunnel" which inserts a default "next..." choice which we will probably // eventually handle automatically for UX reasons, maybe with text which appears over time or // something. whatever. :)

// in this case the game acts as if ST is talking before they choose the options D1@ST>> Hello? -> NEXT ->

  • ST>> Hi. Is Mr. Tong in?
  • ST>> Good morning. We're here to see Mr Tong.
  • -> NEXT -> D1>> I'm afraid he's out right now.

// it's work-aroundable by doing this ... D1@ST>> Hello? -> NEXT ->

  • [Hi. Is Mr. Tong in?] ST>> Hi. Is Mr. Tong in?
  • [Good morning. We're here to see Mr Tong.] ST>> Good morning. We're here to see Mr Tong.
  • -> NEXT -> D1>> I'm afraid he's out right now.

// or, by doing this ... D1@ST>> Hello?

  • Hi. Is Mr. Tong in?
  • Good morning. We're here to see Mr Tong.
  • ST>> -> NEXT -> D1>> I'm afraid he's out right now.

// but neither of these are ideal - one duplicates loads of text, increasing the chance of mistakes; the // other puts the command in a super unintuitive place `

Anyway I thought maybe I could do the first workaround ( the one with the text duplication problem) programatically - either with a function or a thread: ` // generate a spoken option so it works with the in-game ui === function SpokenOption( postChoiceCommand, optionText ) ~return "[<>" + optionText + "<>] <>" +postChoiceCommand +"<>" + optionText

// the <> are necessary or the { SpokenOption(..) } is treated as a condition to include the option // however - I guess because it's inserting a string at run time rather than compile time // this generates the option text "[Hi. Is Mr. Tong in?] ST>>Hi. Is Mr. Tong in?" D1@ST>> Hello? -> NEXT -> +<>{SpokenOption( "ST>>", "Hi. Is Mr. Tong in?" )} +<>{SpokenOption( "ST>>", "Good morning. We're here to see Mr Tong." )}

  • -> NEXT ->

// generate a spoken option so it works with the in-game ui === ThreadSpokenOption( postChoiceCommand, optionText, ->ReturnTo )

  • [{optionText}]{postChoiceCommand}{optionText} ->ReturnTo

// this generates the correct option text and works, but is totally non-scaleable // as the divert target used by the thread would need to be unique for every branch it was used for.. D1@ST>> Hello? -> NEXT -> <- ThreadSpokenOption( "ST>>", "Hi. Is Mr. Tong in?", ->OptionReturn ) <- ThreadSpokenOption( "ST>>", "Good morning. We're here to see Mr Tong.", ->OptionReturn ) -(OptionReturn) -> NEXT ->

`

I guess what I really need is a sort of text insertion macro thing like the C++ pre-processor?

define SpokenOption( postChoiceCommand, optionText )\ "[" #optionText "]"

postChoiceCommand "" #optionText

or is there some other way of achieving this which I missed?

As I was typing this I also realised I can fix my specific issue by just processing commands in option text when the option is selected rather than when the option text is processed so I guess it's moot, but I still think something allowing this sort of thing would be a very helpful feature

— 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/493, or mute the thread https://github.com/notifications/unsubscribe-auth/AA40o9SL9IJHYO_rXKhxVjxx2b1Zmrhdks5utZaXgaJpZM4YWtoQ .

darbotron commented 5 years ago

thanks for getting back to me .

Just wanted to make sure I wasn't missing something obvious! ;)

also, as everyone says, thanks so ,much for making Ink it really is pretty freaking cool of you - I hope to be able to return the favour some time ;)