ArcadeZero4.0的Scenecontrol指南
原文地址:https://github.com/Tempestissiman/ArcadeScenecontrol
API文档地址:https://github.com/Tempestissiman/ArcadeScenecontrol/wiki
ArcadeZero下载地址:https://drive.google.com/drive/folders/1ziY89wDWrwQJxbD-YGCSIwMwdE_WzrRE
开发者油管地址:https://www.youtube.com/channel/UCHnTjGOLbwufuYBnFnW8oYQ/featured
已经取得原作者授权。

引言
本文档是指导新手或已经有一定经验的人开始使用全新的Scenecontrol API。
注意,该文档没有涵盖全部的API中可用的功能,但会为您提供足够的知识以自由探索其余功能。全部的API使用请查看[参考文档](https://github.com/Tempestissiman/ArcadeScenecontrol/wiki)。
一般而言,上手Scenecontrol的脚本编写需要一定的Lua语言基础,本文档假定您已经对该语言有一定的了解。如果您是新手或者想要复习一下Lua语言,请参考[官方教程](https://www.lua.org/pil/contents.html)。
贡献: 您可以帮忙将该文档翻译为其他语言!
新特性
旧版API存在的一些问题:
慢:旧版API严重依赖用户编写的Lua代码,即使是在运行态。这不仅很慢,也有难以移植到其他平台的风险。
不灵活:寄存器系统过于复杂,造成了一些简单任务实现起来过于繁琐,如在多场景下控制同一个对象。(这与它的目的恰恰相反!)
整体丑陋并且不直观。
考虑到以上这些问题,以下是新版API的改进:
完全确定性:新版API依赖于*Channels*这一概念(后续会详细介绍)。只需要执行一次Lua代码,即可导出到其他不需要Lua的格式。
透明:您可以完全控制新系统,从而可以更快地编写脚本。
协作:使用别的脚本也更加容易!
除此之外,本次更新还带来了一些新的功能:
场景控制Scenecontrol编辑窗口:您可以直接在ArcadeZero中编辑您的事件!
拓展功能:如后期效果处理。
内置的场景控制Scenecontrol命令。
如果这些让您感到兴奋,那让我们不再浪费时间,直接开始吧!
对熟悉旧系统用户的警告
新系统完全支持旧脚本的迁移,但请注意坐标和旋转等内容可能需要修改。新系统对此进行了一些变更,以使得它们编辑起来更加常规和便捷。

开始
您需要使用到的工具:
- ArcadeZero v4或更高的版本,或者ArcadeZero v4的任何分支。
- 一个有效的IDE或文本编辑器,推荐使用VSCode。
- 您可以为VSCode安装[“Vscode Arcaea文件格式支持”拓展](https://github.com/yojohanshinwataikei/vscode-arcaea-file-format),以及Lua拓展。
1. 内置命令
我们从最基本的内置命令开始,首先熟悉下Scenecontrol编辑窗口。
aff语法
您可能已经习惯于在.aff文件中手动编写场景控制命令。如果您没有手动编辑过,这里给出一个基础的场景控制语法示例:
- `timing` 通常指特效发生的时间。
- `scenecontrolType` 定义了场景控制的类型。
- `argument0, argument1,...` 定义了场景控制的参数,规定了场景控制命令的操作方式。
我们也可以在timingroup时间序列组中使用场景控制命令,但是可能会有不同的效果,这取决于场景命令的类型。
使用内置命令
ArcadeZero目前支持以下类型:
- `trackdisplay` 改变主轨道的透明度。
- `hidegroup` 改变同一个时序组内所有音符的透明度。
- `enwidenlane` 在主轨道两侧增加两个额外轨道(用于Pentiment, Arcana Eden和Testify)
- `enwidencamera` 移动摄像机来支持*enwidenlane*.
`timing` 和 `duration`都使用毫秒为基本单位。 `changeToAlpha` 接收一个从0-255的值表示透明度的大小。`changeToState`接收0或1,表示是否启用效果,1表示启用,0表示禁用。
> 从技术上讲,这与官方游戏的规范有所不同,但是为了保持系统设计上的一致性,我选择了与官方不完全重合。
如果您不习惯使用这些场景控制命令,那么我强烈建议您自己练习使用一段时间。场景控制是一个很不错的技能!
编辑窗口
现在让我们熟悉一下场景控制的新编辑敞口。打开一个谱面,然后单击右侧带有星型的按钮,您将看到一个如下所示的小窗口:

在最上方,您将会看到:
- 场景控制类型下拉菜单:您可以在这里选择要使用的场景类型。
- 切换字段按钮:这回在每个参数的单独字段和组合字段之间切换。您可以尝试按几次,然后看看会发生什么。
- 刷新按钮:这会刷新列表并重新加载任何包含的脚本。
在中间部分,是场景控制的事件列表。现在它可能是空的,因为您的谱面中并不包含与当前选定控件相关的事件。让我们通过按任意行上的"+"按钮来添加一个,您会看到出现一个新事件:

现在,您可以根据自己的喜好更改参数!
另外请注意,该窗口仅仅显示当前活动时序组内包含的场景控制事件。在编写过程中,这是很容易被遗忘的。在实际编写中,经常会出现为什么列表内没有控制事件,但是控制效果仍然处于活跃状态的问题。
这就是最基本的使用了!我们已经大致了解了场景控制的工作原理,我们将在下一节开始深入了解脚本的部分。
2. 脚本编写
2.1. 谱面文件结构
相信您之前一致在使用官方提供的内置场景控制类型,但是Arcaea自制谱面的一大乐趣是能够自定义场景控制类型,让我们现在开始吧!
首先进入到您的谱面工程文件夹(包含`Arcade`文件夹的那个)。创建一个名为`Scenecontrol`(区分大小写)的文件夹,并在其中创建一个名为`init.lua`的文件。
`Scenecontrol/init.lua`是ArcadeZero加载谱面时默认运行的脚本。`Scenecontrol`文件夹是您放置所有资源文件(如图像等)供脚本读取的位置。
> 对于高级用户:您还可以使用`require`将您的lua代码拆分为多个脚本文件。文件名并不是那么重要!(除了默认的`init.lua`)
让我们打开新创建的lua文件开始编码吧!但是,我们首先先来介绍一个基本概念:场景控制命令中的*Controllers* 和 *Channels*。(下一部分将学习如何自定义不同的场景控制类型!)
2.2. Controllers & Channels - 等级1
> 等级1的控制器和通道能够使您获取场景中已经存在的任何东西,并根据自己的喜好对其进行操作!
不同类型的控制器是您自定义的lua代码与场景中对象交互的工具。举例来说,您将通过轨道控制器控制轨道,与精灵控制器控制2D图像等。这里的交互只意味着修改它的属性,如位置、旋转、缩放和颜色等。
到目前位置,它和旧系统是完全相同的!但是,这次并不是像旧系统那样直接修改,而是用一种更间接的方式。
我们已经了解了控制器,那么通道是什么呢?所有的通道都将用于回答以下问题:
“这首歌现在是X毫秒,它的值是多少?”
控制器的每个属性都将被附加一个通道,该通道决定了对应属性在某个时刻如何改变。
您可能还是不太明白这意味着什么。让我们从一个实际的lua代码具体示例来说明这一点:
我们首先定义一个控制器,在这里我们选择内部控制器。我们选择`Scene`来抓取一个,这将是您抓取和创建绝大部分控制器的地方:
将控制器的位置赋值为其他:
单击场景控件编辑窗口上的`Refresh`刷新按钮,这会重新加载您的脚本,更新后的脚本会告知Arcade将轨道移动到(1, 2, 3)的新位置。您的轨道目前会像这样:

以下是对刚刚发生事情的详细解释:
1. `Channel.constant(value)`定义了一个通道,它总是返回您传入的数值,而不管时间。(译者注:常量通道,数值不会随着时间发生改变。)
2. 您将其中的三个通道分配给控制器的三个属性。
3. 控制器根据您设置的数值更新它的值,这里为常数值1,2和3。
您的脚本现在并没有做什么特别有趣的事,但是这对您理解整个系统非常重要。如果您了解使用通道以后,您就会了解新系统下的场景控制系统,所以请慢慢来!
如果您觉得您掌握了它,那么我们继续学习使用时变通道来增加一些趣味性。(`Channel.constant()`类似的常量通道是行不通的。)在此之前,同样介绍一个新概念:关键帧通道。
关键帧通道由一系列的关键帧定义。如果您之前曾经有过动画制作的经验,那么您会对这个概念很熟悉。如果您没有制作过动画,那么让我们看一个例子:

这个通道有3个关键帧:
- 第一个关键帧在时间0,且值为0。
- 第二个关键帧在时间1000,且值为1。
- 第三个关键帧在时间2000,且值为0。
然后,该通道将三个关键帧点连接起来,来为所有的连续时刻计算出所有的输出值。
实际上,使用关键帧通道非常简单。我们使用lua代码对上述描述的通道进行编码:
>这只是一种写法,也是最清楚地说明正在发生事件的一种方式,您可以这样写来缩短这一命令:
> 您可以复用通道!同一个通道可以被分配给多个属性,更改一个通道可以同时更改全部的属性值。我认为这是一个很好的做法,因为这意味着您只需要设置一次关键帧,就可以更改多个属性。
刷新一下,然后看看效果。轨道现在应该从0ms到1000ms线性移动到一个位置,然后从1000ms到2000ms再返回原来的位置。
诶,您说您不希望它线性移动?那么我们来试试缓动!只需要传递一个额外的字符串:
>警告:这里的'so'实际上类似于Arcaea中的si虹弧(起初快,最后慢)。这是和官方不一致的地方。
您可以在创建通道时更改默认的缓动类型,这和上方的代码片段是等价的:
以下是支持的所有缓动类型列表,每种缓动类型都可以用多种方式编写:
(详情参见原文档)
| 类型 | 别名 | 曲线 |
| - | - | - |
| linear | l | <img src="https://i.imgur.com/RW6npU1.png" width=120em> |
| inconstant | inconst, cnsti | <img src="https://i.imgur.com/OtPvAGb.png" width=120em> |
| outconstant | outconst, cnsto | <img src="https://i.imgur.com/UYKymRq.png" width=120em> |
| inoutconstant | inoutconst, cnstb | <img src="https://i.imgur.com/D916zO3.png" width=120em> |
| insine | si | <img src="https://i.imgur.com/6FWfL7v.png" width=120em> |
| outsine | so | <img src="https://i.imgur.com/VDB347V.png" width=120em> |
| inoutsine | b | <img src="https://i.imgur.com/uprRIh9.png" width=120em> |
| inquadratic | inquad, 2i | <img src="https://i.imgur.com/qafhmH3.png" width=120em> |
| outquadratic | outquad, 2o | <img src="https://i.imgur.com/lPvQZiq.png" width=120em> |
| inoutquadratic | inoutquad, 2b | <img src="https://i.imgur.com/KrMCfGe.png" width=120em> |
| incubic | incube, 3i | <img src="https://i.imgur.com/pBtlUPe.png" width=120em> |
| outcubic | outcube, 3o | <img src="https://i.imgur.com/pBtlUPe.png" width=120em> |
| inoutcubic | inoutcube, 3b | <img src="https://i.imgur.com/2vsaAfH.png" width=120em> |
| inquartic | inquart, 4i | <img src="https://i.imgur.com/1gkdwj3.png" width=120em> |
| outquartic | outquart, 4o | <img src="https://i.imgur.com/1gkdwj3.png" width=120em> |
| inoutquartic | inoutquart, 4b | <img src="https://i.imgur.com/DpLpSKE.png" width=120em> |
| inquintic | inquint, 5i | <img src="https://i.imgur.com/lO6CS1R.png" width=120em> |
| outquintic | outquint, 5o | <img src="https://i.imgur.com/cjNq1sq.png" width=120em> |
| inoutquintic | inoutquint, 5b | <img src="https://i.imgur.com/0ElZdrQ.png" width=120em> |
| inexponential | inexpo, exi | <img src="https://i.imgur.com/CxM7iHY.png" width=120em> |
| outexponential | outexpo, exo | <img src="https://i.imgur.com/zSi35WU.png" width=120em> |
| inoutexponential | inoutexpo, exb | <img src="https://i.imgur.com/bYFbAOK.png" width=120em> |
| incircle | incirc, ci | <img src="https://i.imgur.com/XlLATi4.png" width=120em> |
| outcircle | outcirc, co | <img src="https://i.imgur.com/V7jXmBe.png" width=120em> |
| inoutcircle | inoutcirc, cb | <img src="https://i.imgur.com/Nq5JkvD.png" width=120em> |
| inback | bki | <img src="https://i.imgur.com/vL2NZps.png" width=120em> |
| outback | bko | <img src="https://i.imgur.com/YKBd78p.png" width=120em> |
| inoutback | bkb | <img src="https://i.imgur.com/JnSeIih.png" width=120em> |
| inelastic | eli | <img src="https://i.imgur.com/BsUF01a.png" width=120em> |
| outelastic | elo | <img src="https://i.imgur.com/IhlWlew.png" width=120em> |
| inoutelastic | elb | <img src="https://i.imgur.com/oULApFZ.png" width=120em> |
| inbounce | bni | <img src="https://i.imgur.com/fCHgebv.png" width=120em> |
| outbounce | bno | <img src="https://i.imgur.com/58pzeQD.png" width=120em> |
| inoutbounce | bnb | <img src="https://i.imgur.com/dhdjvX0.png" width=120em> |
> 您还可以打开通道外推的开关。外推意味着延续您添加的关键帧之外的曲线,我不知道为什么有人会使用它,但是它是可用的:
那么现在您已经掌握了控制器和通道的基础知识,现在可以将任何属性更改为您喜欢的任何行为!但是新API系统提供了更多有用的功能,可以为您简化流程,我们在等级2部分回到这一内容。
现在让我们真正开始做一开始我们想做的事情:自定义一个场景控制类型。
2.3 添加一个场景控制类型
您可以完全忽略整个场景控制类型的概念,只需要在`init.lua`中输入所有内容。但是为了复用,我建议您定义它们的类型。
我们来看看要实现这样一个目标需要做什么:
- 指定场景控制类型的名称(可以随意指定!)
- 指定场景控制的参数,我们使用几个参数,它们的参数名是什么
- 指定场景控制的行为
其中以上的三分之二都在单个函数内,其余部分是我们在上一章做的东西,现在我们开始吧!
我们将创建一个简单的场景控制类型,它将读取3个数字,并将轨道从它的当前位置移动到这3个指定的新位置。
发生了什么呢?
1. 我们首先定义了关键帧通道来添加我们的关键帧。
2. 我们定义了一个名为"mytypename"的场景控制类型,它有三个参数。这意味着我们的aff命令将如下所示:`scenecontrol(timing, mytypename, arg1, arg2, arg3`。
3. 我们通过一个函数定义了我们的Scenecontrol的行为。一般而言,这一函数针对谱面中的每个场景控制事件运行。我们的函数会读取这些指令,并采取相应的行动,为我们的通道添加关键帧。
>如果您熟悉Arcade宏,那么这种模式应该很熟悉!唯一的区别是我们接受了一个参数来读取场景控制命令中的数据。
另外, `channel.valueAt(timing)`是读取关键帧数值的很好用的API。
这个例子也很好的说明了场景控制脚本的基本结构:
- 设置控制器
- 分配通道
- 定义场景控制的类型
现在我们成功定义了我们的类型,打开Arcade的Scenecontrol窗口,您现在会看到"mytypename"出现在类型选择的下拉框中,并且可以向其中添加对应的事件。
顺便说一句,您可能会在场景控制编辑窗口的第一行上方看到小的文本,它们表示每列中参数的名称,是的,您可以更改它!
`{"xpos", "ypos", "zpos"}` 创建一个字符串列表作为aff命令的传入参数名。刷新脚本并再次检查窗口!
>零参数的事件也是有效的,命令`scenecontrol(timing,mytypename);`对应于下面这段代码:
到目前位置,您应该能够复制产生大量效果了。让我们更进一步,了解`Scene`的来龙去脉!
2.4. Scene
`Scene`场景对象提供两个函数:获取内部控制器和创建新控制器。我们已经在前面尝试过了内部控制器,但是也只有一种,篇幅限制问题这里不对每种控制器进行详细介绍,请参阅文档以获取更多信息。
想要了解您可以做什么,这里是所有的内部控制器列表。
| 访问路径 | 类 | 描述 |
| - | - | - |
| Scene.gameplayCamera | CameraController | 主摄像机 |
| Scene.combo | TextController | 连击文本 |
| Scene.score | TextController | 得分文本 |
| Scene.jacket | ImageController | Jacket(译者注:曲绘图片) |
| Scene.title | TextController | 曲名文本 |
| Scene.composer | TextController | 作曲家文本 |
| Scene.difficultyText | TextController | 难度文本 |
| Scene.difficultyBackground | ImageController | 难度文本背景图片 |
| Scene.hud | CanvasController | 包含暂停按钮的画布 |
| Scene.pauseButton | ImageController | 暂停按钮 |
| Scene.infoPanel | ImageController | 信息面板背景图 |
| Scene.background | ImageController | 背景图 |
| Scene.videoBackground | SpriteController | 视频背景图 |
| Scene.track | TrackController | 主轨道 |
| Scene.track.divideLine01 | SpriteController | 轨道划分线0-1 |
| Scene.track.divideLine12 | SpriteController | 轨道划分线1-2 |
| Scene.track.divideLine23 | SpriteController | 轨道划分线2-3 |
| Scene.track.divideLine34 | SpriteController | 轨道划分线3-4|
| Scene.track.divideLine45 | SpriteController | 轨道划分线4-5 |
| Scene.track.divideLines | Table (of SpriteController) | 轨道划分线组 |
| Scene.track.criticalLine0 | SpriteController | 0轨道判定线 |
| Scene.track.criticalLine1 | SpriteController | 1轨道判定线 |
| Scene.track.criticalLine2 | SpriteController | 2轨道判定线 |
| Scene.track.criticalLine3 | SpriteController | 3轨道判定线 |
| Scene.track.criticalLine4 | SpriteController | 4轨道判定线 |
| Scene.track.criticalLine5 | SpriteController | 5轨道判定线 |
| Scene.track.criticalLines | Table (of SpriteController) | 轨道判定线组 |
| Scene.track.extraL | SpriteController | 左侧额外轨道(轨道0) |
| Scene.track.extraR | SpriteController | 右侧额外轨道(轨道5) |
| Scene.track.edgeExtraL | SpriteController | 左侧轨道的额外边 |
| Scene.track.edgeExtraR | SpriteController | 右侧轨道的额外边 |
| Scene.singleLineL | SpriteController | 左侧Memory Archive线 |
| Scene.singleLineR | SpriteController | 右侧Memory Archive线 |
| Scene.skyInputLine | SpriteController | 天空线 |
| Scene.skyInputLabel | SpriteController | 天空线标签 |
| Scene.darken | SpriteController | 变暗控制器(由内部trackdisplay控制)|
> 您可能想知道ImageController和SpriteController有什么区别,简而言之:
> - SpriteController可以设置自己的排序图层和排列顺序,但是ImageController不行。
> - 您可以使用枢纽和锚点更轻松地控制ImageController的位置。ImageController的图层和排列顺序只能通过将它们设置为另一个画布来更改。
然而,我们不再使用内部控制器,而是尝试在这里创建我们自己的控制器,并以这种方式探索不同类型的控制器。同样,您需要`Scene`来实现这一点。
| 方法 | 返回类型 | 描述 |
| - | - | - |
| Scene.createSprite(string imgPath, string material = "default", bool newMaterialInstance = false) | SpriteController | 从路径创建一个Sprite对象,具有指定的材质 |
| Scene.createCanvas(bool worldSpace = false) | CanvasController | 创建一个画布,以世界坐标或屏幕坐标展示 |
| Scene.createImage(string imgPath, string material = "default", bool newMaterialInstance = false) | ImageController | 从路径创建一个Image对象,具有指定的材质 |
| Scene.createText(string font = "default", number fontSize = 40, number lineSpacing = 1, string alignment = "middlecenter", string material = "default") | ImageController | 从路径创建一个Text对象,具有指定的材质 |
| Scene.createMesh(string objPath, string texturePath) | MeshController | 从.obj文件和纹理图像中创建3D对象 |
| Scene.getNoteGroup(number group) | NoteGroupController | 获取一个时间组的控制|
当您看到参数中有`=`时,如 `material = "default"`,意味着在这一位置参数有一个默认值,您不必指定它。
您可以使用`material`参数来更改混合模式,以下是混合方式列表:
- default
- add
- colorburn
- darken
- difference
- exclusion
- fastadd
- fastdarken
- fastlighten
- fastmultiply
- fastscreen
- hardlight
- lighten
- linearburn
- lineardodge
- linearlight
- multiply
- overlay
- screen
- softlight
- subtract
- vividlight
`newMaterialInstance`在大多数情况下应该都保留为`false`。如果您需要更改控制器的纹理偏移或者纹理纹理缩放,请将其设置为`true`。
考虑到这一点,让我们尝试在场景中添加自定义的Sprite精灵。默认情况下,精灵放置在“背景”层中,该层位于轨道所属的下方,所以我们需要改变它才能看到别的东西。精灵的图层可以随时间改变,所以我们需要再次使用通道,但是这次我们需要使用的是`StringChannel`。
(译者注:layer图层和order顺序是Unity中两个不同的概念,可以简单理解为layer是图层组,而order是一个图层组中的先后顺序,数值越大优先级越高)
然后我们会在场景中看到:

可以随意改变"test.img"来对应您输入的图像文件名。
让我们用图像来尝试一下,这次我们尝试更改一下它的材质,将上述代码更改为:
注意这次您需要指定图像的高度和宽度,这是Sprite精灵和图像之间的区别。另外需要调整混合模式来使得样式尽量美观,在这里我不得不将其改为了conflict侧,您可以自行调整其他的混合模式。

最后让我们也创建一些文本:
如您所见,字符串通道也可以设置关键帧,在这里,我们让它在0ms时显示"Hello",然后在1000ms时将自身更改为"World"。
>您还可以在字符串通道上使用缓动!尝试将其改为`addKey(0, "Hello", "so")`,然后看看会发生什么。
这是我们的结果:

这涵盖了创建控制器时需要记住的最重要的事情,如果您想要查看每种类型控制器的可用属性,请参阅API文档。
2.5. Controllers & Channels - 等级2
> 等级2将教您如何在使用通道时节省大量的时间,您将了解不同类型的通道,以及如何将通道组合在一起以获得各种效果。
让我们首先考虑如何处理一个非常简单的效果:无限期地以正弦波模式来回移动对象。我们的通道将如下所示:
(img)
手动添加每个关键帧以实现此效果是完全可行的。实际上,要了解它需要做多少工作,让我们来看一下执行此操作的代码。
该通道将以正弦波形式在-1和1之间持续震荡,它虽然有效,但是我们可以做的更好。
实际上,API提供了除关键帧通道以外的*other*类型来帮助您,以上代码可以缩短为:
您还可以使用一系列其他的通道,这是列表:
| 方法 | 类型 | 描述 |
| - | - | - |
| Channel.keyframe() | KeyChannel | 关键帧通道 |
| Channel.constant(value) | ValueChannel | 常数值通道 |
| Channel.random(min, max, seed = 0) | ValueChannel | 随机值通道 |
| Channel.noise(frequency, min, max, offset = 0, octave = 1) | ValueChannel | 柏林噪声通道 |
| Channel.sine(period, min, max, offset = 0) | ValueChannel | 正弦通道 |
| Channel.saw(string easing, period, min, max, offset = 0) | ValueChannel | 锯齿波通道 |
| Channel.fft(freqBandMin, freqBandMax, min, max, smoothness = 0.1, scalar = 1) | ValueChannel | 指定频率范围内当前播放音频的平均响度 |
| Channel.max(channelA, channelB) | ValueChannel | 返回两个通道的最大值 |
| Channel.min(channelA, channelB) | ValueChannel | 返回两个通道的最小值 |
| Channel.clamp(valueChannel, minChannel, maxChannel) | ValueChannel | 将valueChannel的值夹在两个通道之间|
> FFT代表快速傅里叶变换,如果您感兴趣的话。
> 顺便说一下,默认情况下,FFT通道使用256个频段,但您可以使用`Channel.setGlobalFFTResolution(resolution)`进行更改,分辨率必须是2次幂的整数。
一行代码已经够好了,是吗?是的,它确实如此。不过您还可以用不同的通道组合来实现无限可能的效果:
通过将`vibrate` 振动通道和`dampen`阻尼通道相乘,我们限制了通道随时间振动的程度。当`dampen`返回1时振动最大,当`dampen`返回0时不再振动,由于`dampen`的形状是锯齿波,我们得到了有趣的脉动效果。
当然,您也可以通过这种方式将关键帧和其他通道组合,这也为一种非常有效的技术开辟了可能性,该技术实际上在内部用于实现内置的场景控制类型,让我们来看一下其中的`enwidenlane`。
这一场景控制有如下功能:
- 将两个额外轨道的不透明度从0改为255
- 将两个额外轨道的不透明度从0改为255
- 将默认的两条轨道边界从255改为0
- 将两条额外的轨道边界(轨道0和5)从0改为255
- 将轨道0-1和轨道4-5的分界线从0更改为255
- 将两个额外轨道的位置从-100改为0
您可以手动为所有内容设置关键帧,这非常烦人并且需要大量的写作,相反,内部发生的事情是这样的:
如果您仍然不清楚这段代码在做什么,这里有一个简单的解释:
- 第二个参数"toggle"是一个0或1的值,我们将该值直接写入`enwidenLaneFactor`的通道。
- 我们需要更改的每个属性都是主通道的一些变体,这些变体是直接在主通道上进行计算的,而不是单独的关键帧。
您可能还注意到了,我们正在使用`posY`和`alpha`在通道之间进行算数运算。在内部,数字实际上被转换为常量通道,所以这里只是一个简单的缩写。
请注意,此脚本实际上并不其作用,因为在内部额外的轨道已经将它的透明度设置为0,因此在它上面乘以任何数不会发生任何事情,因此,您应该这样做:
这里发生的是我们将内部和本系统提供的API叠加在一起。对于颜色,高于255的值没有什么不同,因此我们不必担心。但是对于额外轨道的位置,我们不希望它超过0,以防同时使用两种场景控制类型。我们可以使用`Channel.min` 来确保这一点。
当然,如果您不关心使用内部类型,那么
也是可以的.
所以这个lua脚本实际兼容的版本是:
本节到此结束,有了这些知识,复杂的运动只需要几行代码!
额外的调试技巧:
您可以通过记录来查看通道的组成,例如:
打开错误日志,您应该看到以下结果:
这会帮您找出为什么控制效果没有按照您的预期方式工作。
2.6. 使用note组来工作
(译者注:这一章有些抽象。可以简单理解为note组的场景控制可能会面临多控制命令间相互覆盖的问题。因此在对note组设计场景控制函数时,需要首先查询是否已经有正在执行的动画命令。)
Note组的使用有些奇怪。定义控制器的工作流程,然后将它们键入到场景控制定义工作流程中,这并不能工作。原因很简单,我们不知道我们想要哪些控制器,因为我们不知道我们的事件会被放置在哪些时间组中。
> 您可能想知道为什么它被成为note group而不是timinggroup。简单的说,您不是在这里与时间事件进行交互,而是与实际的音符本身进行进行交互。我选择使用NoteGroup来更好的反映这一点。
唯一可以确定的方法是为每个场景控制命令查询控制器。
这完全没问题,但是我们不能为每个场景控制命令创建一个新通道,因为这会覆盖每个场景控制命令的旧通道:
相反,我们需要一种方法来确定我们是否为note组创建过一个通道,如果没有,则创建一个新通道。事实上,你可以使用lua代码来实现这一点。但是这对每个通道和每个属性都是非常笨重和繁琐的。
这里我们引入新概念:通道命名。还记得您第一次使用通道时,那里有一堆“未命名”吗?这是每个通道的默认名称,您可以根据自己喜好更改通道名称。
如果一个通道由多个通道组合而成,您可以使用 `.find(name)`将每个通道分开以进行编辑,让我们看看它的实际效果:
combineChannel能够从它的组件中搜索具有您想要的名称的通道。此名称也是目标通道的本地名称,因此您甚至不必担心为每个属性命名通道。
这使我们能够做的是解决我们之前在note组中遇到的问题。 让我们尝试在note组的属性中找到我们的通道,如果它不存在,那么我们将创建一个新的并分配它。
始终为您的通道命名也是一个非常好的策略,因为它有助于调试。特别是如果您打算与其他人共享您的脚本。
2.7. 后期处理
后期是整个屏幕可见的效果,它们的范围很广:模糊屏幕、扭曲屏幕、添加晕影效果、使屏幕嘈杂、添加发光效果等。
老实说,我只是……复制了统一支持的所有内容,并以 Channels 的形式对其进行了包装。所以我自己都不知道这里 80% 的参数是做什么的。 Unity 在这方面的文档也很糟糕,所以你在这里大部分时间都是靠自己的。
话虽如此,如果您已经掌握了 Channels,那么您就有 90% 的方法可以使用后期处理。唯一的区别是您必须从`PostProcessing` 对象中获取控制器,并且您还必须单独启用每个参数才能使其生效(因为启用它们会对性能产生巨大影响)
每个后期处理控制器还可以具有不随时间改变的属性。我不会演示它们,因为它们似乎都没有用,但是如果您好奇,请随时查看文档以获取更多详细信息。
这是一个演示,可能是所有这些中最有用的后期处理效果,颜色分级:
这是结果:

2.8. 一些其他的细节
这些都是你应该知道的小细节,但不足以保证整个部分的内容。
注意事项
1. 相机在aff场景命令和Scenecontrol API中的配合有很大不同!
2. 所有文件路径都是相对于 *`Scenecontrol` 文件夹*,而不是相对于运行脚本
> 例如,如果在您的 `init.lua` 脚本中,您引用了脚本 `folder/other_script.lua`,并且在 `other_script.lua` 中,您尝试使用路径 `image.jpg` 创建一个精灵,实际文件路径应位于 `Scenecontrol/image.jpg` 而不是 `Scenecontrol/folder/image.jpg`
3. 在为精灵、图像和文本选择材质时,请注意,任何不以 `fast` 开头的东西都会严重影响性能,尤其是在低端硬件上。
提示
1. 对于那些熟悉宏的人。 `Event` 和 `Context` 对象可用。我不知道为什么需要`Event`,但这是同样开放使用的。
2. 除了 `log`,`notify` 也是一个选项,它会在 toast 通知上显示一条消息。
3. 您可以随时使用 `log(channel.valueAt(timing))` 获取任何时间点的值。这对于计算场景中对象的坐标也很有用。
4.您可以通过`Context.availableFonts`获取所有有效安装字体的列表。但是,仅将其用于调试,并且不要将其包含在实际脚本中。
5. 除非您确实打算将变量设为全局变量,否则请始终使用 `local`。
下一步是什么?
恭喜您完成了关于新的场景控制 API 的本教程!
您现在应该有足够的知识来有效地使用参考文档,该文档将详细说明 API 的所有内容,供您在编写自己的脚本时参考。 [请在这里查看](https://github.com/Tempestissiman/ArcadeScenecontrol/wiki)
希望您玩得开心!