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

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

ただいまの
回答率

89.13%

タイマーの作り方。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 3,566

tkmnusr

score 305

前提・実現したいこと

タイマーでコルーチンを使ってはいけないのでしょうか?
この記事の、「1. 簡単なタイマー機能にyieldやcoroutineを使用しないこと」では、
タイマーにコルーチンを使ってはいけないと書かれているようですが、その理由が書かれていない為、
使ってはいけない理由がわかりません。コルーチンを使うと何か不具合が生じるのでしょうか?

この記事の末尾の『「何秒後にこういうことをしたい」といったように、
1フレームを超えた処理でコルーチンを使用しないといけないところで自分の理解が止まっていて...』の、
「1フレームを超えた処理でコルーチンを使用しないといけないところ」の文章が理解できません。
単純に、「フレームをまたぐ処理はコルーチンを使用しないといけない」と言っているのでしょうか?

本題ですが、下記のような仕様のタイマーを作りたいと思っています。

①指定キーを押したタイミングでタイマーがスタート。
②毎回、時間を表示する。整数でなく、少なくとも小数第2位までは表示する(今回はDebug.Logを出力)。
③指定時間間隔ごとに定期処理が実行される(今回はDebug.Logを出力)。
④指定した時間経過後、タイマーを終了として、終了処理を行う(今回はDebug.Logを出力)。
⑤上記の処理をなるべく、メソッド化。指定時間間隔と終了時間を引数で渡す。

冒頭のコルーチンを使ってはいけないらしい情報があった為、
上記仕様のタイマーをUpdate()で作ってみました。
しかし、③、④の処理が、時間ピッタリに行うことができません(詳細は試したことのコードのコメント)。

上記仕様のタイマーの作り方のご教授をお願いいたします。

また、コルーチンを使って作る場合、冒頭の件に関しても教えていただきたいです。

試したこと

bool timerflag = false;
float timercount = 0;


    void Update()
    {
        if (Input.GetKey(KeyCode.T) & !(timerflag)) {
            timercount = 0;
            timerflag = true;
        }
        if (timerflag) {
            timerMethod (0.5f, 10);
        }
    }

    void timerMethod(float interval, int limit){
        timercount += Time.deltaTime;
        //毎回時間を表示する。少なくとも小数第2位までは表示する。
        Debug.Log (timercount);

        //この条件では、引っ掛からない可能性が高い。
        //また、if文内をMathf.Floorを使って、timercountを0.5xxx...から0.5に書き換えても、
        //複数回引っかかる可能性がある。例:0.5123のとき、0.5とみなされ引っかかり、
        //0.52431でも0.5とみなされ引っかかる。
        //そもそも、0.5123のとき、0.5とみなすのではなく、
        //0.5000...0という時間ピッタリのときに引っ掛けたい。
        if (timercount % interval == 0) {
            //定期実行処理。
            Debug.Log ("定期実行処理" + timercount);
        }

        //この条件では、指定時間ピッタリに終了させることはできない可能性が高い。
        //10.000...0で引っかからない。
        //Mathf.Floorを使えば、10になるが、そもそもそれでは本当の10.000...0の時間に
        //引っ掛かるわけではない。
        if (timercount == limit) {
            Debug.Log ("タイマー終了");
            timerflag = false;
        }
    }
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

■何故コルーチンを使ってはいけないか
元記事の方にもありますが、

正しく記載することを忘れてしまうことで大混乱に陥ったり、そもそも何の目的で使うか見えなくなりがち

これが理由です。
よく理解しないまま使うとコルーチンが無限増殖したり、永遠に回り続けることで負荷を上昇させます。それを防ぐ為、簡単な処理はUpdateで済ませましょう、或いは別の処理を使いましょうということです。

■時間ぴったりに何かを実行させる
厳密には不可能です。
コルーチンと元記事で紹介されているInvokeも試してみましたが、0.04秒程のラグが発生しました。
そもそもfloat自体にも誤差がありますし、0.01秒以下の単位での時間合わせは難しいものと思います。


コルーチン・Invokeを使用する場合、「コルーチンかInvokeで開始・終了フラグを管理する」「Updateで時間表示機能を作る(開始・終了フラグを監視する。終了時は端数になるので無理矢理きっちりした数字にしてしまう)」の2つを組み合わせれば出来るかと思います。

ただ、改めて考えると描画はフレームレートのタイミングでしか更新されないので、全てUpdateで行っても時間の精度で言えば大した違いは出ないように思います。
(例えば5秒後きっちりにコルーチンを止めたとしても、画面上にそれが表示されるのは「次のフレーム」なので、そこでも微細な誤差が出ます)
一応全てUpdateで処理したバージョンのスクリプトを置いておきます。

bool timerflag = false;
float timercount = 0.0f;
float totalTimerCount = 0.0f;


    void Update()
    {
        if (Input.GetKey(KeyCode.T) & !(timerflag)) {
            timercount = 0.0f;
            totalTimerCount = 0.0f;
            timerflag = true;
        }
        if (timerflag) {
            timerMethod (5.0f, 10.0f);
        }
    }

    void timerMethod(float limit, float totalLimit){
        timercount += Time.deltaTime;
        totalTimerCount += Time.deltaTime;

        //毎回時間を表示する。少なくとも小数第2位までは表示する。
        Debug.Log (timercount);
        Debug.Log (totalTimerCount);

        if (timercount >= limit) {
            //定期実行処理。
            Debug.Log ("定期実行処理" + timercount);
            timercount = 0.0f;
        }

        if (totalTimerCount >= totalLimit) {
            Debug.Log ("タイマー終了"+ totalTimerCount);
            //終了時のtotalTimerCountはきっちりした値ではないので、
            //表示する際はきっちりした値(totalLimit)を表示して、
            //さもきっちり終わったかのように見せかけるとよい
            timerflag = false;
        }

        //誤差は60fpsなら0.01666…秒、30fpsなら0.333…秒(実機動作時の現在のfpsの影響を受けます)
    }

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/10/10 17:56

    ご回答ありがとうございます。コルーチンを使ってはいけない理由、とてもよくわかりました。可能であれば、質問の仕様のタイマーのコードを、コルーチンとInvokeとで掲載お願いできないでしょうか。コルーチンやInvokeで書く場合、毎回、時間表示を行いつつ、途中で定期実行も行うという組み合わせのコードが作れなくて困っています。

    キャンセル

  • 2016/10/10 22:25

    ご回答ありがとうございます。コルーチン・Invokeを使用する場合でも、Updateを併用することで解決できることが勉強になりました。また、誤差に関しても丁寧にご教授くださりありがとうございました。

    キャンセル

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

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