ink has finally reached version 1.0!
We realise that it took us so long to get to this point, and in particular that it's been such a long since our last release. Game development can be very intense, so simultaneously maintaining a project such as this one at the same time can be a little tricky.
However, the great news that happened in November 2020 is that we become a proud recipient of an Epic MegaGrant, which will hugely help in the support of the development of ink.
Here are the release notes for version 1.0. There are major new features, though they're mostly on the game side of things rather than the format or writing side of things:
Major new feature: Multiple parallel flows (BETA)
It's now possible to have multiple parallel "flows" - allowing the following examples:
- A background conversation between two characters while the protagonist talks to someone else. The protagonist could then leave and interject in the background conversation.
- Non-blocking interactions: you could interact with an object with generates a bunch of choices, but "pause" them, and go and interact with something else. The original object's choices won't block you from interacting with something new, and you can resume them later.
The API is relatively simple:
story.SwitchFlow("Your flow name")
-- create a new Flow context, or switch to an existing one. The name can be anything you like, though you may choose to use the same name as an entry knot that you would go on to choose withstory.ChoosePathString("knotName")
.story.SwitchToDefaultFlow()
-- before you start switching Flows there's an implicit default Flow. To return to it, call this method.story.RemoveFlow("Your flow name")
-- Destroy a previously created Flow. If the Flow is already active, it returns to the default flow.
For now it should be considered a beta feature, since it's brand new and relatively untested. We will also be keeping an eye on how people make use of it to see if API improvements can be made.
ink now has a proper bool type
- Booleans are now supported natively in-engine, but they also implicitly coerce to integers to support old behaviour of
true + 1 == 2
. This is useful when you want to convert a flag (have you done X) to a counter (how many times have you done X).
Significant technical improvements:
These are significant improvements, however they may cause breaking changes to your existing setup:
- Better EXTERNALs behaviour: Many people were having issues where their
EXTERNAL
functions were unexpectedly called twice. This was because of the way the story may sometimes look ahead to see what's coming so that glue can function properly. We have now changed the behaviour so that by default it will do exactly what you expect and only ever be called once. However, if you need your functions to be compatible with glue functionality, you can pass the newlookaheadSafe
parameter when you bind a function. For more on this, see the documentation, and original discussion of the issue. - Better error handling: Redesign the way error handling works, so that it's not possible to accidentally ignore errors by default. Previously the Story would silently generate errors that you were supposed to check from
story.hadError
and insidestory.state.currentErrors
but it was far too easy to forget to do this when starting a new integration into a game. The new behaviour is that you need to bind a callback tostory.onError
. If you don’t, it will throw an exception on error, so you’ll definitely see it.
Other improvements
The rest of the changes are primarily technical and bug fixes:
- New:
InkList.FromString(shortName, story)
function - New: Events in runtime story:
onDidContinue
,onMakeChoice
,onEvaluateFunction
,onCompleteEvaluateFunction
,onChoosePathString
. These aren’t recommended as the general approach, but in some cases they are necessary - for example ink-unity-integration’s player window - New: ink engine and inklecate now uses dotnet core (Thanks @13xforever!)
- Support for work-in-progress ink language server by @ephread: Support for character ranges in debug line metadata. Use identifier type rather than plain strings internally in compiler so that you can find a location where a symbol was defined.
- All internal members have now been marked public. Although this is a little risky and I liked the smaller API footprint, I’d rather have the engine more easily hackable/dangerous than blindly prevent people from using certain functionality.
- Stats switch on command line compiler: “-s”. Designed for use in Inky’s “word count and more” option.
- New JSON communication protocol in inklecate for more robust integration with Inky.
- Added overload of BindExternalFunction that takes 4 parameters
- Allow passing ink list as function argument
- Unicode character ranges: support for Korean (thanks @jungeunjin-dev), Latin1Supplement (thanks @landryyrdnal)
- Fix for broken not-equals operator on divert targets. (thanks @russellquinn)
- Fix for rolling back state after having followed a default choice 😱
- Fix for integer overflow when calling RANDOM
- Fix for error in the profiler when current pointer is null
- Prevent super large integers from being successfully parsed yet coming out as zero
- Fix for visit counts of choices/gathers being wrong when they're in a container nested as the first container of another container
- Prevent storing a divert target to a knot/function that takes by-reference arguments since it’s not possible to call correctly at runtime
- Ensure keys are removed from _variableObservers if they are empty. (thanks @mdorr)
- Observer param in RemoveVariableObserver now optional (thanks @michael-badrobotgames)
- Improved some error messages