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

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

ただいまの
回答率

90.47%

  • C#

    9246questions

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

  • Unity

    5661questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

UNITY ロード画面作成

解決済

回答 2

投稿

  • 評価
  • クリップ 3
  • VIEW 748

akikan101010

score 1

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のようなゲームでキャラが時間経過でポコポコ増えていくゲーム
(そのため、どんどんとセーブデータが大きくなっていく)

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

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

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

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

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

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class LoadingScene : MonoBehaviour
{
    IEnumerator Start()
    {
        var asyncOperation = SceneManager.LoadSceneAsync("SceneName");
        asyncOperation.allowSceneActivation = false;
        //allowSceneActivationをfalseにした場合、0.9fが最終です。trueにするまで1fまで進行しません。
        while (asyncOperation.progress < 0.9f)
        {
            //現在のロード進捗が取得できます。
            //asyncOperation.progress;

            yield return null;
        }
        asyncOperation.allowSceneActivation = true;
    }
}


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

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

[追記] 上記で解消されたら

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/26 21:04

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

    キャンセル

  • 2019/03/26 21:21 編集

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

    キャンセル

  • 2019/03/26 21:27 編集

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

    キャンセル

  • 2019/03/26 21:33

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

    キャンセル

  • 2019/03/28 07:43

    ご回答していただいた内容をもとにロードシーンを作成してデータロードするというやり方でうまくいきました。

    シーン遷移はあまり経験がなかったので、変数をSTATICにしなければシーン間で引き継げないということに
    軽く引っかかりましたがうまくできました。

    あと、この内容をもとにいろいろ調べていたら
    理想的なことをしているサイトを見つけましたので、
    これを目指しながら開発を進めていきたいと思います。
    https://qiita.com/toRisouP/items/1713d2addf6f5dc9f9b8

    キャンセル

  • 2019/03/28 07:53

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

    ありがとうございました。

    キャンセル

  • 2019/03/28 18: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やマルチシーンの生成順を解決するだけでもかなり便利です。
    本格的に使うには「依存性逆転の原則」や「インターフェイス分離」など設計を学ぶ必要があります。

    キャンセル

+1

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 21:11

    そういう仕組みになっているのですね、教えてくださってありがとうございます。

    ところで、記述夕に合った「何件毎がいいかはデータ処理時間」とあったのですが、こちらを取得するいい方法はありませんか?
    Time.realtimeSinceStartup
    System.Diagnostics.Stopwatch() の 「.ElapsedMilliseconds」
    では、10万回時に0.008秒の遅延が発生したので
    ほかの時間の取得方法があれば教えてもらえませんか?

    「実験したがうまくいかなかったソース」
    が改良できそうなので、ぜひお願いします!!

    キャンセル

  • 2019/03/27 15:47

    時間計測も間引きすればいいかと。
    例えば100回に1回Time.realtimeSinceStartupを取得、前回からの経過時間を計算。
    経過時間が規定時間より長かったらyieldを入れる。(短かったら何もしない)

    yieldは30fps(0.033秒)毎に1回もあれば十分、何なら1秒に1回でもそれはそれでいいし、時間計測が多少ずれていたところで大した問題にはならないでしょう。
    なので実測しつつ適度な回数を決め打ちしちゃってもいいんじゃないかと思います。

    キャンセル

  • 2019/03/28 07:52

    なるほど、
    「yieldは30fps(0.033秒)」
    はすごく参考になりました、ありがとうございます。

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

    キャンセル

  • 2019/03/29 09:20

    質問は解決されたようで何よりですが一部訂正を。
    「yieldは30fps(0.033秒)」ではないです。

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

    キャンセル

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

  • ただいまの回答率 90.47%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • C#

    9246questions

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

  • Unity

    5661questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。