ひらしょー

平山尚が技術のことを書く場所。Unityが多そう。

非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系の時代にもこの症状は出ていたし、おそらくはさらに前からだろう。

 

こんな根幹の挙動が今更バグるとも思えないので、たぶん仕様なのだろう。

辛い。