inkle / inkle/ink

Minimal Layer for Unity UI Integration

Open
#436 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
4.9k
Forks
540
PR merge metrics
No merged PRs in 30d

Description

Hello,

I am considering adding a thin layer on top of Ink for Unity which is both minimal and very extensible. I wanted to share my idea here to see if it is something others would like open sourced (or if there is even a possibility of getting it included in the Ink-Unity Integration bundle if it is deemed worthy). Please also let me know if I am headed into a well-known trap, since I am fairly new to Ink/C#.

I try to make as few assumptions as possible on the format/UI of the game while still making it easy to get something up on the screen in Unity.

Getting text to the screen is as simple as adding a standard Unity Text UI Object to a scene and adding a "Target" component to it with a target name of "Default". All text will be directed there unless the ink script tells it to go somewhere else.

## Syntax

The basic syntax is as follows for text:

`This is a normal text line`

`@optionalTarget optionalSpeaker: This is still a text line`

As you can see text lines work as normal, but you can optionally add both a target (where it shows up in the UI) and a speaker name to be displayed with the text. (Note: If you need a line which has a ':' in it, you can just start the line with a ':' and it will be treated the same as no speaker name.)

As a concrete example of the ink:
`Bob: Hey Carol!`
`Carol: Hey Bob!`
`@narrator Suddenly they fell in love.`

This would first show "Hey Carol!" in the default UI spot with "Bob" in the speaker label, then Carol saying "Hey Bob!". Then it would show "Suddenly they fell in love." in a different UI component presumably reserved for the narrator's text.

The syntax for other commands are as follows:

`#actionName optional action parameters` //General action

`# @target actionName optional parameters` //Action affecting a specific target

`>> actionName optional parameters` // >> is treated the same as #

`# actionName "a single parameter"` //Quotes stop a parameter from being broken into words

again to show a concrete ink example:
`#play boom`
`#shake`
`#@bob out right`

This would play a sound named "boom", shake the screen, and then animate out a target named bob (presumably showing a picture of Bob) to the right side of the screen. New actions can be easily added (see StageDirections below).

## Director
There is a single "ScriptableObject" called the Director which manages the story state (e.g. save/load) and basically directs things. You drag the Ink JSON file onto it, and then instantiate it into some sort of global variable. You call startStory() on it and away you go.

## Targets
The main concept which is added is that of a target. This is a component which can be added to any object in a given scene. The target has a name which identifies it, and it lets you refer to scene objects which the component is attached to. A target knows how to set the content of the thing it is attached to (mainly text and images), and can also define a desired effect for the content to be added with (e.g. typing text vs instantly appearing text)

There are a few different types of targets defined: Text, Image, Speaker, SpeakerImage, and Choices.
The speaker types are a convenience which let you define a single target which shows both the conversation text and who is speaking if you want to.

The main idea here is that you can route the flow of text and images easily to different spots within the same scene. For example, you may want the narrator text to show up in a different place or you may want each character to have their text show up in a bubble above their head. To do that, you just create the UI and add a Target component to each thing you want to name. Then you just add @TargetName before the line of text you want to direct the content to.

Let's say you have a character image you want to smile, you would just add a target to the container for that character and say something like:
`#@targetName img bob-smile`

The beauty of targets is that you can have different scenes (which you can move between with a built in StageDirection) that have targets with the same name. Thus the text, images, and choices will route as appropriate for whatever scene you are in.

## StageDirections
All tags and text content from Ink get transformed into a series of StageDirection objects. There are builtin StageDirections for things like showing text and images, but you can easily write your own StageDirections to allow whatever functionality is desired (e.g. sound and animation).

Custom StageDirections have an associated keyword that is looked for in the syntax given above (i.e. the 1st word in a tag or directly following a target name). Once that keyword is recognized, the stage direction is passed an array of strings that follow it, and it is asked to either set itself up using them or to refuse based on a syntax mismatch.

When a stage direction is run, it has a startAction method which is called, and then an update method which is called each frame until it returns that it is finished running... at which point the next stage direction in the queue is run. StageDirections are also told whether the user has tapped while they have been running so that they can respond appropriately (e.g. text which is animating with a typing effect can instantly finish if the user taps).

Custom StageDirections are made available by adding them to an array on the Director (which can be done directly in the Unity UI). Thus they can even be enabled and disabled if needed.

By combining custom StageDirections and Targets you can easily do things like having some targets automatically require the user to tap before continuing the conversation and letting others just instantly move on. You could also easily provide actions to temporarily change these settings.

## Choices

Choices follow the same syntax as normal text, with one addition. If the choice has a targetName, then that target is used as a button for selecting that choice (which may mean that the choice shows up in a different spot in the UI from other choices).

`* [@talkButton]->TalkToBob`

Thus the above would show a button in the UI that had a target name of "talkButton" and respond to a tap of that button as the selection of that choice. This could even be done for invisible rectangles or shapes to enable branching based on what part of the screen was tapped.

Note that it is still possible to have other non-button choices at the same time. This is useful for say providing a set of choices in a conversation while having a persistent "Exit Conversation" button in the corner that lets you leave a conversation instead of asking one of the questions.

## Summary & Open Questions

I think this is a very minimal design which still provides a lot of expressive power and extensibility. I am definitely open to suggestions on how to improve the design and warnings of traps which others have seen... especially before I get too far into the implementation (I have the basics working already).

The biggest open question I have is how to handle tags for choices. I have a need to provide metadata for each choice that should show up in the UI in the project I am planning to use this for... which could easily be provided with a StageDirection tag... but it appears from the issues listed here on GitHub that tags don't work with choices. Maybe I should provide syntax which ends the >> variant of tags? Something like `>> action param; This is treated as normal text again`?

Thoughts on the design in general?
Any gotchas headed my way?
Is this something others would be interested in using?

Thanks,
Jon

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.