非Active時にTransformをいじることの罠
長いこと謎の症状に見舞われていた。
DOTweenで拡縮のアニメさせていた物をGameObject.SetActive(false)で消した後、
GameObject.SetActive(true)で復活させると、おかしな大きさになるのだ。
復活させた時はアニメはつけていない。ただ静止画として出しただけだ。
しかも、大きさは毎回同じなわけではない。微妙に大きかったり、
微妙に小さかったりする。
さらに、明らかに画面上では大きさが変にも関わらず、
Inspectorで見ると値は正しい。例えばどう見てもスケールが1.3くらい
ある絵なのに、1だったりする。
そこで、試しにInspectorで1.0001とかを入れてみると、
大きさが正しくなる。1を入れてもおかしなままで変化はない。
Inspectorの表示がおかしいのか?と思い、スクリプトで
スケールの現在値をDebug.Logしてみるが、やはり1だ。正しい。
実害がさほどなかったので「謎現象」として放っていたのだが、
今日たまたま著しく困るケースがあったので調べてみて、
ようやく原因と対処がわかった。
Transformの何かをいじると、普通は即時反映される。
例えばlocalScaleをいじると、そこにImageがついていたり、
その子孫にImageがついていたりすれば、その大きさはそのフレームに変更される。
子孫の位置や拡大率の変更計算が行われ、
また、uGUIのImage等があれば頂点を再計算する。
しかし、SetActive(false)されたgameObjectの下にあるTransformをいじった場合、
これらの計算が行われないようだ。
見えないのでわからないが、子孫要素の位置や拡大率は変化せず、
頂点もそのままにされる。
とはいえ、SetActive(true)された時にこれらの計算が行われて
正しい絵になってくれれば問題ないわけで、Activeでない時に計算を省くことは
なんら問題ではない。厄介なのは、これにもう一つのおせっかいが加わるからだ。
どうやらTransformの要素に同じ値を入れた時にはスルーする、
という実装になっているようで、これがマズいのである。
非Active状態で何か値を入れ、Activeにしてから同じ値を入れると、
非Activeで入れた値に関する計算は行われず元の絵のままなのに、
Activeにしてから入れた値がスルーされるので元の絵のまま、
という困った状況になる。
今回の場合、推測ではあるが例えば以下のような動作と思われる。
- DOTweenがスケールを1.3にする。
- SetActive(false)で消す。
- 次に備えてスケールを1にしておくが、Activeでないので諸々の計算が省略される。
- しばらくしてSetActive(true)して、絵を初期化すべくスケールを1にする。だが、「すでに1が入っている」ので、計算がスルーされる。
- 結果、スケール1.3の絵が出る。
今のところこう解釈すると矛盾がない。
実際「一旦スケール2を入れて、次の行で1を入れる」ことで回避できた。
おそらく諸々の計算が2回走って重くなるので、
「SetActiveする前に1を入れる」が最良なのだろうが、
コードの都合上面倒くさいので今回は「一旦2を入れる」で行く。
(SetActiveするgameObjectがアニメするgameObjectのかなり上流にあるので)。
毎度大きさが違ったのも、Activeだった最後のフレームにおけるDoTweenの
値がdeltaTimeの具合によって異なるからで、説明がつく。
Unityのバージョンは2017.1.2p3。5.6系の時代にもこの症状は出ていたし、おそらくはさらに前からだろう。
こんな根幹の挙動が今更バグるとも思えないので、たぶん仕様なのだろう。
辛い。