# Arcade SceneControl v4
ArcadeZero v4の新しいSceneControl APIのチュートリアルです
[リファレンス集\(英語)](https://github.com/Tempestissiman/ArcadeScenecontrol/wiki)
[旧APIドキュメント\(英語)](https://github.com/Tempestissiman/ArcadeScenecontrol/tree/a30c13efc25b0a4a6e32a820b3dbb5c363564d71)
[旧APIドキュメント\(日本語)](https://hackmd.io/@Gq-K8NqYQ_aQFgcUurVHnw/rJ-I3i9Ud)
[English document](https://github.com/Tempestissiman/ArcadeScenecontrol/blob/v2/README.md)
## はじめに
このドキュメントは、旧SceneControl APIの初心者と経験者の両方の方が全く新しいSceneControl APIを利用できるようになれるよう設計されています。
ここでは新APIのすべての機能を網羅することはできませんが、自身でこのAPIの可能性を開拓していく十分な知識を準備することはできるでしょう。
新APIの詳しい情報は[リファレンス集\(英語)](https://github.com/Tempestissiman/ArcadeScenecontrol/wiki)をチェックしてください。
例によって、スクリプトの作成にはLua言語の知識が必要であり、このドキュメントでもある程度のスキルが要求されます。もしLuaの初心者であったりご無沙汰である場合は、[Lua公式チュートリアル](https://www.lua.org/pil/contents.html)を参照してください。
### 新機能
新APIでは旧版のこれらの問題が解決されています:
1. 低速:旧APIはユーザが書いたLuaコードに強く依存しており、Arcadeの実行中でもそんな状態でした。動作が低速であるだけでなく、iOSなど他のプラットフォームへの移植ができなくなる可能性がありました。
2. 低い柔軟性:関数を登録する仕組みが複雑過ぎて、同じオブジェクトを複数のscenecontrolで操作するような簡単なタスクが過度に複雑になっていました(これでは、そのために作られたものなのに意味がありません!)
3. 全体的に不格好で、直感的ではありませんでした。
それを踏まえて、新APIで新しいシステムを導入した理由がこちらです:
1. 完璧な仕組み:新APIは*Channel*という概念に基づいています(詳細は後ほど!)。これによりLuaコードの実行は一度だけで済み、またLuaを必要としない他のフォーマットに出力することもできます!
2. 透明性:ユーザはこの新システムの完全な主導権を持っており、それによってより早いスクリプト作成を可能にします。
3. コラボレーション:他のユーザのスクリプトを利用することがより簡単になります!
加えて、新APIの特徴とは関係ありませんが、新機能のアップデートがいくつかあります:
1. SceneControl編集ウィンドウ!ArcadeZero内の画面右側のウィンドウで編集可能
2. ポストプロセスエフェクトなどの新要素
3. Luaスクリプトのいらない組み込みのscenecontrolコマンド
ご興味をお持ちいただけたならば、もう時間を無駄にしている暇はありません!早速チュートリアルへと進みましょう!
### 旧システムをご愛願いただいている方へ
従来のスクリプトを新システムに移植することは完全に可能となっていますが、座標や回転などの記述は書き換える必要があります。
より直感的に、規則正しく編集できるよう、記述方法にいくつかの変更を加えています。
## はじめよう
必要なツール:
- ArcadeZero v4以降、またはその派生バージョン
- 使いやすいIDEやテキストエディタ(VSCodeがおすすめ)
- VSCodeを使用する場合は、拡張機能「Vscode Arcaea File Format Support」(https://github.com/yojohanshinwataikei/vscode-arcaea-file-format)とLua言語拡張をインストールすることをお勧めします
## Part 1. 組み込みコマンド
まずは簡単な基礎から。組み込みコマンドを使ってみましょう。最初に新しいSceneControl編集ウィンドウを試します。
### aff用コマンド構文
affファイルに手書きでscenecontrolコマンドを書き込むという従来の方法に慣れているかと思いますが、scenecontrolの基本的な構文はこの通りです:
```
#affファイル:
scenecontrol(timing, scenecontrolType, argument0, argument1, argument2, ...);
例:
scenecontrol(1000, trackdisplay, 1000, 127);
```
- `timing` エフェクトやイベントを発生させるタイミング
- `scenecontrolType` このコマンドのタイプ(種類)
- `argument0, argument1,...` コマンドの動作を自在に変化させるためのパラメータ
timinggroupの中にscenecontrolを入れることもでき、それによって動作が変わったり変わらなかったりします(もちろんコマンドのタイプによります)。
### 組み込みコマンドを使う
ArcadeZeroではこれらのコマンドのタイプに対応しています:
```
#affファイル:
scenecontrol(timing, trackdisplay, duration, changeToAlpha);
scenecontrol(timing, hidegroup, duration, changeToAlpha);
scenecontrol(timing, enwidenlane, duration, changeToState);
scenecontrol(timing, enwidencamera, duration, changeToState);
```
- `trackdisplay` トラックの不透明度(*alpha*のこと)を変化させる
- `hidegroup` このscenecontrolコマンドがあるtiminggroup内のすべてのノートの不透明度を変化させる
- `enwidenlane` トラックの両端に追加のレーン2つを出現させる(Pentiment、Arcana Eden、Testifyで使用されていたもの)
- `enwidencamera` カメラをenwidenlaneに対応した位置に移動させる
`timing`と`duration`はどちらもミリ秒で表します。
`changeToAlpha`は0~255の範囲で指定します。
`changeToState`は0でエフェクトを無効化、1でエフェクトを有効化します。
> 厳密には本家ゲームの仕様とは技術的に異なるのですが、ArcadeZeroにおけるscenecontrolの整合性を重視するため、あえて異なる仕様としています。
もしこれらのscenecontrolコマンドに慣れていなければ、しばらくの間は自身でこれらを書いてみることを強くお勧めします。そのほうがスキルアップにつながりますよ!
### 編集ウィンドウ
それではscenecontrolの編集ウィンドウに慣れていきましょう。譜面を開き、画面右側の☆アイコンのボタンをクリックします。するとこのような小さなウィンドウが現れます:
<img src="https://i.imgur.com/oBUABzr.png">
それぞれのボタンの機能:
- scenecontrolタイプのドロップダウンリスト。ここから編集するタイプを選択します。
- テキストフィールド切り替えボタン。テキストフィールドをそれぞれのパラメータ毎に分割するか、一つに結合するかを切り替えます。
- 更新ボタン。すべてのスクリプトを読み込みなおしてリストを更新します。
その下にあるのがscenecontrolイベントのリストです。現在選択されているscenecontrolタイプに対応するイベントが譜面に含まれていないため、今は空になっています。
いずれかの行の「+」ボタンを押して一つ追加しましょう。
要素が一つ追加されることが確認できます。
<img src="https://i.imgur.com/Im7es5n.png">
ここから好きなようにパラメータを変更できます!
加えて、このウィンドウは現在編集中のTiming Groupに含まれているScenecontrolイベントのみを表示するものになっていることにご注意ください。
リストには何もないのになぜエフェクトが有効になっているのかという勘違いの原因となり得ます。
基本は以上です!
これでscenecontrolの大まかな仕組みを理解できましたので、次章ではついにスクリプト作成パートへ移ります。
## Part 2. スクリプト作成
### 2.1. プロジェクト構造
これまではすでに用意されたscenecontrolタイプで作業をしてきました。しかし自身のArcaea譜面を制作することの真髄は自身のscenecontrolタイプを独自に定義することにあります。
さあ、やっていきましょう!
最初に自身の譜面プロジェクトフォルダを開いてください(`Arcade`フォルダ、楽曲音声ファイル、ジャケット画像ファイルなどがあるフォルダ)。
`Scenecontrol`というフォルダを新規作成して(大文字小文字すべて一致すること)、その中に`init.lua`というファイルを作成します。
ArcadeZeroは譜面ファイルを読み込んだ際に、最初に`Scenecontrol/init.lua`を実行するようになっています。
`Scenecontrol`フォルダはスクリプトによって読み込まれる素材ファイル(画像など)を置く場所でもあります。
> 上級者向け:`require`を用いてLuaコードを複数のスクリプトファイルに分割することができます。それらのファイル名は自由に決められます(デフォルトである`init.lua`を除く)。
新たに作成したLuaファイルを開いてコーディングしていきましょう!
まずは独自のscenecontrolタイプを定義することはせずに、「*Controllers*」と「*Channels*」という概念に着目していきます。
(独自のタイプを定義する方法は今後のパートで紹介します!)
### 2.2. ControllerとChannel - レベル1
>この章ではすでにシーン内に存在しているものを取得して自在に操作する方法を紹介します!
Controllerは自身で作成したLuaコードがシーン内のオブジェクトを操作するために働きかける場所です。
例えば、トラックを操るためにTrack Controllerに働きかけたり、2Dの画像オブジェクトを操るためにSprite Controllerに働きかけたりします。
ここでの「働きかける」とは、位置、回転、スケール、色などのプロパティを変更することを意味します。
これは旧システムと同じですね!
しかし、プロパティを**直接**編集する旧システムとは違い、今回はやや間接的な方法になります。
それは、**Channel**です。
Channelとは何でしょうか?
すべてのChannelは共通して一つの役割を持っています。これがその答えです。
###### 「楽曲の今の再生位置はxミリ秒である。この時のプロパティの値は何か?」
Controllerのそれぞれのプロパティは紐づけられたChannelを持っており、このChannelはあらゆる時点でのプロパティの値をどのように変化させるかを決定しています。
まだこれがどういうことなのかよく分からないとは思います。なので、実際のLuaコードで具体的な例を挙げて解説します。
まずControllerを定義するところから始めます。
今はすでにある内部のControllerを使います。独自のControllerをインポートするやり方については後程扱います。
Controllerをひとつ取得するには`Scene`を用います。この`Scene`はほとんどのControllerを取得できる場所です(また、ほとんどの独自のControllerを生成する場所でもあります)。
```lua
-- init.lua
local controller = Scene.track --Track Controllerを取得する
```
このControllerの位置を移動するには:
```lua
-- init.lua
...
controller.translationX = Channel.constant(1)
controller.translationY = Channel.constant(2)
controller.translationZ = Channel.constant(3)
```
SceneControl編集ウィンドウの更新ボタンをクリックします。
これにより、トラックを新しい座標(1, 2, 3)に移動させるようArcadeに指示するこのスクリプトが読み込まれます。
そして、この通りトラックは新しい場所に配置されていることが確認できるはずです。
<img src="https://i.imgur.com/JK7SyQ5.png">
この一連の出来事を詳しく説明するとこうなります:
1. `Channel.constant(value)`は、Timingに関わらず渡したい定数を常に返すChannelです。
2. このChannelを3つ用いて、このControllerの3つのプロパティにそれぞれ値を割り当てます。
3. Controllerは渡されたChannelをもとにそれらのプロパティの値を更新します。ここでは、1、2、3の3つの定数を渡されています。
このスクリプトではまだなにも面白いことはしていませんが、これはシステム全体を理解する上でとても基本的なことです。
Channelについて理解できれば、この新しいSceneControlについて理解できたことになります。
じっくり時間をかけて学んでいきましょう!
"完全に理解"できましたら、次は時間をかけてオブジェクトを移動させる方法を見ていきましょう。
これは`Chennel.constant()`では実現できません。
ここで、**Keyframe Channel**の登場です。
Keyfrarme Channelは**Key**という概念をもとに定義されています。
もし何かしらのアニメーションに取り組んだことがあれば、このKeyという概念に馴染みがあるのではないでしょうか。
そうでない方向けに、例をお見せします。
<img src="https://i.imgur.com/t7w0ezs.png">
このChannelには3つのKeyがあります。
- 一つ目のKeyはTiming=0の位置にあり、その位置での値は「0」となっています
- 二つ目のKeyはTiming=1000の位置にあり、その位置での値は「1」となっています
- 三つ目のKeyはTiming=2000の位置にあり、その位置での値は「0」となっています
そして、これらの点同士をつなぐことで、それぞれの点の間の値も常に一つに決まることが見て分かりますね。
実際に、Keyframe Channelもこのようにとてもシンプルな仕組みになっています。
上記の3つのKeyをLuaコードで表現してみます。
```lua
--init.lua
local channel = Channel.keyframe()
channel.addKey(0, 0)
channel.addKey(1000, 1)
channel.addKey(2000, 0)
controller.translationX = channel
```
>この書き方は一つの例に過ぎず、短縮して記載する場合はこのように書き換えることもできます:
```lua
controller.translationX = Channel.keyframe().addKey(0, 0).addKey(1000, 1).addKey(2000, 0)
```
> 一度作成したChannelは再利用することができます!つまり、同じChannelを複数のプロパティに割り当てることも可能であるということです。そうした場合にそのChannelに変更を加えた際は、そのChannelを割り当てられているすべてのプロパティがその変更点に従うことになります。こうすることで、複数のプロパティに一括で変更を加えられるという利便性を実現できます。
```lua
local channel = Channel.keyframe().addKey(0, 0).addKey(1000, 1).addKey(2000, 0)
controller.translationX = channel
controller.translationY = channel
controller.translationZ = channel
-- これらとは全く関係のないプロパティでも大丈夫
controller.colorR = channel
```
SceneControl編集ウィンドウから更新して結果を見てみましょう。
トラックがTiming 0から1000までの範囲で直線的に別の位置へ移動していき、Timing 1000から2000までの範囲で元の位置に戻っていったはずです。
直線的な移動にはしたくないですか?イージングを使いたいですよね!
それなら、単純にこのように文字列を一つ追加するだけでできます。
```lua
local channel = Channel.keyframe()
channel.addKey(0, 0, 'so')
channel.addKey(1000, 1, 'so')
channel.addKey(2000, 0, 'so')
```
>注意:ここでの`so`は、アークノートの`si`の動きと同様のものになります(最初は速く、最後には遅くなる動き)。これは公式が定めたアークノートの動きが逆になっているということなので、混同しないようご注意ください。
Channelを生成するときにデフォルトのイージングを設定することもできます。以下のコードは上記のものと同じ動きになります。
```lua
local channel = Channel.keyframe().setDefaultEasing('so')
channel.addKey(0, 0)
channel.addKey(1000, 1)
channel.addKey(2000, 0)
```
これが、対応しているすべてのイージングのリストになります。それぞれのイージングは複数の書き方で記述することができます。
| 名称 | 別名 | 移動の形 |
| - | - | - |
| 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> |
> おまけ!また、チャンネルの外挿(はみ出し)をオンにすることもできます。外挿は基本的に、追加したキーフレームを越えてカーブを継続します。使いどころは分かりませんが、とりあえずここに書いておきます。
```lua
local channel = Channel.keyframe()
.setDefaultEasing('so')
.setIntroExtrapolation(true)
.setOuttroExtrapolation(true)
```
これでControllerとChannelの基礎をマスターし、自在にあらゆるプロパティを操ることができるようになりましたね!
しかし、このAPIにはこれまでの一連の作業をより簡単にするとても便利な機能があるのです。
それについては「ControllerとChannel - レベル2」の章をお待ちください。
次は、最初に目指していた*独自のSceneControlタイプを定義する*ことをやってみましょう。
### 2.3. SceneControlタイプの追加
SceneControlタイプのコンセプトを完全に無視して、全てのKeyを`init.lua`に書くこともできます。ただし、再利用性を考えて、タイプを定義することをおすすめします。~~あなたが1347ファイルものluaスクリプトを書くことに躊躇しないなら止めませんがね。~~
そのためには何が必要でしょうか。
- SceneControlの名前を指定する(自由に決められます!)
- 引数の数とその名前を指定する
- そのSceneControlの動作を定義する
3つのうち2つはある関数を使うことでカバーできます。残った1つも前の章でやったことです。では、やっていきましょう。
まずはシンプルな例から。引数に3つの数値をとって、トラックを500ミリ秒かけて現在の位置から引数で指定された位置に移動させる、SceneControlタイプを作成しましょう。
```lua
local track = Scene.track
track.translationX = Channel.keyframe()
track.translationY = Channel.keyframe()
track.translationZ = Channel.keyframe()
addScenecontrol("mytypename", 3, function(cmd)
local timing = cmd.timing
local x = cmd.args[1] -- 他の言語とは違い、luaの添字は1から始まります
local y = cmd.args[2]
local z = cmd.args[3]
track.translationX.addKey(timing, track.translationX.valueAt(timing))
track.translationX.addKey(timing + 500, x)
track.translationY.addKey(timing, track.translationY.valueAt(timing))
track.translationY.addKey(timing + 500, y)
track.translationZ.addKey(timing, track.translationZ.valueAt(timing))
track.translationZ.addKey(timing + 500, z)
end)
```
何が起こったのかを見てみます。
1. Keyを追加するためのChannelを作成して、設定しました。
2. "mytypename"という名前の、引数を3つ取るSceneControlタイプを定義しました。
つまり、affコマンドは`scenecontrol(timing, mytypename, arg1, arg2, arg3)`のようになります。
3. SceneControlタイプの動作を関数を渡して定義しました。基本的に、この関数は譜面内のすべてのSceneControlイベントに対して実行されます。この関数はそのaffコマンドから読み取られた引数に従って動作します。つまり、1.のChannelにKeyを追加するのです。
> もし、あなたがArcadeのマクロを見慣れているなら、この形は見たことがあるはずです!唯一の違いは、動作定義の関数にSceneControlコマンドから情報を受け取るための引数が追加されていることです。
また、`channel.valueAt(timing)`にも注目してください。Keyframeを扱うときに便利な機能で、指定したタイミングでの値を取得できます。
この例は、スクリプトの基本的な構造もうまく示しています。
- コントローラーを取得して、
- プロパティにChannelを割り当てて……
- それから、そのChannelにKeyを追加するSceneControlタイプを定義します。
これでタイプの定義が終わりました!Arcadeを起動して、SceneControl編集ウィンドウを開いてください。ドロップダウンメニューに"mytypename"が表示され、イベントを追加することができるようになるはずです。
ところで、編集ウィンドウの表のヘッダーに何か書いてあるのが見えるかもしれません。それらは各列の引数の名前を表していて、もちろん!変更できます。
```lua
addScenecontrol("mytypename", {"xpos", "ypos", "zpos"}, function(cmd)
...
end)
```
`{"xpos", "ypos", "zpos"}`は引数の名前の配列です。スクリプトを再読み込みしてウィンドウを見てみてください!
> 引数のないイベントも正しく動きます!以下のコードは、affコマンドの`scenecontrol(timing,mytypename);`に対応しています。
```lua
addScenecontrol("mytypename", 0, function(cmd) ... end)
-- or --
addScenecontrol("mytypename", {}, function (cmd) ... end)
```
ここまでで、すでに多くのエフェクトを再現できるようになったはずです。しかし、`Scene`について知ることで、さらに一歩踏み込んでみましょう!
### 2.4. Scene
`Scene`オブジェクトには2つの機能があります:内部Controllerを取得すること・新しいControllerを生成すること
前者は既に実施しましたが、それはこれまでのところ1つのタイプのみです。
このチュートリアルでは全てのタイプの詳細を説明することはないので、より詳細な情報についてはドキュメントを参照してください。
SceneControlでできることのアイデアを得られるよう、全ての内部Controllerのリストを紹介します。
| パス | タイプ | 概要 |
| - | - | - |
| Scene.gameplayCamera | CameraController | 実際のゲーム画面を映すカメラ |
| Scene.combo | TextController | コンボのテキスト |
| Scene.score | TextController | スコアのテキスト |
| Scene.jacket | ImageController | ジャケット画像 |
| Scene.title | TextController | 楽曲名のテキスト |
| Scene.composer | TextController | 楽曲作者名のテキスト |
| Scene.difficultyText | TextController | 難易度表示のテキスト |
| Scene.difficultyBackground | ImageController | 難易度表示の背景画像 |
| Scene.hud | CanvasController | ポーズボタンとスコアパネルが配置されているキャンバス(UI表示のための平面全体) |
| 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 (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 (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 | SkyInputのライン |
| Scene.skyInputLabel | SpriteController | SkyInputのラベル |
| Scene.darken | SpriteController | 背景の黒いオーラの画像(内部組み込みのSceneControlタイプであるtrackdisplayで使用) |
> ImageControllerとSpriteControllerの違いについて疑問に思われているかと思います。これは簡単に言うと…
> - SpriteControllerではsorting layerとsorting orderを設定できます(ImageControllerではできない)。
> - ImageControllerではピボットとアンカーを用いてより簡単に位置を操作することができます。ImageControllerのsorting layerとsorting orderは自身を他のキャンバスの子オブジェクト化することで変更可能になります。
ここでは、内部Controllerを使うのではなく、独自のControllerを作成し、さまざまな種類のControllerを試してみましょう。
ここで、`Scene`についてまとめておきます。
| メソッド | 返り値の型 | 概要 |
| - | - | - |
| Scene.createSprite(string imgPath, string material = "default", bool newMaterialInstance = false) | SpriteController | 指定されたマテリアルの種類で、指定されたパスからSpriteとして画像オブジェクトを生成する |
| Scene.createCanvas(bool worldSpace = false) | CanvasController | ワールド座標系(3D空間上の座標系)か画面座標系にキャンバス(画像やテキストなどを配置できる平面)を生成する |
| 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 | 指定されたそれぞれのプロパティでテキストオブジェクトを生成する |
| Scene.createMesh(string objPath, string texturePath) | MeshController | 指定された.objファイルとテクスチャ画像のパスから3Dオブジェクトを生成する |
| Scene.getNoteGroup(number group) | NoteGroupController | 指定されたTimingGroupの番号に属しているすべてのノートをまとめた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`のままにしてください。そのControllerのテクスチャのオフセットやスケールを変更する必要がある場合に`true`を指定してください。
これを踏まえて、シーンに自身のスプライトを追加してみましょう。
スプライトは通常ではトラックよりも後ろ側に配置されている「Background」レイヤーに置かれます。
なので、画像全体を見えるようにするためにこれを変更する必要があります。
スプライトのレイヤーを変更することにもChannelを用いますが、今回は`StringChannel`を使います。
```lua
local sprite = Scene.createSprite("test.png")
sprite.layer = StringChannel.constant("Foreground") -- 指定した文字列のレイヤーに変更する
sprite.order = Channel.constant(1) -- そのレイヤー内における並び順を設定する
```
そして、このようにシーン内に画像が見えるはずです:
<img src="https://i.imgur.com/tAEbq6Q.png">
「test.png」は自身で用意した画像ファイルの名前に変更しても構いません。
今度は同じようにしてマテリアルも変更してみましょう。コードを次のように変更します:
```lua
local image = Scene.createImage("test.png", "fastadd")
image.rectW = Channel.constant(300) -- 画像の幅と高さを指定する
image.rectH = Channel.constant(200)
local canvas = Scene.createCanvas(false)
image.setParent(canvas) -- この`image`は`canvas`に指定されているsorting layerに配置される
canvas.layer = StringChannel.constant("Foreground") -- レイヤーを変更する
canvas.sort = Channel.constant(1) -- そのレイヤー内での並び順を変更する
```
今回、画像の幅と高さを指定する必要があることに注目してください。これがSpriteとImageの大きな違いの一つです。
また、描画モード「Add」で画像が見えづらくならないよう、スキンをConflictに変更しました。
同様にして別の描画モードを試していくことができます。
<img src="https://i.imgur.com/vstHQ7X.png">
最後に、テキストも同様に生成してみましょう:
```lua
local text = Scene.createText("Forte") -- 指定するフォントはユーザのPCにインストールされているものでなければなりません
text.text = StringChannel.create().addKey(0, "Hello").addKey(1000, "World")
local canvas = Scene.createCanvas(false)
text.setParent(canvas)
canvas.layer = StringChannel.constant("Foreground") -- レイヤーを変更する
canvas.sort = Channel.constant(1) -- そのレイヤー内での並び順を変更する
```
見ての通り、String ChannelでもKeyframeを用いることができます。
ここでは、Timing 0で「Hello」を、Timing 1000で「World」を表示するようにKeyを追加しています。
>そして、String Channelでもイージングを使うことができます!`addKey(0, "Hello", "so")`に書き換えるとどうなるか試してみてください。
最終的にこのようになります:
<img src="https://i.imgur.com/vPq6Zm8.png">
これでControllerを生成する際に知っておくべき重要なことを網羅できました。
それぞれのタイプのControllerが持つプロパティについてはドキュメントを参照してください。
### 2.5. ControllerとChannel - レベル2
> 「ControllerとChannel - レベル2」の章では、Channelを用いた処理で凄まじい数のTimingをArcadeZeroに登録する方法について紹介します。
> Channelのあらゆるタイプについて学び、さまざまなエフェクトのChannelを一つに結び付ける方法を理解していきます。
まずは「オブジェクトを滑らかに何度も往復移動させる」というとても簡単なエフェクトで考えてみましょう。
このエフェクトは手作業でそれぞれのKeyframeを追加することで完全に実現できます。そして実際に、どれくらいの作業が必要なのかを知るために、そのコードを見てみましょう。
```lua
-- これは良くない例なので真似る必要はありません。
Channel c = Channel.keyframe()
for timing = 0, Context.songLength, 2000 do
c.addKey(timing, 0, "so")
c.addKey(timing + 500, 1, "b")
c.addKey(timing + 1500, -1, "si")
end
```
このChannelはSin波を描くように-1から1の間を振動し続けるものになっています。これでも想定通りに動作しますが、もっと良いやり方があります。
実は、Keyframe以外の**他の**種類のChannelも多くあり、より簡単に実装できるようになっています。
上記のコードはこのように短縮できます:
```lua
Channel c = Channel.sine(2000, -1, 1, 0)
-- -1から1までの範囲でSin波を生成、最初に0ミリ秒の間をおく
-- 2000ミリ秒単位でこれを繰り返す
```
同様にしてあらゆるChannelの種類が用意されています!こちらがそのリストです。
| メソッド | 返り値の型 | 概要 |
| - | - | - |
| Channel.keyframe() | KeyChannel | Keyを設定できるChannel |
| Channel.constant(value) | ValueChannel | 定数を渡すChannel |
| Channel.random(min, max, seed = 0) | ValueChannel | 常にランダムに決められた値を返すChannel |
| Channel.noise(frequency, min, max, offset = 0, octave = 1) | ValueChannel | パーリンノイズ(滑らかなランダム移動)を生成するChannel |
| Channel.sine(period, min, max, offset = 0) | ValueChannel | Sin波を生成するChannel|
| Channel.saw(string easing, period, min, max, offset = 0) | ValueChannel | 指定されたイージングでminからmaxに向かってノコギリ状にループする値を返すChannel |
| Channel.fft(freqBandMin, freqBandMax, min, max, smoothness = 0.1, scalar = 1) | ValueChannel | 現在再生中の音声の、指定された周波数範囲における平均的な大きさを返すChannel |
| Channel.max(channelA, channelB) | ValueChannel | 2つのChannelのうちの最大値を返すChannel |
| Channel.min(channelA, channelB) | ValueChannel | 2つのChannelのうちの最小値を返すChannel |
| Channel.clamp(valueChannel, minChannel, maxChannel) | ValueChannel | 最小値を表すminChannelと最大値を表すmaxChannelで値の範囲を限定したvalueChannelの値を返すChannel |
> FFTとは、Fast Fourier Transformの略で、高速フーリエ変換のことです。
> FFT Channelは0~256Hzの範囲で動作しますが、`Channel.setGlobalFFTResolution(resolution)`で範囲を変更することができます。resolutionは2の累乗である必要があります(64, 128, 256, 512, ...)。
コードを1行で書ければもっといいですよね?ちゃんとできます。
可能なエフェクトであれば異なるChannelを無限に結びつけることができます。
```lua
-- -2から2の範囲で振動
Channel vibrate = Channel.noise(100, -2, 2)
-- 1000ミリ秒毎に、1から0に向かって値を変化させ、そのあと1に戻る
Channel dampen = Channel.saw("so", 1000, 1, 0)
-- 時間とともに振動の大きさが変わっていく!
Channel combined = vibrate * dampen
```
`vibrate`と`dampen`のかけ算によって、時間とともに振動の大きさを限定しています。
`dampen`が1を返したとき振動の幅は最大になり、`dampen`が0を返したとき振動の幅は0になり振動しなくなります。
ノコギリのような波の形で値を返す`dampen`によって、脈動するような振動をするエフェクトになります。
もちろん、同様にしてKeyframe Channelに他のChannelを結びつけることもできます。
これは非常に効率的なやり方の可能性を広げるもので、実際に内部の組み込みのSceneControlタイプを実装するために使用されています
その一つである`enwidenlane`について見てみましょう。
このSceneControlはこれらの役割を持っています:
- 2つの拡張レーンの不透明度を0~255の間で変化させる
- 2つの拡張レーンの端のラインの不透明度を0~255の間で変化させる
- 通常時のレーンの両端のラインの不透明度を0~255の間で変化させる
- 2つの拡張レーン(レーン0とレーン5)の判定線の不透明度を0~255の間で変化させる
- レーン0と1の分割線とレーン4と5の分割線の不透明度を0~255の間で変化させる
- 2つの拡張レーンの位置を-100~0の間で変化させる
全てをKeyframeで操作することができますが、とてもややこしく、記述する量も多くなります。
その代わりに、内部ではこのようになっています。
```lua
-- 注! 内部のC#コードをLuaに訳したものです。これは実際に正しく動作するコードではありません。
local track = Scene.track
-- The main channel, which is 0 by default
local enwidenLaneFactor = Channel.keyframe().setDefaultEasing("l").addKey(0, 0);
-- These objects are disabled by default. Enabling them:
track.extraL.active = Channel.constant(1)
track.extraR.active = Channel.constant(1)
track.criticalLine0.active = Channel.constant(1)
track.criticalLine5.active = Channel.constant(1)
track.divideLine01.active = Channel.constant(1)
track.divideLine45.active = Channel.constant(1)
track.edgeExtraL.active = Channel.constant(1)
track.edgeExtraR.active = Channel.constant(1)
-- メインのChannelを単純に変換しただけのChannelを割り当てる
track.edgeExtraL.colorA = track.edgeExtraL.colorA * enwidenLaneFactor
track.edgeExtraR.colorA = track.edgeExtraR.colorA * enwidenLaneFactor
track.criticalLine0.colorA = track.criticalLine0.colorA * enwidenLaneFactor
track.divideLine01.colorA = track.divideLine01.colorA * enwidenLaneFactor
track.divideLine45.colorA = track.divideLine45.colorA * enwidenLaneFactor
track.criticalLine5.colorA = track.criticalLine5.colorA * enwidenLaneFactor
track.extraR.colorA = track.extraR.colorA * enwidenLaneFactor
track.extraL.colorA = track.extraL.colorA * enwidenLaneFactor
local posY = -100 * (1 - enwidenLaneFactor)
track.extraL.translationY = track.extraR.translationY + posY
track.extraR.translationY = track.extraR.translationY + posY
local alpha = (1 - enwidenLaneFactor)
track.edgeLAlpha = track.edgeLAlpha * alpha
track.edgeRAlpha = track.edgeRAlpha * alpha
-- それぞれのSceneControlコマンドで実行すること:
addScenecontrol("enwidenlane", {"duration", "toggle"}, function(cmd)
local timing = cmd.timing
local duration = cmd.args[1]
local toggle = cmd.args[2]
enwidenLaneFactor.addKey(timing, enwidenLaneFactor.valueAt(timing))
enwidenLaneFactor.addKey(timing + duration, toggle)
end)
```
このコードがよく分からない方のために、簡単な解説を添えておきます:
- 2つ目の引数「toggle」は、0か1の値をとります。この値を`enwidenLaneFactor`と名付けたChannelに直接代入しています。
- 変更する必要があるすべてのプロパティは、単にメインのChannelのいくつかのtranslationにおいて、個々のKeyの代わりにChannel自身で直接計算しています。
また、`posY`や`alpha`の持つ数値とChannelの間で計算を行っていることに気づいたかと思います。
内部ではこうした単純な数値は定数を持つChannel(`Channel.constant(value)`)に変換されるので、便利な略記になります。
このスクリプトは実際には動作しないことに注意してください。
なぜなら、内部的に拡張トラックはすでに不透明度`alpha`を0に設定するよう実装されているので、そこへの上書きは起こらないためです。
代わりに次のようにしてください。
```lua
track.extraEdgeL.colorA = track.extraEdgeL.colorA + 255 * enwidenLaneFactor
-- 判定線、分割線、拡張レーンと同様
local posY = 100 * enwidenLaneFactor
track.extraL.translatonY = Channel.min(track.extraEdgeL.translationY + posY, Channel.constant(0))
-- 右側拡張レーンと同様
```
ここでは独自のChannelと内部のChannelを一緒に追加しています。
色に関しては255を超える値は動きに違いが生まれないので気にする必要はありませんが、拡張レーンの位置においては0を超えて移動させるわけにはいきません。
これを保証するために`Channel.min()`を使用します。
もちろん内部のタイプを気にしないのであればこのようにできます:
```lua
track.extraEdgeL.colorA = 255 * enwidenLaneFactor
-- 判定線、分割線、拡張レーンと同様
track.extraL.translationY = -100 * (1 - enwidenLaneFactor)
-- 右側拡張レーンと同様
```
よって、実際に動作するLuaスクリプトとして記述したものが以下になります:
```lua
local track = Scene.track
-- メインで扱うChannel、デフォルトの値は0
local enwidenLaneFactor = Channel.keyframe().setDefaultEasing("l").addKey(0, 0);
-- 何も有効化をする必要はありません。内部のSceneControlタイプですでに有効化済みです
track.edgeExtraL.colorA = track.edgeExtraL.colorA + 255 * enwidenLaneFactor
track.edgeExtraR.colorA = track.edgeExtraR.colorA + 255 * enwidenLaneFactor
track.criticalLine0.colorA = track.criticalLine0.colorA + 255 * enwidenLaneFactor
track.divideLine01.colorA = track.divideLine01.colorA + 255 * enwidenLaneFactor
track.divideLine45.colorA = track.divideLine45.colorA + 255 * enwidenLaneFactor
track.criticalLine5.colorA = track.criticalLine5.colorA + 255 * enwidenLaneFactor
track.extraR.colorA = track.extraR.colorA + 255 * enwidenLaneFactor
track.extraL.colorA = track.extraL.colorA + 255 * enwidenLaneFactor
local posY = 100 * enwidenLaneFactor
track.extraL.translationY = Channel.min(track.extraR.translationY + posY, Channel.constant(0))
track.extraR.translationY = Channel.min(track.extraR.translationY + posY, Channel.constant(0))
local alpha = (1 - enwidenLaneFactor)
track.edgeLAlpha = track.edgeLAlpha * alpha
track.edgeRAlpha = track.edgeRAlpha * alpha
-- タイプの競合を避けるために、あえて異なる名称を定義
addScenecontrol("enwidenlanelua", {"duration", "toggle"}, function(cmd)
local timing = cmd.timing
local duration = cmd.args[1]
local toggle = cmd.args[2]
enwidenLaneFactor.addKey(timing, enwidenLaneFactor.valueAt(timing))
enwidenLaneFactor.addKey(timing + duration, toggle)
end)
```
最後に、**内部で組み込み済みのタイプを自分で作成したスクリプトで上書きすることはできないと認識してください。**
先ほどのコードが実際には動作しないというのは、これが理由です。
同じ名称や動作のSceneControlタイプを定義したい場合は、あえて異なる名称を定義するようにしてください。
この章はこれで以上です。この知識があれば、複雑な動きも数行のコードで実現可能になります!
#### おまけ:デバッグ
ログを出力することで、そのChannelがどのような構成になっているかを見ることができます。
```lua
local channel = Channel.saw("si", 1000, 1, 0) * Channel.noise(100, -1, 1)
channel += Channel.keyframe()
channel *= 5
log(channel)
```
エラーログを開くと以下のような結果が見られるはずです。
```
((unnamed@saw(si,1000,0,1,0)) * (unnamed@noise(100,-1,1,0,1)) + unnamed@key(0)) * (5)
```
これを利用することで、うまくエフェクトが動作していないときの原因を簡単に探れるようになります。
### 2.6. Note Groupを使う
Note Groupを使うことは、少し奇妙な作業になるかもしれません。Controllerを定義して、Keyframeを設定するという今までの方法は、どのControllerが欲しいのかが分からないためうまくいかないでしょう。
なぜなら、このSceneControlがどのTiming Groupに追加されるか事前に知ることができないからです。
> なぜTiming Groupではなく、Note Groupと呼ぶのか不思議に思うかもしれません。簡単に言うと、ここではTimingイベントを操作するのではなく、実際のノートそのものを操作するのです。それを反映させるために、Note Groupとしています。
それを確認する唯一の方法は、SceneControlコマンドのたびにControllerを取得することです。
```lua
addScenecontrol("myType", {}, function(cmd)
local noteGroup = Scene.getNoteGroup(cmd.timingGroup)
end)
```
次のコードは動きますが……すべてのSceneControlコマンドごとに新しいChannelを作成することはできません。代わりにSceneControlコマンドごとに、それより古いChannelを上書きすることになります。
```lua
addScenecontrol("myType", 1, function(cmd)
local noteGroup = Scene.getNoteGroup(cmd.timingGroup)
noteGroup.translationX = Channel.keyframe().addKey(0, cmd.args[1])
end)
-- これはうまくいきません。
-- translationXには最後に実行されたときの値だけが割り当てられます。
```
その代わりに、あるNote GroupのChannelがすでに作成されているかどうかを調べ、もし作成されていなければ、新しいChannelを作成する方法が必要です。そして、もちろん、それはluaのコードで行うことができます。
ただ、すべてのChannel、すべてのプロパティに対してそうするのは、非常に不便で面倒です。
INTRODUCTION OF 名前付きChannel:[Channelをログに出力したとき](#おまけ:デバッグ)に、"unnamed"がたくさんあったのを覚えていますか?これはChannelのデフォルトの名前です。Channel名は変更することもできます!
```lua
local channel = Channel.named("myChannelName").keyframe()
```
そして、複数のChannelを組み合わせた場合、`.find(name)`を使ってそれぞれを切り離して編集することができます。実際に使ってみましょう。
```lua
local myChannel = Channel.named("IWantThisBackLater").keyframe()
-- 後で同じChannelであることを確認するために、いくつかKeyframeを追加してみましょう。
myChannel.addKey(0, 0).addKey(1000, 1).addKey(2000, 2)
log(myChannel.keyCount) -- エラーログに"3"が表示されるはずです
local otherChannel = Channel.named("OthersCreatedThisAndIDontCareAboutIt").keyframe()
local combinedChannel = myChannel * otherChannel
local myChannelFound = combinedChannel.find("IWantThisBackLater")
log(myChannelFound.keyCount) -- ここでも"3"が表示されるはずです
```
`combinedChannel`は、その構成要素の中から希望する名前のChannelを取得する機能を備えています。その名前は、そのChannelに対してローカルなものでもあるので、プロパティごとに異なるChannel名を付ける必要もありません。
これによって、先ほどのNote Groupの問題を解決することができます。NoteGroupのプロパティからチャンネルを探し、存在しないなら新規に作成して割り当てましょう。
```lua
addScenecontrol("myType", 1, function(cmd)
local noteGroup = Scene.getNoteGroup(cmd.timingGroup)
-- プロパティからChannelを探す
local channel = noteGroup.translationX.find("myType")
if channel == nil then -- 見つからなかったら……
-- Channelを作成して、
channel = Channel.named("myType").keyframe()
-- プロパティに割り当てる
noteGroup.translationX = channel
end
channel.addKey(0, cmd.args[1])
end)
```
常に名前付きChannelを使うのはとてもいいアイディアです。デバッグにも効果的です。あなたのスクリプトを他の人と共有するつもりがあるのなら、なおさら良いでしょう。
### 2.7. ポストプロセス
ポストプロセスとは、画面全体にかかる視覚効果のことです。画面をぼかす、画面を歪ませる、ヴィネット効果[^271]を加える、ノイズを加える、グロー効果を加えるなど、さまざまな効果があります。
正直に言うと、私はただ……Unityがサポートしているものを全部コピーして、Channelという形で整えただけなんです。
だから、プロパティのほとんどは何をするものなのか、自分でもよくわかっていません。
Unityのドキュメントも……あまりいいとは言えないので、ここは自分で調べてやってくださいね?
とはいえ、もしあなたがChannelをマスターすることができたなら、ポストプロセスを使えるようになるまでの道のりの90%は進んでいることになります。(使いこなせるようになるかは……あなた次第ですが)
唯一の違いは、`PostProcessing`オブジェクトからControllerを取得する必要があることと、エフェクトの効果を発揮させるためには各プロパティを個別に有効にする必要があることです。
これらを有効にするとパフォーマンスに大きな影響を与えるため、必要なプロパティのみを有効にしてください。
各ポストプロセスのControllerは、時間の経過によって変更されないプロパティを持つこともできます。どれも仕えなさそうなのでデモはしませんが、興味があるならドキュメントで詳細を確認してみてください。
ここでは、ポストプロセスの中で最も有用(かもしれない)
「カラーグレーディング[^272]」のデモンストレーションを紹介します。
```lua
local colorGrading = PostProcessing.colorGrading
colorGrading.enableEffects({"temperature", "colorfilter", "saturation"}) -- 3つのプロパティを有効化
colorGrading.temperature = Channel.constant(10) -- 色温度を増加
colorGrading.ColorS = Channel.constant(-0.2) -- 彩度を減少
```
上のスクリプトを実行すると、こうなります。
<img src="https://i.imgur.com/3raINS4.png">
[^271]: 画面中央を明るく、画面端を暗くして、中央に注目を集めるエフェクト
[^272]: 色味や明るさなどを調整するエフェクトです。まぁ、画像を見れば分かるでしょう
### 2.8. Extra: ヒントと注意事項
これらはすべて知っておくべき細かいことですが、チャプターを一つ割くほどのことではありません。
#### 注意事項
1. aff内のCameraコマンドと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`を使うこともできます。
3. `log(channel.valueAt(timing))`を使えば、いつでも好きなタイミングでの何かの値を見ることができます。これは、例えばシーン内のオブジェクトの座標を把握するのにも便利です。
4. `Context.availableFonts`から、デバイスにインストールされている利用可能なフォントのリストを取得することができます。ただし、これはデバッグにのみ使用し、実際のスクリプトには含めないようにしましょう。
5. 変数をグローバルにするつもりがない限り、**常に`local`を使用してください。**
## お次はなんだ?
新しいScenecontrol APIに関するチュートリアルはこれにて完了、おめでとうございます!!
これで、リファレンスを効果的に利用するための知識は十分なはずです。リファレンスにはAPIに関するあらゆることが詳しく書かれています。自分でスクリプトを書くときに参考にしてください。[リファレンスはこちら](https://github.com/Tempestissiman/ArcadeScenecontrol/wiki)
***Have fun scripting!***
---
## クレジット
(敬称略)
**原著**
Tempestissiman
**翻訳**
Yut0124
rassvet_ii
### 編集ログ
>自身が担当する節にアカウント名を記入してください:
>
> Introduction, Getting Started: Yut
> Part 1. Built-in commands: Yut
> Part 2.1. Project structure: Yut
> Part 2.2. Controllers & Channels - Level1: Yut
> Part 2.3. Adding a scenecontrol type: rass 完了
> Part 2.4. Scene: Yut
> Part 2.5. Controllers & Channels - Level2: Yut
> Part 2.6. Working with note groups: rass 完了
> Part 2.7. Post processing: rass 完了
> Part 2.8. A few extra tips and cautions: rass 完了
> What's next?: rass 完了
>
> **翻訳メモ**((アカウント名) 内容)
> - (Yut)今回も多くの内容がありますが、頑張っていきましょう!基本的には自力での翻訳をお勧めしますが、DeepLによる訳を参考にするのもありです
> - (rass) こう訳した、ってのを書き連ねます。意見ください
> - (rass) scenecontrol type: SceneControlタイプ
> - (rass) aff: aff
> - Affの方がいい?
> - (rass) chart: 譜面
> - (Yut) controllers: Controller
> - (Yut) channels: Channel
> - (Yut) keys: Key
> - (Yut) keyframes: Keyframe
> - (Yut) ms: ミリ秒
> - (rass) timing group: Timing Group
> - 調子が乱れる気がする。TimingGroupの方がいい?
> - (rass) timing (イベントに対して): Timing
> - (rass) timing (時間という意味): タイミング
> - (rass) note: ノート
> - ノーツの方がいいかも
>
> **アップデート**
> - 2022/07/31: [af02f48](https://github.com/Tempestissiman/ArcadeScenecontrol/commit/af02f480c87386f7c33897da8f6f0cbfc94ace79)