Unityでプログレスバー(progressbar)を作るときは、uGUIのImageを使うことが多いと思います。Imageの設定をFillに設定することで簡単にプログレスバーのようなものを作ることができます。
スクリプトからプログレスバーを更新する場合は、次のようにImageオブジェクトのfillAmountの項目を0.0〜1.0の間で設定することで表示の割合を変えることができます。
GetComponent<Image>().fillAmount = 0.5;
やることはこれだけなのですが、重い処理に合わせてプログレスバーを進めようとするとなかなかうまく行きません。次の3つが失敗例。最後に成功例を載せています。
誤り実装① コルーチンを使う
IEnumerator Progress() { for(int i = 0; i <= 100; i++){ GetComponent<Image>().fillAmount = i / 100.0f; yield return null; } } void HeavyProcess() { StartCoroutine(Progress()); //以下重たい処理 // ・・・・ }
重たい処理をはじめる前にコルーチンを生成し、そちらでなんちゃってアニメーションしようという案です。
Unityのコルーチンはスレッドっぽくてスレッドではない(並列動作しない)ため、この実装は正常に動きません。重たい処理に引きずられて、Progressコルーチンは重たい処理が終わったあとにようやく実行されます。
誤り実装② 処理の間に挟み込む
void HeavyProcess() { //重たい処理 //・・・・ GetComponent<Image>().fillAmount = 0.2f; //重たい処理 //・・・・ GetComponent<Image>().fillAmount = 0.4f; //超重たい処理 //・・・・ GetComponent<Image>().fillAmount = 0.9f; //重たい処理 //・・・・ GetComponent<Image>().fillAmount = 1.0f; }
コルーチンがだめなら、直接重たい処理の中にプログレスバーの更新プログラムを挟み込めばいいじゃん案です。
なかなか良さそうですが、残念ながらこれも正常には動作せず、いきなりプログレスバーの進捗が100%になってしまいます。
これは、fillAmountに値を代入したあとも重たい処理が続くため、uGUIの画面更新が行われないのが原因です。当たり前といえば当たり前か・・・・
誤り実装③ Threadを使う
private Thread _thread; IEnumerator Progress() { for(int i = 0; i <= 100; i++){ GetComponent<Image>().fillAmount = i / 100.0f; Thread.Sleep(10); } } void HeavyProcess() { _thread = new Thread(Progress()); _thread.Start(); //以下重たい処理 // ・・・・ }
コルーチンがだめなら非同期スレッドだ、案です。そもそもUnityの設計思想がシングルスレッドなので、Threadを使う時点でイケてないのですが・・・Threadの中ではuGUIの更新ができずにエラーが出ます。
ちゃんとプログレスバーが伸びる実装
IEnumerator HeavyProcess() { //重たい処理 //・・・・ GetComponent<Image>().fillAmount = 0.2f; yield return null; //重たい処理 //・・・・ GetComponent<Image>().fillAmount = 0.4f; yield return null; //超重たい処理 //・・・・ GetComponent<Image>().fillAmount = 0.9f; yield return null; //重たい処理 //・・・・ GetComponent<Image>().fillAmount = 1.0f; } void Hoge() { StartCoroutine(HeavyProcess()); }
さて、いよいよ正しく動く実装です。結局何をしたかというと、コルーチンと挟み込みの折衷案です。重たい関数自体をコルーチンにして、プログレスバーをすすめるたびに、一旦処理をメインスレッドに返します。これにより描画処理が実行され、プログレスバーも進む、という仕組みです。
これにて一件落着!?