Introduction
In this article, we are going to learn about a feature of merging multiple scenario scripts into a single scenario script and a feature of getting token codes specified for asynchronous commands in ScenarioFlow.
These features are not essential for creating dialogue scenes, but they can be useful depending on the situation. Especially, we need to get token codes specified for asynchronous commands when implementing a save function for dialogue scenes.
Set Up the Sample Project
Import other-features.unitypackage
below to an Unity project to preapare programs and objects needed for this learning.
The sample project includes the following five scenario scripts, and we will run Story
(Composite Scenario Script). Make sure that this script is asigned to the ScenarioScript
property of the ScenarioManager
object on the scene, and then run the sample scene.
- Story (Composite Scenario Script)
- Story-Intro (SFText)
- Story-Composite (SFText)
- Story-TokenCode (SFText)
- Story-End (SFText)
The important points of this sample are, (1) a running dialogue scene consists of multiple scenario scripts, and (2) token codes specified for asynchronous commands are shown on the console. Refer to 【ScenarioFlow】How to Write SFText and 【ScenarioFlow】Practical Features: Branching Scenarios and Fast-Forwarding for the details of the commands in the sample project.
Merge Sceanrio Scripts by Composite Script
How to Use Composite Script
We can merge multiple scenario scripts into a single scenario script using composite script.
We create a new composite script by right-clicking on the Project window and selecting Create/ScenarioFlow/Composite Scenario Script
.
We asign scenario scripts to be merged to the Scenario Script
property after creating a new composite script.
The sample project has the composite script named Story
, on which the four scenario scripts are registered. Then, the single dialogue scene that consists of the dialogue scenes described in the four registered scenario scripts will be played if we run the Story
composite script.
Important Points of Composite Script
Reagarding use of composite script, keep the following points in mind.
- Scenario scripts registered on a composite script are merged in the order of the registration
- Registered scenario scripts are independent of each other
- A registered scenario script can refer to labels declared in other registered scenario scripts
First, a composite script generates a single scenario script from multiple scenario scripts, and the generated content differs depending on the registration order. A composite script merges registered scenario scripts from the top to the bottom. Make sure that the Story
composite script merges the registered scenario scripts in the order of Story-Intro
, Story-Composite
, Story-TokenCode
, and Story-End
initially, but the data content shown on the Inspector window changes if you change the registration order of the scenario scripts.
Second, scenario scripts registered on a composite script are independent of each other. This means, for example, macro scopes declared in a SFText registered on a composite script have no impact on other registered SFTexts.
Finally, a scenario script registered on a composite script can refer to labels declared in other registered scenario scripts. Confirm that the show two selections async
command declared in the Story-Intro
SFText refers to the composite script lecture
label and token code lecture
label, which are declared in the Story-Composite
SFText and Story-TokenCode
SFText, respectively. Also, note that labels that have the same name can’t exist across scenario scripts registered on the same composite script.
Sheena | So could I ask you which one you want to know? |
| |
$f-promised | show two selections async |
| {How to merge multiple scenario scripts} |
| --- Jump to {composite script lecture} |
| {How to retrieve specified token codes} |
| --- Jump to {token code lecture} |
SFText#label | //============ {composite script lecture} ============// |
$parallel | change character image async |
| Change the icon to {smile} |
Sheena | OK! Let's learn how to merge multiple scripts. |
SFText#label | //============ {token code lecture} ============// |
$parallel | change character image async |
| Change the icon to {smile} |
Sheena | OK! Let's learn how to retrieve the token code specified for a command. |
SFTextWhen to Use Composite Script
We may want to use a composite script when we collaborate with others to make a large dialogue scene. We can make a single dialogue scene while the team members work independently by creating small dialogue scenes and merging them by a composite script.
Get Token Codes Specified for Commands
Get Token Codes through the ITokenCodeGetter Interface
The ScenarioTaskExecutor
class implements the TokenCode
property, and we can retrieve the token code specified for a command when that command is called by reading that property through the ITokenCodeGetter
interface. (The reason why we access the property thorough the interface is not to access other irrelevant members implemented by the class.)
As the value of the TokenCode
property changes every time a command is called, we have to take care of the place where the value is read and the timing of reading the value in order to get the value properly.
One of the best ways to read the TokenCode
property is to create a decorator of the IScenarioTaskExecutor
interface and read the token code value as the first process of the ExecuteAsync
method that is a member method of the interface.
The IScenarioTaskExecutor
interface is an interface implemented by the ScenarioTaskExecutor
class, which has the ExecuteAsync(UniTask, CancellationToken)
method as a member method. We give an instance of the ScenarioTaskExecutor
class to the constructor of the ScenarioBookReader
class basically, but as it is an implementation of the IScenarioTaskExecutor
interface that the constructor of the ScenarioBookReader
class actually requires, we can include the function of a decrator we create by giving it to that constructor.
The Implementation Example in the Sample Project
Let’s take a look at the example in the sample project. In the sample project, the ScenarioTaskExecutorTokenLoggingDecorator
class is implemented as a decorator of the IScenarioTaskExecutor
interface.
using Cysharp.Threading.Tasks;
using ScenarioFlow.Tasks;
using System;
using System.Threading;
using UnityEngine;
namespace OtherFeatures
{
public class ScenarioTaskExecutorTokenLoggingDecorator : IScenarioTaskExecutor
{
private readonly IScenarioTaskExecutor scenarioTaskExecutor;
private readonly ITokenCodeGetter tokenCodeGetter;
public ScenarioTaskExecutorTokenLoggingDecorator(IScenarioTaskExecutor scenarioTaskExecutor, ITokenCodeGetter tokenCodeGetter)
{
this.scenarioTaskExecutor = scenarioTaskExecutor ?? throw new ArgumentNullException(nameof(scenarioTaskExecutor));
this.tokenCodeGetter = tokenCodeGetter ?? throw new ArgumentNullException(nameof(tokenCodeGetter));
}
public UniTask ExecuteAsync(UniTask scenarioTask, CancellationToken cancellationToken)
{
// Get a token code
var tokenCode = tokenCodeGetter.TokenCode;
Debug.Log(tokenCode);
return scenarioTaskExecutor.ExecuteAsync(scenarioTask, cancellationToken);
}
}
}
C#The ExecuteAsync
method of this decorator gets a token code from an implementation of the ITokenCodeGetter
interface as the first process, print it to the console, and finally call the ExecuteAsync
method of the implementation of the IScenarioTaskExecutor
class stored as a field. The scenarioTask
argument is the asynchronous command being called. We must not operate this argument in the method of a decorator because any processes regarding asynchronous commands are performed by the ScenarioTaskExecutor
class.
An instance of the ScenarioTaskExecutor
class is asigned to both of the scenarioTaskExecutor
field and tokenCodeGetter
field in the ScenarioManager
class. Also, note that in the ScenarioManager
class, a decorator (i.e., an instance of the ScenarioTaskExecutorTokenLoggingDecorator
class) is given to the constructor of the ScenarioBookReader
class but an instance of the ScenarioTaskExecutor
class isn’t.
ScenarioTaskExecutor scenarioTaskExecutor = new(buttonNotifier, buttonNotifier);
ScenarioTaskExecutorTokenLoggingDecorator scenarioTaskExecutorTokenLoggingDecorator = new(scenarioTaskExecutor, scenarioTaskExecutor);
ScenarioBookReader scenarioBookReader = new(scenarioTaskExecutorTokenLoggingDecorator);
C#With these implementations, we can get the token codes specified for asynchronous commands being called. Run the sample scene to make sure that the corresponding token code is output to the console when a command is called.
When to Get Specified Token Codes
We may want to get token codes specified for commands, for example, when we implement a save function in dialogue system. The save function in this context means a function that enables the player to save data by unit of a dialogue whenever they want while a dialogue scene is running.
To implement such a save function in ScenarioFlow, we have to capture necessary data on the scene (e.g., character positions, character images, background image, music, etc.) at appropriate times periodically. An appropriate time is a moment no command is running and therefore the environment on the scene doesn’t change. A moment no command is running is, for example, a moment the system waits for the player’s action (e.g., clicking the screen). A moment the system waits for the player’s action is, for example, a moment right after a command with a non-fluent token code (i.e., standard, forced, or promised) finishes.
In conclusion, it can be said that we sometimes have to get token codes specified for commands in order to capture data necessary for a save function at appropriate times.
Actually, we may not have to get any token codes depending on the way to implement a save function. However, the ITokenCodeGetter
interface is provided so that we can implement this kind of function in multiple ways.
Summary
In this article, we learned how to merge multiple scenario scripts into a single scenario script and how to get token codes specified for commands.
First, we can register scenario scripts on a composite script to merge them into a single scenario script. The important points are:
- Scenario scripts registered on a composite script are merged in the order of the registration
- Registered scenario scripts are independent of each other
- A registered sceneario script can refer to labels declared in other registered scenario scripts
Second, we can get the token code specified for an asynchronous command being called with the TokenCode
property of the ITokenCodeGetter
interface. We have to take care of the place where we read the property and the timing of reading it, and one of the best ways is to create a decorator of the IScenarioTaskExecutor
interface and read the property as the first process in the ExecuteAsync
member method.
We may not have many opportunities to use the features we learned in this article, but we can make use of them in some cases. It is better to keep in mind that these kinds of features exist.
Comments