引言
行为(Actions)是Milovana Webtease的核心机制,它构成了一个互动文游的框架和主体。
本文主要介绍一些基础的行为,以及它们的用法案例。阅读本文后,你应该能够制作一个简单的Webtease互动文游。
创建行为
进入Edit界面
要创建一个行为,首先在左侧菜单栏点击按钮(一个类似于铅笔的图标),进入Edit界面。

进入Edit界面后,UI会展示你目前已经创建的页面(Pages)。

创建与选中页面
如果没有页面,那么你需要先创建一个,点击Add Page按钮即可:

创建页面后,单击选中页面进行编辑:

创建行为
此时可以点击Create Action按钮来创建行为,两个按钮是一样的,点哪个都可以:


现在可以随便选择一种行为,行为的类型之后再详细讲解。
创建出来的行为会按照顺序排成一列。

单击某个行为将会选中它,并展示它的编辑页面。

行为的创建位置
如果目前已经选中了某个行为,那么后续创建的行为将不会默认在末尾生成,而是会生成在当前已经选中行为之后。
示例1:未选中状态下,行为会生成在末尾。

示例2:已选中状态下,行为将会生成在当前选中的行为之后。

如图,先选中第二个行为——Say。

如图,此时生成的行为Say位于已经选中的第二个行为Say之后,是第三个行为,而不是最后一个行为。
行为的基本调整操作
拖动改变顺序
可以通过拖动行为来改变执行顺序,拖动按钮是右侧这两道横杠:

行为调整菜单
点击这个三个点的图标即可查看一些针对行为的细化操作。

禁用/启用(Disable / Enable)
被禁用的行为会被无视和跳过,类似于注释一段代码。启用后恢复正常。


剪切(Cut)
剪切一个行为,它会消失,并转移到剪贴板上等待粘贴。

复制(Copy)
复制一个行为,在剪贴板上创建这个行为的副本,等待粘贴。

创建副本(Duplicate)
立即创建一个行为的副本,就生成在这个行为的后方。

在后方粘贴(Paste Below)
将剪贴板中的行为粘贴到这个行为的后方。

删除(Delete)
删除当前行为。

粘贴行为(Paste Action)
将剪贴板中的行为粘贴,它的位置逻辑和创建行为是一致的。
如果当前没有选中任何行为,粘贴在末尾。
如果当前选中了某个行为,粘贴在这个行为的后方。

同步与异步
同步
在Milovana Webtease的一个Page中,多数行为的执行是同步(Synchronous,Sync)的。
也就是说,行为通常是顺序执行的,后面的行为必须等待前面的行为完成,才能开始。
同步执行的行为可以造成阻塞(Blocking)。
生活中的例子
举个简单的例子来说明这个概念:
假设A、B、C三个人依次排队去买面包。
那么只有等到A买完面包,才能轮到B买面包。这就是同步——排队处理,一个一个来。
此时如果A出了点小问题(比如说A的手机因为装了某些奇怪的东西和下载了大量的学习资料,变得非常卡顿,迟迟打不开支付软件),那么B和C就只能等着,不能做其他的事情。B和C的时间被浪费掉了,这就是阻塞——某件事情被卡住,那么什么也不能做,只能干等。
EOS中的例子
在EOS中举个简单的例子来说明这个概念:

在这个页面中,有三个行为依次排列——Say,Say,End。
点击Preview,预览一下这个案例:
首先,弹出一个对话——Hello, world!

此时,用户需要点击响应才能使得游戏继续下去,后续的两个时间在排队等待,这是同步——排队处理,一个一个来。

如果用户因为一些原因(例如把双手放在一些自然而然,理所应当的地方),用户在此时什么也没有做(或者是做不了),后面的行为将不会执行,整个游戏将会卡在这个地方,这就是阻塞——某件事情被卡住,什么也不能做,只能干等。
异步
在Milovana Webtease的一个Page中,也有一些行为是异步(Asynchronous,Async)的。
也就是说,一些行为不会按照顺序执行,它们不会阻塞主线程。
异步行为通过非阻塞(Non-Blocking)机制执行。
生活中的例子
举个简单的例子来说明这个概念:
假如你今天要做一顿晚饭——青椒炒肉片+米饭。
当你淘米,把饭煮下去之后,你并不用等待饭煮完才能去做另一件事情。当饭煮下去之后,你可以去做一件别的事情,例如洗菜、切菜、炒菜。当饭煮好后,电饭煲会提示。此时你就知道,饭煮好了,可以去盛饭。
这是异步——开始做一件事之后,可以不用等待,立即去做别的事情,完成后再回来处理。
而煮饭这件事本身是非阻塞的,时间没有被浪费——你不用站在电饭煲旁边干等着,可以去做点别的,反正它会自己完成。
EOS中的例子
在EOS中举个简单的例子来说明这个概念:

在这个页面中,有三个行为依次排列——Audio, Say, End。
点击Preview,预览一下这个案例:

在声音开始播放的同时,第一句对话就已经出现了。这就是异步——开始做一件事之后,可以不用等待,立即去做别的事情,完成后再回来处理。这里的这个Audio——开始播放声音,就类似于前面我们提到的把饭煮下去。
而播放声音(Audio)这件事情本身是非阻塞的——Say不需要等待Audio真的把声音播放完,可以直接弹出,反正Audio自己会播放完的。这里的Say——显示字幕,就类似于前面提到的,在煮饭的同时去洗菜,切菜,炒菜。
基本行为类型
掌握一些基本类型的行为,就已经足够在EOS中做出多样的互动文游。
Image
Image是一个非常基本的行为——显示一张图片。
如果目前已经有显示的图片,那么就替换显示的图片。
它是一个加载媒体的行为,是非阻塞的。

Select Image(选择图片)
建议先把要用的图片素材全部导入到Galleries。这个操作在上一篇已经讲过,不再重复。
点击Select Image按钮。


点击From Galleries,点击Choose Image。(Use Random Image是从这个Gallery中随机选择一张)


点选你想要选择的图片即可。
当然如果你想要用From Files也是可以的,直接手动上传即可,这里不多赘述。
Recent会展示你最近使用过的Galleries,可以提高工作效率。
Say
Say是一个非常基本的行为——显示一段文字。
它是阻塞的。

输入文字(Input)
在右侧的编辑栏,可以看见一个输入框。可以在这里直接输入文字。

注意:PC 端Web UI这个输入框不能直接输入中文,否则会被立即清除。
目前有两种办法来处理这个问题。
一种是直接使用移动端的Web UI,可以绕过这个限制。(但是用着有点累)
一种是从其他地方(例如文本文档,Word文档的台本中)复制粘贴中文文本。
(感谢鹭鸣有音提供的解决方案)
文本样式(Style)
在右侧编辑栏上方有一组文本样式按钮,可以用来调节选中文本的样式。

它的逻辑与Microsoft Word基本类似,这里操作方法不再详细赘述。它既可以作为一个Toggle的输入样式开关,又可以对选中区域的文字应用相对应的样式。
从左到右依次是:
B - 粗体(Bold)
I - 斜体(Italic)
U - 下划线(Underline)
A - 字体颜色
<> - Eval()函数文本,这个稍微有点复杂了,涉及JavaScript,到时候放到进阶篇来讲。
文本对齐(Alignment)
文本对齐的方向,很好理解。

Left - 左对齐
Center - 居中
Right - 右对齐
时间机制(Timing)
文本的时间机制是最重要的设定之一,它直接关系着一个文游的人机交互和用户游玩体验。

讲讲这些选项。
Auto / Pause


这是最常见的选项。这两个放在一起是因为,Auto通常就表现为Pause。
Auto / Pause的机制很简单——阻塞,等待用户交互后继续(通常是单击或空格)。
没错,就类似于平时玩的Galgame。
大多数情况下可以放着不管,默认的设置是Auto,Auto在绝大多数情况下就表现为Pause。这意味着用户点一下,它前进一步。
然而,当用户可能处于某种腾不出手的状况时,这个选项会显得稍微有点不讨好——这会迫使用户停下他们手头正在做的事情(比如说用户正在做某些理所应当的,自然而然的事情)来进行交互,这会让用户感到沮丧和挫败。所以最好是注意一下。
Instant

瞬间跳过交互,直接呈现一段文字。你可以理解为即时的自动点击,Galgame的快速跳过,或者是非阻塞。
这个其实不太常用。
Auto-Play

根据内容信息的长短,大致估计一个阅读时间,然后自动跳过。
Allow user to skip the wait这个开关决定用户能否跳过这段文字的等待时间。(开发者在Debug模式下是一定可以跳过的)
这个估计的时间其实不太准确的……用这个功能真的还不如用Custom来得灵活。
Custom
最常用的选项之一。

作者可以自定义这段文字信息的持续展示时间,接受的时间单位包括:
s(second,秒)
m(minute,分)
h(hour,时)
Allow user to skip the wait这个开关决定用户能否跳过这段文字的等待时间。(开发者在Debug模式下是一定可以跳过的)
这里就再次提到之前Auto / Pause出现问题的那种情况。当用户腾不出手的时候,给出一个自动播放的对话字幕是很有帮助的。
举个例子,像是这种对话字幕:
给我继续无脑自慰❤
不可以擅自停下❤
有些用户习惯于用双手,那么如果此时使用Auto / Pause,会给他们带来一个比较糟糕的游戏体验——他们的动作会被反复出现的强制交互打断(这是否也算是一种物理寸止?)。
一种更好的方式就是切换到Custom模式,打开Allow user to skip the wait,同时设定一个自动跳过的时间。这样,如果用户腾得出手,他们可以掌控游戏节奏,就算用户真的腾不出手,文游也还会继续下去。
当然也可以不打开Allow user to skip the wait,这样用户就必须等待这段时间,之后文游才会继续。
Goto
Goto其实很简单,行为触发后,将会直接跳转到对应的页面(Page)。


固定跳转
在右侧的Page输入框,可以输入确定的一个页面名,来确保跳转到某一个页面。

目标页面存在
如果页面名没有问题,目标页面确实存在的话,会提示”This page exists.”,代表可以成功跳转。

此时点击右侧的Go to edit page可以直接进入目标页面的编辑界面。

目标页面不存在
如果页面名有问题,目标页面不存在,会提示”This page does not exist.”,代表目前无法成功跳转。

此时点击右侧的Create it可以立即创建一个对应名称的页面。
随机跳转
在右侧的Page输入框,还可以输入一个不确定的页面名,实现随机跳转的效果。

在这个用法中,*
代表了任意字符。因此在页面命名时可以稍微注意一下,例如:
EndA
,EndB
,EndC
。那么此时,Goto的页面名输入End*
,即可跳转到三个End
页面中的随机一个。
Timer
Timer(计时器)是EOS的核心机制之一。
持续时间(Duration)
持续时间(Duration)很好理解——倒计时持续多久。

固定持续时间(Specific)
在Specific模式下,倒计时的时长是固定的。
只要输入你想要的时长就可以了。

范围持续时间(Range)
在Range模式下,倒计时的时长是随机的。
时长是Min(最小值)和Max(最大值)之间的一个随机值。

Eval(JavaScript)
在之后和JavaScript一起讲。

视觉样式(Visual Style)
视觉样式(Visual Style)决定了计时器在Webtease中应该如何显示。

Normal
Normal意味着一切正常——用户可以看到计时器本身,也可以看到剩余时间。

它的效果是这样的:

Secret
Secret意味着隐藏时间——用户可以看到计时器本身,但无法看到剩余时间。

它的效果是这样的:

Hidden
Hidden意味着完全隐藏计时器——用户既不能看到计时器本身,也无法看到剩余时间。

同步/异步模式(Behavior)
计时器可以调整同步/异步模式,这是计时器最重要的机制之一,它决定了一个计时器如何发挥作用,是否阻塞主线程。

同步模式(Sync)
同步模式的计时器是阻塞的。

也就是说,一个同步模式的计时器必须走完,才能进行进入后面的游戏。后面的行为必须等待这个计时器的结束。
打个比方,同步模式的计时器就好比那个正在面包店门口掏钱的人。在他完成交易之前,后面排队的人都得干等着。
做个简单的示例来解释一下同步模式的计时器:

在这个例子中,Tease先显示”Hello, world!”,用户需要先完成第一个Say的交互,进入5秒的倒计时。倒计时完成后,才显示”Sync Timer!”。
一切都是顺序执行的。
异步模式(Async)
异步模式的计时器是非阻塞的,且可以添加计时结束后的行为。

也就是说,一个异步模式的计时器并不需要走完,可以直接进入后面的游戏。等这个计时器结束后,它可以主动触发一些行为。
打个比方,异步模式的计时器就好比一个外卖小哥。你点了外卖,可以去做其他事情。过了一段时间,外卖送上门了,外卖小哥按下你的门铃,你知道是外卖到了,于是去取外卖。
做个简单的例子来解释一下异步模式的计时器:

在这个例子中,Tease先显示一张幻樱的图片,然后进入一个计时器。
这个计时器有整整10秒,它添加了一个行为——更换一张幻樱的图片。
在计时器启动后,用户仍然可以正常地与两个Say行为交互,不会受到任何的影响。

在10秒后,计时器结束,计时器触发预设的事件——图片被更换。

Choice
Choice(选择)可以创建互动和分支。


添加选项(Add Option)
Choice默认只有一个选项(我莫得选择)。
这个其实可以用于制作一些类似于……
跪下了❤
我没有作弊!
这种交互。
可以点击Add Option按钮添加选项。


单击上方的Option即可进入对应选项的编辑页面。

选项标签(Label)
Label其实就是该选项的文字内容。直接输入即可,自动保存的。

行为类型(Type of Behavior)
行为类型决定了用户点击选项后会发生什么。

前往页面(Go to page)
默认的行为类型,用户点击选项后,文游跳转前往指定的页面。页面的跳转方式同之前的Goto,不再赘述。

自定义行为(Custom Actions)
自定义按钮的行为类型,用户点击选项后,执行这些行为。机制等同于Create Action,套娃,不再赘述。

按钮颜色(Button Color)
调整选项按钮的颜色,点击Reset即可重置默认。

可见性(Visible)
通过JavaScript调整按钮的可见性,默认是一直可见。
别问怎么调,我也不会(

End
End(结束)是最简单直白的行为——只要触发这个行为,Webtease就结束了。

End行为没有任何可以设置的东西。
Eval
Eval动态执行字符串形式的JavaScript代码。


这里不详细讲解,总之,在基础部分,通常只需要掌握这个:
1 | Sound.get("").stop() |
这段语句配合Audio的Identifier使用,可以停止一段音频的播放。
Notification: Create
(注意:这是一个可选功能,如果要使用,记得先在左侧菜单栏的选项中打开Notification)

Notification: Create(创建通知)将会创建一个跨越Page层级的通知,一直悬浮于Webtease的界面上,直到触发事件或者被Notification: Remove(移除通知)删除。

通知标题(Title)
这是可选的,输入通知的标题。例如这样:

按钮(Button)
在通知中增加一个按钮,用户点击后会触发按钮的事件,例如这样:

这是它的实际效果:

用户点击败北❤
按钮后,就会跳转到lose这个Page。
计时器(Timer)
在通知中增加一个计时器。

这个计时器基本上和之前讲的大差不差,但是要注意一下,通知(Notification)中的计时器默认的设定是:
异步(Async)+ 可见(Visible)
这个可见的形式稍有不同——它不会详细显示剩余时间,而是一个进度条的形式,就像这样:

标识符(Identifier)
通知的标识符(Identifier),主要是用于给Notification: Remove定位通知用的。
Notification: Remove
(注意:这是一个可选功能,如果要使用,记得先在左侧菜单栏的选项中打开Notification)

Notification: remove(移除通知)只需要设定一个对应的标识符(Identifier),就可以立即移除一个通知(Notification)。

例如,在之前我创建了一个通知,它的标识符是edge1
:

现在只要填写对应的标识符,就可以利用Notification: Remove移除对应的这个通知:

Audio: Play
(注意:这是一个可选功能,如果要使用,记得先在左侧菜单栏的选项中打开Audio)
Audio: Play(播放音频)
这个行为是非阻塞的,它会播放一段指定的音频。


定位器(Locator)
定位器是用来选择音频文件的,在使用播放音频功能前,建议先在Files当中上传你需要使用的音频文件——上一篇已经讲过,不再赘述。

点击Select Audio File即可选择音频文件:

选择已经上传的音频文件,单击即可选中。Use Random Sound是随机。

选项(Options)
选项主要调整音频文件的一些基本播放参数。

音量(Volume)
拖动进度条可以调整音频文件的播放音量。

循环(Loops)
拖动进度条可以调整音频文件的循环播放次数,可以在1-31次之间调整,拉到最右侧是无限(Infinite)。

交互性(Interactivity)
主要是调整播放音频的交互行为模式,以及用于控制播放用的一些东西。

跨页连续播放(Continue across pages)
开启后,Audio: Play行为的生命周期从单个Page提升到整个Tease。
也就是说,原本跳出当前Page之后,Audio会自动停止。开启后则不会。

标识符(Identifier)
类似于Notification,Audio的标识符(Identifier)是用来定位音频和控制播放的(暂停,停止,调整音量等等)。

这里,简单举个例子讲一下这个用法:
首先设置目前Audio的标识符:

然后新建一个Timer,Timer之后是一个Eval,里面写一段JavaScript代码来停止这个音频的播放:

经过测试,可以发现计时器走完后音频停止了。
结语
本文主要介绍了Milovana EOS Editor当中的一些基本行为。
下一篇是一个实战演练——制作一个简单的R-18文游。