DOTween完全に理解するその6 イベント編

前回：[DOTween完全に理解するその5 Sequence編](https://qiita.com/BEATnonanka/items/50cacac803f88f5074dd)


#今回解説するもの
今回はDOTweenのイベントについて解説していきたいと思います。
イベントはTweenに特定のタイミングで処理を挟むのに有効ですが、
設定や状況によっては想定と違う動作を起こすので注意が必要です。

**開発環境**
Unity:2019.4.0f1
DOTween:v1.2.335

#イベント登録API
|API|概要|
|---|---|
|OnStart|動作開始時に１度だけ呼ばれる|
|OnPlay|動作開始時に毎回呼ばれる|
|OnUpdate|動作中毎フレーム呼ばれる|
|OnPause|動作停止時に毎回呼ばれる|
|OnComplete|正方向の動作完了時に呼ばれる|
|OnStepComplete|動作完了時に呼ばれる<br>各ループの完了時にも呼ばれる|
|OnRewind|初期位置に戻った時に呼ばれる(PlayBackwardsなどで逆再生した場合も含む)|
|OnWaypointChange|DOPathで各Pathに到達した時に呼ばれる<br>Pathのインデックスが引数で|
|OnKill|Tweenが破棄される時に呼ばれる(Tweenがリサイクルされる場合も呼ばれる)|

#各イベントの特徴
## OnStart
動作開始時に１度だけ呼ばれます。
それ以降は`Restart()`や`Rewind()`を使用しても呼ばれることはありません。
また、TweenにDelayがかかっていた場合**Delayが終わった後に呼ばれます。**
(個人的にはこれが少し使いづらいかなと思う時もあります...)
`Play()`などを行わずに`Complete()`で終端まで実行した後`PlayBackwards()`などで逆再生した場合にも呼ばれます。
Sequenceの場合は先頭に`AppendInterval()`や`PrependInterval()`をした場合でも
**動作開始時に`OnStart`が呼ばれます（`OnPlay`も同様）**


## OnPlay
停止状態から動作開始した時に**毎回**呼ばれます。
`OnStart`と同様にDelayの影響を受けます。
`PlayForward()`、`PlayBackwords()`、`SmoothRewind()`など
停止中に動作を開始するAPIが呼ばれた場合にイベントが呼ばれますが、
**動作中にこれらのAPIで再生方向が変わったとしてもOnPlayは呼ばれません。**
また、すでに`OnPlay`が呼ばれた状態で`Goto(andPlay:true)`を呼んだ場合も`OnPlay`は呼ばれません。

## OnUpdate
動作中に呼ばれます。
ただあまり使うシチュエーションは無い印象です。
`OnStart`と同様に通常のTweenの場合はDelayの影響を受け、
Sequenceの場合はDelay(Interval)の影響を受けません。

## OnPause
動作停止時に呼ばれます。
AutoKill=falseの場合は動作完了時にも呼ばれる為、
`OnComplete`や`OnStepComplete`とタイミングがシビアになります（後述）
AutoKill＝trueの場合は動作完了時に`OnKill`が呼ばれ`OnPause`は呼ばれません。

## OnComplete
動作完了時に呼ばれます。
ただ、通常再生の場合にのみ呼ばれ`PlayBackwards()`などで逆再生の場合は
動作完了時であっても呼ばれません。
また、`Complete()`実行時にも呼ばれます。

## OnStepComplete
動作完了時に呼ばれ、通常再生、逆再生に関わらず呼ばれます。
`SetLoop()`で設定したループの一動作が終わる度に呼ばれます。
**`Complete()`、`Rewind()`、`Goto()`などで動作が終了した場合は呼ばれません。**

## OnRewind
Tweenが初期位置に戻った時に呼ばれます。
初期位置というのは経過時間が０秒の位置のことを指します。
`PlayBackwards()`や`SmoothRewind()`でアニメーションが終わった場合に呼ばれますが
LoopType.Restartでアニメーションがループされる場合は呼ばれません。
また、`Rewind()`実行時にも呼ばれます。

## OnWaypointChange
DOPath限定のイベントです。
DOPath生成時に設定したPathに到達した時に呼ばれます。
引数にPathのインデックスが渡されてきます。
`SetOption（）`でClosePath=trueだった場合などでIndexが変わります（後述）

## OnKill
Tweenが破棄される時に呼ばれます。
破棄される時と言ってもTweenがRecycable=trueでも呼ばれます。
再生されていない状態で`Complete()`を実行した場合にも呼ばれます。
AutoKill＝falseの場合は動作完了時に`OnPause`が呼ばれ`OnKill`は呼ばれません。

# 注意すべきイベントの挙動
## 動作完了時に呼ばれるイベント群とその優先順位
通常の再生の場合は`OnStepCoplete > OnComplete > OnKill/OnPause`の順で呼ばれます
![tween_event1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/45830341-4f55-078a-d101-fc5224af0d9d.gif)
逆再生の場合は`OnRewind > OnStepComplete > OnPause`の順で呼ばれます。
![tween_event2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/6928e7b8-e022-1698-268a-b56d31e17e75.gif)
よくあるパターンとしてスライドインしてくるUIを作った場合は
`OnComplete`に通常再生の終了処理を書いて、`OnRewind`に逆再生の終了処理を書くのが良いかなと思います。

## Sequence内のTweenのイベント
以下はイベントを設定したTweenに直接`SetLoop()`でLoopType.Yoyoを設定しています。
各Loop終了時に`OnStepComplete`が呼ばれていることが確認できます。
![tween_event3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/19b61356-f3ef-0262-e6be-da7637e34b40.gif)

以下は同じイベントを設定したTweenを**SequenceにAppendし、Sequenceに`SetLoop()`を行なっています。**
![tween_event4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/e05ab8d4-ff2d-5112-154b-d624289940c6.gif)
各Loop終了時に`OnComplete`や`OnRewind`が発生するようになっています。
このようにSequence内にあるTweenのイベントは個別に状況に合わせたイベントが呼ばれます。

## OnWaypointChangeのindex
OnWaypointChangeの引数として来るindexはDOPathの設定によって変わります。

###一般的な場合（ClosePath=false、ループなし）
|index|場所|
|---|---|
|0|初期位置|
|1|Path[0]|
|2|Path[1]（終了位置）|

![tween_event_path1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/b9c49180-5352-9e97-1397-5e3fe084183e.gif)

### ClosePath＝trueの場合
ClosePath=trueの場合、終了位置が初期位置になりますがその場合は
**Indexが0ではなくPath総数+1になります。**

|index|場所|
|---|---|
|0|初期位置|
|1|Path[0]|
|2|Path[1]|
|3|終了位置(初期位置)|
![tween_event_path2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/68bed323-4603-7757-417c-3aafc5da3b63.gif)

###ClosePath=trueでLoopする場合
ClosePath=trueで`SetLoop`を設定した場合は複雑になります
LoopType.Restartの場合はLoopなしの時と同じように
**Restartした初期位置はIndex=0で終了位置はPath数+1になります。**
![tween_ecent_path3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/e07e3335-3297-7bd1-ab97-dfd603d15be0.gif)
LoopType.Yoyoの場合は
**逆再生する直前のIndexはPath数+1となり、逆再生中は各PathのOnWayPointChangeは２回ずつ呼ばれる**
という挙動になります。
![tween_event_path4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203331/85ee4e05-7863-0c33-1a94-20191f1a546b.gif)
この**２回ずつ呼ばれる**という挙動が結構曲者で、ドキュメントにも特に何も書いてないので
もしかしてバグなんじゃ...なんて思います。
同じIndexのコールバックは無視するような対応を行い、`OnStepComplete`時に無視フラグをリセットするような実装が必要になるかと思います。

#まとめ
イベントはエフェクトを表示したり、入力の受付状態の切り替えなど
DOTweenとは別の仕組みと連携するために使用されることが多いと思います。
特に`OnStepComplete`や`OnPause`、`OnRewind`などは使うシチュエーションはよく使うことになるので
DOTweenを応用して使う場合は身につけておくべき要素ですね。

余談ですがこの記事を書いていた7/29に
**DOTweenの新バージョン(1.2.420)がAssetStoreで公開されたようです。**
async/await対応
Sequence向けのSetDelayの拡張
ゲームでよく使いそうなカウントを使用するDOCounter
１文字ずつにアニメーションを行えるDOTweenTMPAnimator
などの良さそうな変更がかかっているようです。
特にDOTweenTMPAnimatorがかなり強力そうなのでこれを機にPRO版を購入しようと思います。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Sent <a href="https://twitter.com/hashtag/DOTween?src=hash&amp;ref_src=twsrc%5Etfw">#DOTween</a> + Pro update to the Asset Store, with lots of new stuff (and some bugfixes) like TextMesh Pro per-character animation/transforms (Pro only): <a href="https://t.co/sI2qJVllFC">https://t.co/sI2qJVllFC</a><br><br>(no DOTween Timeline yet but that&#39;s the next step for real :P) <a href="https://t.co/fyDdq5MSGo">pic.twitter.com/fyDdq5MSGo</a></p>&mdash; 💬Daniele Giardini (@demigiant) <a href="https://twitter.com/demigiant/status/1286650647207698433?ref_src=twsrc%5Etfw">July 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>