【ScenarioFlow】How to Choose Token Code

ScenarioFlow
Tool versions
  • Unity: 2022.3.35f1
  • UniTask: 2.5.4
  • ScenarioFlow: 1.1.0
  • SFText Extension Pack: 1.1.0
    • Syntax: v1.1.0
    • Formatter: v1.0.0
    • Utility: v1.0.0

Introduction

There are two types of command used for playing dialogue scenes in ScenarioFlow. The first one is synchronous command whose process finishes immediately, and the second one is asynchronous command whose process takes time to finish. The important difference between synchronous command and asynchronous command is that asynchronous command takes a parameter called token code to change its operating behavior.

In ScenarioFlow, token code plays important roles in making scenario script structure simple and its flexibility high. In other words, to be competent to write dialogue scenes in ScenarioFlow freely, you have to understand the feature of token code and make full use of it.

We are going to learn about token code in this article. There are several kinds of token codes, and our purpose is to understand the features of them and to be able to use different token codes depending on the situation.

We learn about token codes that are mainly used in SFText .

SFText is a kind of scenario script used to describe dialogue scenes in ScenarioFlow, however, token codes that can be used in SFText don’t actually correspond to those in the actual dialogue system completely. That’s because SFText provides its own token codes so that we don’t need to use all the token codes that actually exist. It means that in SFText, we don’t need to differentiate some token codes but we can write a specific token code on behalf of them. All token codes that are uniquely provided by SFText and written in a script are appropriately replaced with other token codes that the actual dialogue system can handle when it is imported to Unity.

We are going to refer to token codes that are used in the dialogue system in practice in the end of this article. But let’s learn about practical token codes used in SFText at first.

Install the Sample Project

Import how-to-choose-tokencode.unitypackage below to an Unity project to prepare programs and objects for the learning.

Important classes and commands in the sample projects are shown below. You can refer to the article “How to Write SFText” for information about other classes and commands.

ClassSummary
ScenarioManagerComposes the dialogue system and plays dialogue scenes
ButtonNotifierImplements the functionalities that enables the player to move dialogue scenes forward by clicking the Next button and cancel commands by clicking the Cancel button
SkipActivatorImplements the functionality that enables the player to toggle skip mode by clicking the Skip button
ShapeAnimatorDefines the commands that move the shapes on the screen
ScenarioTaskConsumerDefines the commands to handle “general token codes”
Clases included in the sample project and their roles
CommandInvoked Direction Implemented in
move all shapes to leftMove all the shapes on the screen to left (to reset the positions)ShapeAnimator
move shape to right asyncMove the specfied shape on the screen to rightShapeAnimator
accept task asyncAwaits scenario tasksScenarioTaskConsumer
cancel task asyncCancels scenario tasksScenarioTaskConsumer
Commands included in the dialogue system and their directions

After importing the file and opening the how-to-choose-tokencode scene, you can run the sample scene. In this sample project, a scenario script asigned to the Scenario Script property in the ScenarioManager object on the Hierarchy window is played. At first, let’s make sure that Storys.ftxt is asigned to the Scenario Script property and then run the sample scene.

Run the sample scene

In this sample project, the Next button and the Cancel button are placed individually, where we move a dialogue scene by clicking the former and cancel running direction by clicking the latter. The skip button, with which we can toggle “skip mode” on and off, is also placed.

The three shapes, circle, triangle, and square are placed on the screen, and in the sample scene, the “animation command” is repeatedly called for each of the shapes in the order of the circle, triabgle, and square. For command calls, different token codes are given. You will understand the feature of each token code by seeing how the shapes move and how the Next button, Cancel button, and Skip button behave while a shape is moving or after a shape finishes moving depending on the token code.

Skip Mode

To understand features of token codes and differentiate them appropriately, you have to know about “skip mode”. We are going to learn about this skip mode in another article again, so we will see the minimum necessary feature of this functionality here.

Skip mode is a functionality that enables the player to skip reading stories in dialogue scenes. In the sample project, the SkipActivator class implements this functionality, and we can enable skip mode by clicking the Skip button. If we actually enable skip mode, dialogues will be skipped (i.e., they will be displayed and switched rapidly) and this skip will look stopped at a certain point.

By enabling skip mode, most directions are skipped but some directions are not skipped

While skip mode is enabled, asynchronous commands are generally skipped. “A command is skipped” means that a command is canceled immediately affter its proces starts, and the next command starts right after that. Neighter implementations of the InextNotifier interface nor those of the ICancellationNotifier interface affect this behavior.

The thing is that this skip doesn’t apply to asynchronous commands if specific token codes are specified for them. In fact, there are commands that are skipped and those that are not skipped while skip mode is enabled in the sample project, and this difference is due to the differences of the token codes specified for those commands. In the example above, the circle on the screen is moving slowly although skip mode is enabled, and it means that a token code that prohibits skip is specified for the command that plays the animation.

Clasify Token Codes

We mainly use the following 8 types of token codes in SFText:

  1. standard
  2. forced
  3. promised
  4. f-standard
  5. f-forced
  6. f-promised
  7. serial
  8. parallel

Here, we calsify these token codes from the three perspectives of “permission level of cancellation”, “start timing of next command”, and “serial/parallel linking”.

Permission Level of Cancellation

Token codes are given to asynchronous commands as their arguments, and asynchronous commands can be canceled while their processes are running. However, in actual dialogue scenes, there are asynchronous commands that should not or must not be canceled. For example, if we want the player not to skip reading certain dialogues, commands that show the dialogues should not be canceled. Also, if a command shows the player selections, that command must not be canceled because the system can’t determine the branch target if that command is canceled.

We can specify what level of cancellation is permitted for an asynchronous command by using “standard”, “forced”, or “promised” token code depending on the situation. The following table shows the token codes used for this specification, their features, and a guideline for choice.

Token CodeCanc. Inst.Skip Inst.Guidline for Choice
standard,
f-standard
PermittedPermittedFor commands whose cancellation is fine
forced, f-forcedProhibitedPermittedFor commands that SHOULD NOT be canceled
promised,
f-promised
ProhibitedProhibitedFor commands that MUST NOT be canceled
The token code clasification based on cancellation permission level and a guidline for choice

Cancel instruction (Canc. Inst. in the above table) is an instruction issued by CancellationNotifier (i.e., an implementation of the ICancellationNotifier interface). A “cancel instruction” is issued when the process of the NotifyCancellationAsync method declared in the class that implements the ICancellationNotifier interface finishes, which cancels a running asynchronous command.

On the other hand, skip instruction is an instruction issued by skip mode. A “skip instruction” is issued everytime an asynchronous command is called while skip mode is enabled, which skips (i.e., cancels) the asynchronous command.

As shown in the table above, (f-)standard accepts both cancel instruction, (f-)forced accepts skip instruction but cancel instruction, and (f-)promised accepts neither cancel instruction nor skip instruction. Examples for standard, forced, and promised are shown below.

An example for “standard”. Both cancellation and skip are valid
Operation confirmation for “standard”
$standard   | move shape to right async  | 
            | Move {Circle} to right     | 
$standard   | move shape to right async  | 
            | Move {Triangle} to right   | 
$standard   | move shape to right async  | 
            | Move {Square} to right     | 
SFText
Example for “forced”. It can be skipped but canceled
Operation confirmation for “forced”
$forced     | move shape to right async  | 
            | Move {Circle} to right     | 
$forced     | move shape to right async  | 
            | Move {Triangle} to right   | 
$forced     | move shape to right async  | 
            | Move {Square} to right     | 
SFText
‘promised’ の動作例。キャンセルもスキップもできないAn example for “promised”. It can be neither canceled nor skipped
Operation confirmation for “promised”
$promised   | move shape to right async  | 
            | Move {Circle} to right     | 
$promised   | move shape to right async  | 
            | Move {Triangle} to right   | 
$promised   | move shape to right async  | 
            | Move {Square} to right     | 
SFText

To choose an appropriate token code depending on the situation from the perspective of cancellation permission level, we consider firstly whether cancellation is acceptable for the system, and secondly how important the command is as direction.

For example, for a command that shows the player selections, the system doesn’t allow that command to be canceled because the system can’t determine the branch target if that command is canceled. Therefore, we specify (f-)promised that prohibts both issue of cancel instruction and that of skip instruction for this kind of commands so that the process will never be canceled.

On the other hand, if the system permits cancellation of a command, we consider the significance of the command. For example, for a command that shows a dialogue, we generally specify (f-)standard to permit its cancellation completely. However, if we don’t want the player to skip reading the dialogue because it is very important in the story but we allow the player to do so if the player really wants to do, we specify (f-)forced. Aditionally, if we definitely want the player to read carefully, we specify f-promised.

The difference between (f-)forced and (f-)promised is especially important. Briefly speaking, specifying the former indicates that the command SHOULD NOT be canceld while specifying the latter indicates that the command MUST NOT be canceled.

Start Timing of Next Command

After a command process finishes, the next command generally starts after the process of the NotifyNextAsync method defined by an implementation of the INextNotifier interface finishes. For example, by defining a NotifyNextAsync method whose process finishes when the screen is clicked, we can realize a functionality with which after a dialogue is shown by an asynchronous command, the next asynchronous command starts and the next dialogue starts to be shown after the player clicks the screen.

That is, after a asynchronous command process finishes, the start of the next command is generally delayed until a “forwarding instruction” is issued by a NextNotifier (i.e., an implementation of the INextNotifier interface). However, by specifying certain token codes for an asynchronous command, we can make the next comand ignore this fowarding instruction and start immediately. The following table shows the token code classification from this perspective, the features of the token codes, and a guidline for choice.

Token CodeFwd. Inst.Guidline for Choice
standard, forced, promisedAwaitedFor commands that display dialogues
f-standard, f-forced,
f-promised
IgnoredFor commands that play animations or show selections
The token code classification based on start timing of next command and a guidline for choice

If f- is put at the head of standard, forced, or promised, after the asynchronous command finishes, the next command ignores issue of forwarding instruction and starts its process immediately. If the token code doesn’t have f- conversely, after the asynchronous command finishes, the next command waits for the issue of a forwarding instruction and starts its process after that as usual. As a sidenote, f- is the first letter of “fluent”.

Examples for standard and f-standard are shown below. Make sure that the next command starts after a command finishes, even if you don’t click the Next button.

An example for “standard”. After a command fnishes, the next command doesn’t start until the Next button is clicked
Operating confirmation for “standard”
$standard   | move shape to right async  | 
            | Move {Circle} to right     | 
$standard   | move shape to right async  | 
            | Move {Triangle} to right   | 
$standard   | move shape to right async  | 
            | Move {Square} to right     | 
SFText
An example for “f-fluent”. After a command finishes, the next command starts immediately, even if the Next command is not clicked
Operating confirmation for “f-standard”
$f-standard   | move shape to right async  | 
              | Move {Circle} to right     | 
$f-standard   | move shape to right async  | 
              | Move {Triangle} to right   | 
$f-standard   | move shape to right async  | 
              | Move {Square} to right     | 
SFText

We usually specify the non-fluent token codes (standard, forced, and promised) for commands that display dialogues. By doing that, we can realize natural behavior that after a dialogue is shown, the next dialogue is shown after the player make an action like clicking the screen. Also, which one we use, standard, forced, or promised depends on the significance of the dialogue as mentioned earlier.

We specify the fluent token codes (f-standard, f-forced, and f-promised) for commands that play animations and show selections. If we specify non-fluent token codes for such commands, it causes a little unnatural behavior that the player has to make an action to move on to the next dialogue after an animation finishes or the player choose a selection. We choose one of the fluent token codes depending on the significance of the direction caused by the command and whether the cancellation is acceptable for the system.

Serial/Parallel Linking

By specifying serial or parallel token code, we can link an asynchronous command to the next asynchronous command and run them as one asynchronous command. The table below shows the features of these token codes.

Token CodeLink MethodFwd. Inst.Canc. Inst.Skip. Inst.
serialSerialIgnoredFollows the nextFollows the next
parallelParallelFollows the nextFollows the nextFollows the next
The features of the token codes that link commands

Specifying serial results in serially linking, where linked commands are executed in order. The next command starts right after a command with serial token code finishes as well as that with a fluent token code, however, unlike in the case of running fluent commands, cancellation of any of serially linked commands causes cancellation of the other linked commands.

An example for “serial” without cancellation. A “serial” command behaves like a fluent command if it is not canceled
An example for “serial” with cancellation. If any of serially linked commands is canceled, the other linked commands are canceled at the same time
Operating confirmation for “serial”
$serial     | move shape to right async  | 
            | Move {Circle} to right     | 
$serial     | move shape to right async  | 
            | Move {Triangle} to right   | 
$standard   | move shape to right async  | 
            | Move {Square} to right     | 
SFText

Specifying “parallel” results in parallely linking, where linked commands are executed simultaneously. As linked commands are executed at the same time, all the commands are canceled at the same time when they are canceled.

An example for “parallel” without cancellation. Parallelly linked commands start simultaneously
An example for “parallel” with cancellation. Parallely linked commands are canceled at the same time
Operating comfirmation for “parallel”
$parallel   | move shape to right async  | 
            | Move {Circle} to right     | 
$parallel   | move shape to right async  | 
            | Move {Triangle} to right   | 
$standard   | move shape to right async  | 
            | Move {Square} to right     | 
SFText

It is very important to note that the cancellation permission level of an asynchronous command with sreial or parallel is the same as that of the following command to which the serial/parallel token code is linked. For example, a serial/paralell asynchronous command accepts both cancel instruction and skip instruction if it is linked to a (f-)standard asynchronous command, but it doesn’t accept either of them if it is linked to a (f-)promised one.

We often use these token codes serial and parallel to connect short character animations to make a longer animation. When we want to create complicated character animations, the number of commands will increase little by little if we try to prepare a command dedicated to each of them, however, we can make complicated animations with a small number of commands by preparing commands that play simple animations and combining them.

Summary of Token Code

The features of the token codes are summarized in the table below.

Token CodeForwarding Inst.Cancel Inst.Skip Inst.
standardAwaitedPermittedPermitted
forcedAwaitedProhibitedPermitted
promisedAwaitedProhibitedProhibited
f-standardIgnoredPermittedPermitted
f-forcedIgnoredProhibitedPermitted
f-promisedIgnoredProhibitedProhibited
serialIgnoredFollows the nextFollows the next
parallelFollows the nextFollows the nextFollows the next

Forwarding instruction is an instruction issued by an implementation of the INextNotifier interface, which makes the next command start its process after a command finishes. Cancel instruction is an insutruction issued by an implementation of the ICancellationNotifier interface, which causes command cancellation. Skip instruction is an instruction that is repeatedly issued while skip mode is enabled, which starts a command and cancel it immediately again and again.

First, there are standard, forced, and promised as the basic token codes. If we specify any of these token codes, after the command finishes, the next command starts after an next instruction is issued. Also, whether cancel instruction and skip instruction are permitted depends on the specified token code.

Second, there are the “fluent” token codes, f-standard, f-forced, and f-promised, which are composed of the above token codes and f- at the heads of them. If we specify any of these token codes, after the command finishes, the next command starts immediately without waiting for forwarding instruction. Also, whether cancel instruction and skip instruction are permitted depends on the specified token code.

Finally, there are the token codes that link multiple commands, and they are serial and parallel. A command with any of these token codes is linked to the next asynchronous command. In the case of serial, linked commands are executed in order, and the next command starts immediately after the serial command finishes as well as in the case of specifying a fluent token code. However, unlike in the case of specifying a fluent token code, if any of the serially linked command is canceled, the other commands are also canceled at the same time. On the other hand, in the case of parallel, linked commands are executed simultaneously, and they are canceled at the same time when they are canceled. The cancellation permission level of a serial/parallel command is the same as that of the following command to which the serial/parallel command is linked.

To choose an appropriate token code depending on the situation, we consider the role of the target command. For example, for a command that shows a dialogue, we specify a non-fluent token code because after a dialogue is shown, the next one should be shown after the player makes an action like clicking the screen. We determine the cancellation permission level depending on the significance of the dialogue. On the other hand, for a command that shows the player selections, we specify a “promised” token code because its cancellation is unacceptable for the system. Both fluent one and non-fluent one will not cause error, but we specify fluent one, f-promised, because it results in more natural direction where the next direction starts right after the player choose a selection.

Other Functionalities

We have learned about the main usage of token code, especially in SFText so far. In this section, we are going to learn about other functionalities of token code. However, as these functionalities are hardly used in practice and use of them is not recommended, you can skip reading this section.

12 Types of Token Codes

The 8 types of token codes we learned already are, strictly speaking, token codes that can be used in SFText. There are actually the following 12 types of token codes that can be used in the actual SceanrioFlow system.

StandardFluentSerialParallel
standardf-standards-standardp-standard
forcedf-forceds-forcedp-forced
promisedf-promiseds-promisedp-promised

The standard token codes that has no prefix and the fluent token codes that has the prefix f- are the same as those used in SFText. The serial and parallel token codes that have the prefix s- and p-, respectively, are almost the same as those used in SFText, but they are different in that their cancellation permission levels can be specified regardless of the following commands to which they are linked.

The important thing is that when we specify a serial/parallel token code, its cancellation permission level should be the same as that of the linked command, which leads to the most natural direction in most cases. Therfore, SFText provides serial and parallel token codes that are used as representatives of serial and parallel token codes, respectively, for simplification. serial and parallel token codes written in a SFText script are replaced with corresponding serial and parallel token codes that have the same cancellation permission levels when the script is important to Unity. In fact, we can confirm that this conversion is actually performed by seeing the Inspector window of a SFText script.

The inspector window of a SFText script. We can see the original text in the “SFText” tab and the converted data in the “Scenario Script” tab. For example, the last element at the 4th line is “p-standard”, which was originally “parallel”

You shoudn’t specify cancellation permission level explicitly, for example, by writing s-standard or p-standard, but you should simply write serial or parallel as long as you don’t have special reason.

General Token Code

We learned that we use mainly the 8 types of token codes in SFText while the 12 types of token codes are handled in the actual sytem. In fact, we can specify other token codes. The 12 token codes are called “special token code” while others are called “general token code”.

Asynchronous commands with general token codes are stored in the SceanrioTaskExecutor class as “scenario task”, and they are executed independently of other asynchronouos commands with special token codes. After a command with a general token code starts, the next command starts immediately as well as in the case of parallel token code, however, it doesn’t mean that the command is linked to the following asynchronous command.

We can wait for completion of scenario tasks by the AcceptAsync method, which is a member method of the IScenarioTaskStorage interface implemented by the ScenarioTaskExecutor class, and we can cancel them by the Cancel method. using these methods, we can create commands to handle general token codes. You can refer to the ScenarioTaskConsumer class in the sample project as an example.

ScenarioTaskConsumer.cs
using Cysharp.Threading.Tasks;
using ScenarioFlow;
using ScenarioFlow.Scripts.SFText;
using ScenarioFlow.Tasks;
using System;
using System.Threading;

namespace HowToChooseTokenCode
{
	public class ScenarioTaskConsumer : IReflectable
    {
        private readonly IScenarioTaskStorage scenarioTaskStorage;

        public ScenarioTaskConsumer(IScenarioTaskStorage scenarioTaskStorage)
        {
            this.scenarioTaskStorage = scenarioTaskStorage ?? throw new ArgumentException(nameof(scenarioTaskStorage));
        }

        [CommandMethod("accept task async")]
        [Category("Task")]
        [Description("Await scenario tasks with the specified token code.")]
        [Snippet("Accept {${1:token code}}")]
        public async UniTask AcceptScenarioTasksAsync(string tokenCode, CancellationToken cancellationToken)
        {
            await scenarioTaskStorage.AcceptAsync(tokenCode, cancellationToken);
        }

        [CommandMethod("cancel task")]
        [Category("Task")]
        [Description("Cancel scenario tasks with the specified token code.")]
		[Snippet("Cancel {${1:token code}}")]
		public void CancelTask(string tokenCode)
        {
            scenarioTaskStorage.Cancel(tokenCode);
        }
    }
}
C#

The accept task async command waits for the cancellation of the scenario task bound to the specified general token code, and the cancel task command cancels the scenario task. Let’s see operating examples of general token codes using these commands. You can run the operating examples we are going to see by yourself with GeneralTokenCode.sftxt in the sample project. You asign this SFText to the Scenario Script property in the ScenarioManager object to see the examples.

With the following script, all the shapes move at the same time, which looks the same as in the case of specifying parallel for the first two commands. However, the second command with parallel and the third command with f-standard are linked while the first command is not linked to the others. We can confirm this by canceling the animation. In fact, if we click the Cancel button after the animation starts, the commands with parallel and f-standard are canceled but the first command with task1 token code is not canceled. After the cancellation, the accept task async command waits for the completion of the task1 command, and it can’t be canceled because promised is specified for the accept task async command.

SFText
$task1      | move shape to right async  | 
            | Move {Circle} to right     | 
$parallel   | move shape to right async  | 
            | Move {Triangle} to right   | 
$f-standard | move shape to right async  | 
            | Move {Square} to right     | 
            |                            | 
$promised   | accept task async          | 
            | Accept {task1}             | 
SFText
The “task1” command is not canceled, even if the Cancel button is clicked

For scenario tasks with the same token code, the completion of their process is awaited or they are canceled at the same time. The following script cancels the scenario tasks with task2 token code by the cancel task command. We can confirm the cancellation behavior by skipping reading the dialogue before the animation finishes.

SFText
$task2      | move shape to right async                                                | 
            | Move {Circle} to right                                                   | 
$task2      | move shape to right async                                                | 
            | Move {Triangle} to right                                                 | 
$task2      | move shape to right async                                                | 
            | Move {Square} to right                                                   | 
Sheena      | Click the cancel button and next button quickly to cancel the animation! | 
$sync       | cancel task                                                              | 
            | Cancel {task2}                                                           | 
SFText
The scenario tasks that have the same token code are canceled simultaneously

Command execution felxibility will improve if we use general token codes, but use of this functionality is not recommended because of the following two reasons.

  • The flow of process will be more likely to be complicated, and as a result of which it will be more difficult to imagine a dialogue scene that is actually played from a scenario script
  • Abuse of this functionality will easily cause problem when the dialogue system has a functionality of saving data

In practice, we can make dialogue scenes flexibly enough using only special token codes, but we don’t need to use general token codes.

Conclusion

We learned about token code that is specified for an asynchronous command as a parameter in this article. Token code enables to call one asynchronous command in several ways and is therefore a powerful functionality to write dialogue scenes flexibly. However, specifying an inappropriate token code can lead to unnatural direction or error on the system. Hence, you have to understand the feature of each token code to be able to choose appropriate token codes depending on the situation.

Comments