質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

8313閲覧

UNITY ロード画面作成

akikan101010

総合スコア13

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

4クリップ

投稿2019/03/26 08:53

UnityとC#を使用してゲームを作成しています。
使用してから6か月と初心者ですが、自力で調べても問題解決ができなかっため、
質問させていただきます。

セーブデータをロードした際に処理が重く画面が停止してしまうため、
なんとかして停止しないようにしたい。

〇理想

NOWLOADING画面を表示し、
WINDOWSのビジー状態のように「丸い円がぐるぐる」と回り処理終了まで待つ。
(プレイヤーに現在ゲームがフリーズしていないことを見せておきたい)
と考えております。

現状の問題

重たい処理を連続してさせてしまうと、画面が停止してしまう
「丸い円がぐるぐる」が完全停止してしまう
(許容範囲としてはぽつぽつ止まる程度なら、OK)

現状のソース

記述例 <処理説明> 関数などの呼び出しによる処理 今回変更する予定のないブラックボックス部 public static MonoBehaviour main_mono;//IEnumerator内でコルーチンを呼び出すための宣言 public int yomikomi_end_flag = 0;//読み込み終了受け取り void Start() { //処理の開始 //staticでもコルーチンを使うよう g.main_mono = this; //ゲーム処理のメイン部の起動 StartCoroutine(main()); } public IEnumerator main() { //重たい処理の並列実行 yomikomi_end_flag = 0; main_mono.StartCoroutine(load_data_()); main_mono.StartCoroutine( <NOWLOADINGの動く画像表示等の処理>); //処理待ちループ while (true) { if (yomikomi_end_flag > 0) { //読み込み終了したため、ループからの脱出 break; } yield return null; } } public IEnumerator load_data_() { ///////// ※※2 ///////// //セーブファイル読み込み string save_data = <セーブデータ読み込み>; //読み込んだデータの解析 //読み込んだデータの解析 for (int i = 0; i < <データ数行>; i++) { for (int j = 0; j < <データ数列>; j++) { //ココが何度も実行させると重たい 単体ではそこまで重たくない ///////// ※1 ///////// <個別のデータ処理>(save_data, i, j); } } } //読み込み終了の連絡 yomikomi_end_flag = 1; ///////// ※※3 ///////// yield return null; }

現状の解決方法

///////// ※1 /////////

yield return null;
を入れると「丸い円がぐるぐる」がずっと回り続ける
だが、これではデータ数が多くなると読み込み時間が多大になる
(予定ではデータ数はプレイに応じて多くなっていく)

初心者の考察

yield return null;
で「丸い円がぐるぐる」になるため
1フレーム中の処理が重たくなりすぎると、画面が止まってしまうのでは?

希望したいこと

///////// ※1 /////////

処理に時間がかかっているか確認をして、
時間がかかっているときはyield return null;を実行させて、1フレーム中の処理が重たくなっている状態を開放する

実験したがうまくいかなかったソース

逆に重たくなってしまったため、断念しました
Time.realtimeSinceStartup を何度も実行するほうが重すぎました

public static Dictionary<string, float> check_time = new Dictionary<string, float>(); //1秒以上処理していたらウェイト 用のキャッシュ位置を取得 public static string get_chach_iti(string back_time_iti = "") { //存在チェック float times = keisan_time(); if (back_time_iti == "") { back_time_iti = "" + times; } //名前重複チェック while (true) { if (check_time.ContainsKey(back_time_iti)) { int ran = Random.Range(0, 20); back_time_iti = back_time_iti + "_" + ran; } else { break; } } check_time.Add(back_time_iti, times); return back_time_iti; } //前回処理より時間がたっていたらウェイトさせる信号を送る true でウェイトさせる public static bool chk_wait_over(string back_time_iti,float over_time = 100) { //over_time = over_time / 1000; float now_sec =Time.realtimeSinceStartup*1000; float chk_time_over = 0; float back_time = 0; back_time = check_time[back_time_iti]; chk_time_over = back_time + over_time; //d.b("タイムチェック" + now_sec + " : " + back_time + ")"); //大きいまたは少なすぎるとウェイト if (now_sec > chk_time_over) { //d.b("ナガスギウェイト" + now_sec + " : " + back_time + "(" + chk_time_over + ")"); check_time[back_time_iti] = now_sec; return true; } else if (now_sec < back_time) { //d.b("短すぎウェイト" + now_sec + " : " + back_time + "(" + chk_time_over + ")"); check_time[back_time_iti] = now_sec; return true; } return false; } //終了後のキャッシュの解放 public static void del_check_time(string back_time_iti) { check_time.Remove(back_time_iti); } ///////// ※※1 ///////// を //長過ぎウェイト用キャッシュ取得 string wait_iti = wait.get_chach_iti(); ///////// ※※2 ///////// を //長過ぎウェイトチェック if (wait.chk_wait_over(wait_iti)) { yield return wait.f(1); } ///////// ※※3 ///////// を //キャッシュ解放 wait.del_check_time(wait_iti); に置換して使用する

現在作成中のゲーム

光栄様の三国志7のようなゲームでキャラが時間経過でポコポコ増えていくゲーム
(そのため、どんどんとセーブデータが大きくなっていく)

なにとぞ、ご教授の方をよろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

IShikawanさんの方法で動くかもですが一応理屈の説明など。

yield return null;は**「1フレーム分、画面描画されるまで待つ」**と大体同じ意味です。
(「1フレーム中の処理が重たくなっている状態を開放する」ではありません)

本来であればスクリプトの処理が完了するまで描画は実行されません。
例えばループが1万件あったら、1万件の処理が終わるまで画面に変化は起こりません。
なのでアニメーションも動かないように見えます。
(パラパラマンガのどこかの1コマで止まっていて次のコマに進まない状態だと思ってください)

で、ループの中にyield return null;を置くと、そのタイミングで描画を待ちます。
1件処理→描画→1件処理→描画……となるので、アニメーションも表示されます。

ただし「これではデータ数が多くなると読み込み時間が多大になる」、まさにその通りです。
ループ1件毎に1フレーム待っていると1万フレーム掛かることになり、100fpsだったとしても100秒掛かります。
じゃあどうすればいいか?

1件毎に1フレームではなく、10件毎に1フレームとか、100件毎に1フレームにすればいいです。

幸い「ぽつぽつ止まる程度ならOK」とのことなので、これでもそれっぽく見えると思います。
(何件毎がいいかはデータ処理時間とか全体件数とかによって決めてください)

ご参考までに。

投稿2019/03/26 10:43

sakura_hana

総合スコア11425

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

akikan101010

2019/03/26 12:11

そういう仕組みになっているのですね、教えてくださってありがとうございます。 ところで、記述夕に合った「何件毎がいいかはデータ処理時間」とあったのですが、こちらを取得するいい方法はありませんか? Time.realtimeSinceStartup System.Diagnostics.Stopwatch() の 「.ElapsedMilliseconds」 では、10万回時に0.008秒の遅延が発生したので ほかの時間の取得方法があれば教えてもらえませんか? 「実験したがうまくいかなかったソース」 が改良できそうなので、ぜひお願いします!!
sakura_hana

2019/03/27 06:47

時間計測も間引きすればいいかと。 例えば100回に1回Time.realtimeSinceStartupを取得、前回からの経過時間を計算。 経過時間が規定時間より長かったらyieldを入れる。(短かったら何もしない) yieldは30fps(0.033秒)毎に1回もあれば十分、何なら1秒に1回でもそれはそれでいいし、時間計測が多少ずれていたところで大した問題にはならないでしょう。 なので実測しつつ適度な回数を決め打ちしちゃってもいいんじゃないかと思います。
akikan101010

2019/03/27 22:52

なるほど、 「yieldは30fps(0.033秒)」 はすごく参考になりました、ありがとうございます。 これをもとに実行する際は1フレーム毎に現在のFPSを取得しておく並列関数を置いておき、 それをもとに「Time.realtimeSinceStartup」取得するための「100回に1回」をFPSから計算して、 「Time.realtimeSinceStartup」を取得する回数をしぼることでより スムーズに「yield」を入れられるようになりました。
sakura_hana

2019/03/29 00:20

質問は解決されたようで何よりですが一部訂正を。 「yieldは30fps(0.033秒)」ではないです。 yield return null;は「1フレーム分、画面描画されるまで待つ」であり、 「1フレームに掛かる時間はUnityが現在の負荷状況を鑑みて勝手に変える」という仕様です。 (100fpsかもしれないし49fpsかもしれないし21fpsかもしれない。その時々によってコロコロ変わる) 人間の目でアニメーションと認識するのは24〜30fpsもあれば充分(60fpsあると滑らかだなーと感じますがローディングアニメでそこまでは要らないでしょう)なので便宜上30fpsを目安にと書きました。もっとカクカクしていいのなら更に減らしてもいいと思います。この辺りはお好みでどうぞ。
guest

0

ベストアンサー

回答させていただきます。
ロードシーンを挟むことで他に何もせず改善できる可能性があります。

シーン遷移は下記のような流れを想定しています。
アプリ起動 -> ロードシーン -> ゲームシーン

下記のコードはAsyncOperation .allowSceneActivationを利用し、シーンがロード完了までアクティブ化を遅延させるロードシーンの最小構成です。ローディングアニメーションやプログレスバーなどを設置しても、ほぼカクつきなどありません。

※LoadSceneAsyncを有効にするにはBuild Settings ウインドウのScene In Buildにシーンを設定してください。設定していない場合、エラーがでます。

C#

1using System.Collections; 2using UnityEngine; 3using UnityEngine.SceneManagement; 4 5public class LoadingScene : MonoBehaviour 6{ 7 IEnumerator Start() 8 { 9 var asyncOperation = SceneManager.LoadSceneAsync("SceneName"); 10 asyncOperation.allowSceneActivation = false; 11 //allowSceneActivationをfalseにした場合、0.9fが最終です。trueにするまで1fまで進行しません。 12 while (asyncOperation.progress < 0.9f) 13 { 14 //現在のロード進捗が取得できます。 15 //asyncOperation.progress; 16 17 yield return null; 18 } 19 asyncOperation.allowSceneActivation = true; 20 } 21}

※[追記] asyncOperation.progressはEditor上では一瞬で完了してしまうので実機でご確認ください。

[参考] https://docs.unity3d.com/ja/current/ScriptReference/AsyncOperation-allowSceneActivation.html

#[追記] 上記で解消されたら
ロードシーンからスタートすることで、重い処理をシーンがアクティブになる前に詰め込めるので初回に用意する必要があるものはCoroutineを使わず実装すると良いと思います。Coroutineはフレームをまたいで処理されるので重い処理が完了しないままゲームシーンが表示されてしまいます。

投稿2019/03/26 09:45

編集2019/03/26 12:34
IShix

総合スコア1724

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

akikan101010

2019/03/26 12:04

なるほど、シーン移動でロードシーンを持ってきて、データロードするという手法ですね。 これから試してみたいと思います、ありがとうございます。
IShix

2019/03/26 12:22 編集

了解です。この方法で解決できれば一番楽です。 もし解決(許容)できなければセーブの保存形式を見直す必要があるかもしれません。
IShix

2019/03/26 12:31 編集

実装にもよりますが、セーブデータの用意が完了しないままゲームがスタートするのは、何処かで破綻する可能性があり危険だと思うので、想定するデータを高速に準備する方法を考える必要があるかもしれません。
IShix

2019/03/26 12:33

すみません。追記し忘れておりました。データのロードと関係ありませんが、asyncOperation.progressはEditor上では一瞬で完了してしまうので確認する際は実機でご確認ください。
akikan101010

2019/03/27 22:43

ご回答していただいた内容をもとにロードシーンを作成してデータロードするというやり方でうまくいきました。 シーン遷移はあまり経験がなかったので、変数をSTATICにしなければシーン間で引き継げないということに 軽く引っかかりましたがうまくできました。 あと、この内容をもとにいろいろ調べていたら 理想的なことをしているサイトを見つけましたので、 これを目指しながら開発を進めていきたいと思います。 https://qiita.com/toRisouP/items/1713d2addf6f5dc9f9b8
akikan101010

2019/03/27 22:53

この度はご回答をしていただき、誠にありがとうございます。 上手くロード画面が作成できたので、こちらのご回答をベストアンサーとして質問を締め切りたいと思います。 ありがとうございました。
IShix

2019/03/28 09:07

解決できてよかったです。上記リンク見ました。 toRisouPさんはかなり優秀なエンジニアなので参考にされると良いと思いますが、使用されているプラグインのUniRx、Zenjectの使用はしっかり検討して導入されると良いと思います。 ▼ UniRx ゲームでは、00秒後に00するといった記述が多いと思いますが、それを簡単に実装できます。イベントの上位互換でUnityが苦手な別スレッドでの処理も簡単に実装できます。便利なので習得をオススメしますが、習得コストが高めです。すぐUniRxは学ばず下記のような流れで学ぶと良いと思います。 1. LINQを学ぶ( ※MySQLの構文を書いたことあれば必要ないです ) 2. UniRxの概念を学ぶ 3. UniRxの記述方法を学ぶ 4. UniRxのHot / Cold、外部への公開を学ぶ ▼ Zenject DI(Dependency Injection)をUnityで扱える代表的なプラグインです。 ZenjectBindingやマルチシーンの生成順を解決するだけでもかなり便利です。 本格的に使うには「依存性逆転の原則」や「インターフェイス分離」など設計を学ぶ必要があります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問