TyrnoScriptでタイトル画面の演出スキップ機能を作ろう！ 

# 概要
今回初投稿となります。文章が至らない点などあるかとは思いますが、どうか温かい目で見てくださるとありがたいです。

早速なんですが、ノベルゲームのタイトル画面にキャラが出てきたりする演出あるじゃないですか。あれ、すごく好きなんですよ。
そんなこんなでタイトル画面の演出を作ってみたんですけど、一つ問題発生。
ボタンが出るまでの演出が長いと、最初の方は楽しいんですけど、途中からスキップ不可の演出にストレスが貯まるようになっちゃうんですよね。
そこで今回、「タイトルの演出中にクリックしたらタイトル画面の演出をスキップする機能」を作ってみました。

また、この機能を作る最中、startTagやnextOrderにかなり苦しめられたので、本記事はこの辺の割と複雑な仕様についての備忘録でもあります。startTag, nextOrder辺りに苦しめられている方に少しでも有用な情報を残せたらと思います。

# 環境
TyranoScript Ver6.00
V5以下の環境での動作は未確認です。本記事のコードでは動かない可能性があります。


# 仮演出
今回解説するにあたって、こんな感じで解説用のタイトル画面を作成しました。
ボタンが少なかったり、色々位置調整用のパラメータが少なかったりしますが、この辺は各プロジェクトで色々変えてくれればと思います。
想定としては、最初に背景として white.png が出てきて、その後にtest.pngがアニメーションしながら出てきて、最後にゲームスタート用のボタンが出てくる。といった構成になっています。
本記事はこの演出にスキップ機能を付けていこうと思います。
```
[bg storage="white.png" time="1000"]

[image storage="test.png" layer="0" name="test" visible="true" time="0"]
[kanim layer="0" name="test" keyframe="test_fade_in" time="1500"]
[wait time="1500"]

[button graphic="start.png" target="gamestart"]
[s]

*gamestart
; ゲームスタートの処理
```

# 実際に作ってみよう

### 1. bgタグのwaitパラメータをfalseにする。
TyranoScriptには、[[wait_cancel]](https://tyrano.jp/tag/#wait_cancel)という便利なタグがあります。これは、waitタグによるウェイトを強制的に解除するというタグで、これを上手いこと使ってやることで演出スキップ機能を作成します。
まずはその下準備をします。今のままだと、bgタグの画面の切り替え中にwait_cancelタグを呼んでも上手く動作しないため、bgタグの中に`wait=false`を入れ、次の行にwaitタグを追加します。
これをすると、画面の切り替えはwaitタグで待つことになるため、wait_cancelタグを使っての演出スキップが可能となります。
もしアニメーションの待機にwaタグを使っている場合も同様に動かなくなるため、必ずwaitタグに置き換えてください。

```
[bg storage="white.png" time="1000" wait="false"]
[wait time="1000"]

[image storage="test.png" layer="0" name="test" visible="true" time="0"]

[kanim layer="0" name="test" keyframe="test_fade_in" time="1500"]
[wait time="1500"]

[button graphic="start.png" target="gamestart"]
[s]

*gamestart
; ゲームスタートの処理
```


### 2. クリック待ちイベントを登録する
`window.addEventListener`というメソッドを使うことで、プレイヤーが特定の動作をした時に、指定したメソッドを実行することが出来ます。
これを使用し、プレイヤーが画面をクリックした時に画面をスキップする処理を記述します。

また、2回目以降のクリックではスキップをする必要がないため、`window.removeEventListener`を用いて、もうこれ以上スキップ処理が呼ばれないようにします。

そして、全ての演出が終わった際は、これまたスキップの必要がなくなるため、`f.isEndOpeningAnimation`という変数を用意し、それを演出が終わった時 true にしてあげることでスキップを止めます。

このタグを演出が終わるところ(つまり、sタグの1個上)に置いてあげます。
```
[eval exp="f.isEndOpeningAnimation = true"]
```

iscript内でタグを実行するには、`TYRANO.kag.ftag.startTag`を使用します。この引数に色々指定されていますが、この辺りの仕様はかなりややこしいため後述します。

実際のiscript内部のコードはこんな感じになります。
```javascript
skipOpening = () => {
    window.removeEventListener("click", skipOpening, false);
    console.log("skipOpening");
    if(!f.isEndOpeningAnimation){
        TYRANO.kag.ftag.startTag("wait_cancel", {}, () => {
            TYRANO.kag.ftag.startTag("jump", {target: "*skip_title_animation"}, () => {
                TYRANO.kag.tmp.cut_nextorder = null;
                TYRANO.kag.ftag.nextOrder();
            });
        });
    }
}

window.addEventListener("click", skipOpening, false);

f.isEndOpeningAnimation = false;
```


### 3. *skip_title_animationラベルを置く
演出スキップ処理が実行された時、*skip_title_animationというラベルにジャンプします。このラベルを同ファイル内のどこかに置いてあげます。

*gamestartラベルの上あたりがわかりやすいと思います。ラベルを置いた後は必ずすぐ下にsタグを配置しましょう。よく忘れるのでラベルと一緒に書いておくのが無難です。


### 4. 演出スキップ時の挙動を記述する
最後に、スキップした時にちゃんとタイトル画面が正しく表示されるように色々処理を書いていきます。

ここで書くのは、
1. 全てのアニメーションを止める
2. 画面内のすべての要素をリセットする
3. 画面を再構成する

です。

1は、どの場面からスキップされるかわからないので、タイトル画面で実行されるであろう全てのアニメーションを止める処理を記述します。

2も、これまたどの場面からスキップされるかわからないので、一旦全ての画像やレイヤー処理、ボタン等を消去します。これでどの場面からスキップされても同じ画面構成になります。

3で、全ての要素を一気に出してあげます。これで、どの場所からスキップしても同じ画面を表示させることが出来ます。

実際に書くとこんな感じになります
```
*skip_title_animation

; 全てのアニメーションを止める
[stop_kanim name="test_fade_in"]

; 全てのボタンを消去
[cm]

; 全ての画像を消去
[freeimage layer="0" name="test" time="0"]

; 画面を再構成
[bg storage="white.png" time="0"]
[image storage="test.png" layer="0" name="test" visible="true" time="0"]
[button graphic="start.png" target="gamestart"]

[s]
```


# 最終的に出来上がったコード
```
[iscript]
    skipOpening = () => {
        window.removeEventListener("click", skipOpening, false);
        console.log("skipOpening");
        if(!f.isEndOpeningAnimation){
            TYRANO.kag.ftag.startTag("wait_cancel", {}, () => {
                TYRANO.kag.ftag.startTag("jump", {target: "*skip_title_animation"}, () => {
                    TYRANO.kag.tmp.cut_nextorder = null;
                    TYRANO.kag.ftag.nextOrder();
                });
            });
        }
    }

    window.addEventListener("click", skipOpening, false);

    f.isEndOpeningAnimation = false;
[endscript]
[bg storage="white.png" time="1000" wait="false"]
[wait time="1000"]

[image storage="test.png" layer="0" name="test" visible="true" time="0"]

[kanim layer="0" name="test" keyframe="test_fade_in" time="1500"]
[wait time="1500"]

[button graphic="start.png" target="gamestart"]

[eval exp="f.isEndOpeningAnimation = true"]

[s]

*skip_title_animation

; 全てのアニメーションを止める
[stop_kanim name="test_fade_in"]

; 全てのボタンを消去
[cm]

; 全ての画像を消去
[freeimage layer="0" name="test" time="0"]

; 画面を再構成
[bg storage="white.png" time="0"]
[image storage="test.png" layer="0" name="test" visible="true" time="0"]
[button graphic="start.png" target="gamestart"]

[s]

*gamestart
; ゲームスタートの処理
```

# iscript内部のややこしい処理の詳細

### まずstartTagって何なのさ
一言で言うと、 **JavaScript内でTyranoScriptのタグを実行するためのもの** です。
iscript内部ではTyranoScriptのタグが書けないため、このようなメソッドを使うことで無理矢理タグを実行することが出来ます。
第一引数にタグ名、第二引数にパラメータ、第三引数にこのタグが実行し終わった後の処理が指定できます。

以下はstartTagを使ってwaitタグを実行する処理です。
```
TYRANO.kag.ftag.startTag("wait", {time: "5000"});
```

### 変な動きするよstartTag
さてこのメソッド、一見便利なメソッドに見えるのですが、実はとんでもない落とし穴があります。なんとこのstartTagというメソッド、<font color="red">**TyranoScriptのタグと同じ感覚で使用すると思い通りに動きません。**</font>思っていた動作順にならなくて痛い思いをした人も多いんじゃないでしょうか。

簡単な例を示します。
以下のTyranoScriptのコードがあったとします。black.png, white.png, orange.ongはそれぞれ黒・白・オレンジで塗りつぶされた画像です。
一見2つ目のタグがstartTagになっているだけの普通のコードに見えますが、実行してみるととても変わった挙動をします。
```
[bg storage="black.png" time="1000"]

[iscript]
    TYRANO.kag.ftag.startTag("bg", {storage: "white.png", time: "1000"});
[endscript]

#
「テスト」[p]

[bg storage="orange.png" time="3000"]

#
「テスト2」[p]

[s]
```

まず、背景が黒に切り替わったあと、背景が白になりながら「テスト」の文字が出てきます。
その後に何もしないと、少し経ってから勝手に背景がオレンジに切り替わった後、「テスト2」の文字が出てきます。
また、「テスト」の文字が出てきた時にクリックで改ページをするともっと不思議なことが起き、クリックした瞬間、背景がオレンジになりながら「テスト2」の文字が出てき、その後勝手に文字が消えます。

どうしてこんなことが起こるのでしょうか？
実はstartTag自体は非同期で動作します。この辺のアレコレは、[こちらの記事](https://scrapbox.io/violetgametips/javascript%E5%86%85%E3%81%A7%E3%82%BF%E3%82%B0%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AE%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85)がかなりわかりやすく解説してくれていますので、まだ見たことが無い方はこの記事を読んでみることをおすすめします。

このように、startTagを使う際は色々気を使わなければいけません。
このstartTagを使いこなすために、もう少し詳しく仕様を見ていきましょう。

### nextOrderって何なのさ
startTag の仕様が複雑な要因として nextOrder というメソッドの存在が挙げられます。大体のタグの完了時にティラノ内部で呼ばれているもので、これは凄く簡単に言うと、 **次のタグを読むよ！** っていうメソッドです。
たったそれだけなんですが、これが startTag の「非同期で動く」という仕様と組み合わさることでとんでもなくややこしい仕様を生み出しています。startTag関連でなにか不具合が起きた場合、大体これが原因の場合が多いです。

### nextOrder、いらないなら呼ばなきゃ良いじゃない
実はこのstartTagなんですが、 **nextOrderを呼ばない方法があります！** 
やり方も簡単で、第三引数に適当な関数を入れるだけです。

startTagの第三引数には、nextOrderの代わりに実行される関数を指定することが出来ます。
つまり、ここに何もしない関数を入れてあげるだけで、pタグが勝手に呼ばれるなどといった不都合は起きなくなります。第三引数に startTag を呼ぶ関数なんかを入れてあげると startTag の順次実行も可能になります。これを使用すれば、startTag だけを使用してTyranoScriptを組む。といったことも可能になります。

### 割り込み処理は思ったより大変
今回のケースの場合、もう一つ別の問題が発生します。
今回、クリック時に強制的に別のラベルにジャンプする。という処理を行っていますが、 当然 **元々実行されていたタグの処理が消えるわけではありません。** これも実行が終わると nextOrder が呼ばれてしまいます。
しかも、こちらはTyranoScriptで書かれているため nextOrder の実行を止めるといったことも困難になります。

そこで使えるのが[[wait_cancel]](https://tyrano.jp/tag/#wait_cancel)タグです。
元々非同期で呼ばれることを前提としているタグなこともあって、wait_cancelタグでwaitタグの動作を止めても、waitタグの方のnextOrderが呼ばれることはありません。
これとjumpタグをstartTag関数を使用して連続で実行すれば、waitタグで止まっている時に限り、正しくジャンプしてくれます。

startTag の第三引数に関数を指定すると、cut_nextorderという値がこの関数に置き換わります。 **この値は基本的に自分で置き換えない限りずっとそのままです。** 
第三引数に関数を記述する際、どこかに `TYRANO.kag.tmp.cut_nextorder = null;` という処理を入れてあげないと、以降のタグで nextOrder が正しく呼ばれず、TyranoScriptのタグ読み込みが止まってしまうので気をつけましょう。


# まとめ
今回はTyranoScriptを用いてタイトル画面の演出スキップ処理を作成し、その際、必ずといっていいほど悩まされる startTag の仕様や割り込み処理を実現するための方法をまとめてみました。
TyranoScriptで startTag の動作に悩んでいる人の力に少しでもなれたらと思います。


# 参考URL
> ゲームを作ってみTARI「オープニングなどのスクリプトでクリックでスキップさせる」 https://wgs.way2go.biz/2016/12/26/post-146/
>  ゲーム制作TIPS「javascript内でタグを実行するときの注意事項」https://scrapbox.io/violetgametips/javascript%E5%86%85%E3%81%A7%E3%82%BF%E3%82%B0%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AE%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85