uGUIでRectTransfromが確定したときに何かしたい…みたいな話
ざっくりまとめ。
- uGUI で複雑(込み入った)な UI を組んだときにサイズとか確定するの遅い
Awake()
内ではもちろん未確定Start()
でも未だ未確定(単純なものならここで決まっていることもある)
OnRectTransformDimensionsChange
を使うと良い- 読んで字のごとく。。変更されるたびに発火します
です。
環境
- Unity v2018.2.17
やりたいこと
uGUI で UI 組んでて... ロジック側でその位置座標やサイズなどを使いながらなにかしたいときってありませんか?
width の何割かでなにかをするとか...。座標をみながら、何かを調整するとか...。
問題
ところが、複雑な UI を組んでいると、RectTransfrom
が確定されるのが遅いのです。
Vertical Layout Group
とか Horizontal Layout Group
とか使っていると...
たとえば下記のような構成
Panel に Vertical Layout Group
コンポーネントがはってあって
Row に Horizontal Layout Group
コンポーネントがはってあります。
これの Left の RectTransfrom が確定されるのはいつでしょうか..?
ということでこんなの書いてみました
public class LeftPanel
{
[SerializeField] private RectTransfrom rectTransform;
void Awake()
{
Debug.Log("Awake:" + rectTransform.rect.ToString());
}
void Start()
{
Debug.Log("Start:" + rectTransform.rect.ToString());
Invoke("SomethingMethod", 0.1f);
}
void SomethingMethod()
{
Debug.Log("SomethingMethod:" + rectTransform.rect);
}
}
実行してみると...
Start()
の時点でも決まっていません!
(まぁ Invoke の記述があったら気づくでしょうけど(笑))
0.1f だけ遅延させてやっているけど、まぁ1フレーム後でも良いかもしれないし、もしかしたら 0.2f 必要かもしれない...よくわからん 🤔
みたいなのが正直なところ...
0.1f 後にやるというのを我慢したとしても、ソレ以降に何かしらの要因でまた RectTransfrom が変わったときには対応できません... 😢
OnRectTransformDimensionsChange
というのがあります
RectTransform が変わるたびに発火します!
ってことでこれを使えばやりたいことが実現できそう。
が、MonoBehaviour
ではなく UIBehaviour
なので、OnCollisionEnter()
のようにすぐには使えません。
ってことでサブクラスをつくってみた
OnRectTransformDimensionsChange
を使うためにサブラスを作ってみました。
using Handler = System.Action;
public class DimensionsChangedNotification: UnityEngine.EventSystems.UIBehaviour, IHandler
{
private Handler handlers;
override protected void OnRectTransformDimensionsChange()
{
if (handlers == null) {
return;
}
handlers.Invoke();
}
#region "IHandler"
public void AddHandler(Handler handler)
{
handlers += handler;
}
public void RemoveHander(Handler handler)
{
handler -= handler;
}
#endregion
}
public interface IHandler
{
void AddHandler(Handler handler);
void RemoveHander(Handler handler);
}
こういう感じのものを。
で、件の Left のオブジェクトにこの DimensionsChangedNotification
を加えてはっつけておくと
それで、 LeftPanel は以下の様に変更
public class LeftPanel
{
void Awake()
{
GetComponent<IHandler>().AddHandler(OnDimensionsChanged);
}
void OnDestroy()
{
GetComponent<IHandler>().RemoveHander(OnDimensionsChanged);
}
void OnDimensionsChanged()
{
Debug.Log("Changed:" + rectTransform.rect);
}
}
これで、実行してみると...
ちゃんと確定されるまで自動で呼び続けられます。
(4ステップもあるんだ(笑))
これで、柔軟な UI 組んでても大丈夫ですね! 💪
ソレ、UniRx でできるよ?
なのです 😇
void Awake()
{
var trigger = AddComponent<ObservableRectTransformTrigger>();
trigger.OnRectTransformDimensionsChangeAsObservable().Subscribe(
onNext: (_) => {
// Changed!!
}
).AddTo(gameObject);
}
最高かよ!
補足
Unity の PlayerSettings で Scripting Runtime Version を .NET 4.x Equivalent を有効にしておくと
DimensionsChangedNotification
にある
if (handlers == null) {
return;
}
handlers.Invoke();
という記述を
handlers?.Invoke();
と書き換えられます!
ほかにも 4.x にしておくといろいろ便利なものが使えるようになるので、環境的に許されるなら変えておくことをおすすめ!