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

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

ただいまの
回答率

88.59%

「Updateと各ボタンのコールバック関数」のみで、入力と描画を完結させる方法

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,239

tsubozaemon

score 9

 前提・実現したいこと

「Updateと各ボタンのコールバック」しか実装していないコードで、
「一部処理をFixedUpdateに切り出す」等をせず、「早すぎない描画」と
「ある程度正確な入力受け取り」を実現する方法

 発生している問題・エラーメッセージ

開発当初、Unityの「Updateの間隔」や「入力受け取り」について知らないままに
実装を始めてしまいました。
結果、現在の実装は各シーンごとに
・start()完了後に実行されるのは、updateと各ボタンのコールバック関数のみ
・updateでは「if(Input.GetMouseButtonDown(0))後に画面クリック時の処理」と
「毎フレームの計算処理(移動、カウンタ増加など)」を行っている
・コールバック関数では、各ボタンに対応した処理を書いている
と、updateに「入力受け取り」「定周期で行うべき計算」「描画」が混在してしまっています。

開発マシンでは不思議と「ちょうどいい速度」だったので、気にならなかったのですが
かなり形になってから別マシンで動かす機会があり、「Updateごとに移動などを行う」と
基本的に早すぎる、ということが(今更)判明しました。

「今からUpdateを修正し、入力以外の処理をFixedUpdateに切り出す」のも考えたのですが、
Update内は「入力受け取り」「定周期で行うべき計算」「描画」が相当強く相互に結びついてしまっており、下手に切り出すとバグの原因になりそうで、それは最終手段にしたいです。

下記のソースが、「とりあえずの現実解」として、今動かしている実装なのですが
何かアイデアや、逆に「〇〇××しないと、この実装は致命的な問題がある」など
ご意見があったら教えてください。

 該当のソースコード

// 現在のコードであり、当初の実装ではありません

    void Start () {
        button[i].GetComponent<Button>().onClick.AddListener(() => OnButtonClick(index));
    }

    void Update() {
        if (Input.GetMouseButtonDown(0))
        {
            // 今回「IsPassedInterval == false」になってUpdateを抜ける場合でも
            // 「処理のインターバル中に画面はクリックした」という事実は保管しておかないと
            // クリックが無かったことにされる可能性がある
            isGamenClick = true;//isGamenClickはグローバル静的変数
        }
        if (!IsPassedInterval()) return;
        if (cantSyoriCounter > 0) cantSyoriCounter--;//cantSyoriCounterはグローバル静的変数
        if (isGamenClick)//当初はif(Input.GetMouseButtonDown(0))
        {
            isGamenClick = false;//こうしないと次回Update時もコールされてしまう
            ClickWindow();//画面クリック時の処理
        }
        // 以降、毎フレームごとの描画処理など
        //()攻撃演出中
        if (gameState == GAME_STATE_ATTACK) {

            if (ensyutsuCounter <= 15) {
                printDamage();//ダメージ量を表示
            }
            if (ensyutsuCounter == 10) {
                targetHp -= damage;//ターゲットのHPを今回のダメージ分だけ減らす
            }
            ensyutsuCounter--;
            if (ensyutsuCounter == 0) {
                // 攻撃演出状態終了。初期状態に戻る
                gameState == GAME_STATE_FIRST;
            }
        }
    }

    // 一定周期ごとにupdateの実質処理が行われるようにチェック
    public static Boolean IsPassedInterval()
    {
        tempInterval += Time.deltaTime;//tempIntervalはグローバル静的変数
        if (tempInterval > UPDATE_INTERVAL)
        {
            tempInterval = 0;
            return true;
        }
        return false;
    }

    void OnButtonClick(int index){
        // ボタンを押された時の、カウンタの増加やゲーム内状態変数の変更など
        if (cantSyoriCounter != 0)
        {
            return;
        }
        //ボタン押したときの処理()
        if (gameState == GAME_STATE_ATTACKCHECK) {//攻撃決定するかの確認状態
            gameState = GAME_STATE_ATTACK;//攻撃の演出に入る
            ensyutsuCounter = 20;//20フレーム間、攻撃演出
            cantSyoriCounter = 30;//連打で一気に進む防止のため
        }
    }

 試したこと

まずは、上記のソースでの「IsPassedInterval」処理を追加しました。
が、それだけだと、「スキップされるupdate()」で拾った画面クリックが
いざ「スキップしないupdate()」が来た時に、無視されてしまうことに気づきました。
よってisGamenClickフラグを追加しました。

ボタンについては、cantSyoriCounterで「どんなに連打しても、
スキップしないupdateが30回呼ばれるまでは無視する」ようにしました。

思想としては、実装された処理の違いから
画面クリックは「クリックしたことを落とさないこと」重視、
ボタンクリックは「連打で一気に進まないこと」重視(cantSyoriCounterに引っかかって
無視されることがあるのは、大した問題ではないと判断)です。

 補足情報(FW/ツールのバージョンなど)

Collision系の処理は使用していないので、気にしなくてよいです。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • sakura_hana

    2018/11/20 10:47

    現状の移動のスクリプトをざっくりとでもいいので記載頂けますか?(多分そっちにTime.deltaTime入っていないだけだと思いますが)/また、やりたいことは「入力受け取りは毎フレーム行うが、一度入力があった後一定時間は受け取りたくない(連打は無効)」「計算は現実時間単位の一定周期(3秒毎とか)で行いたい」「フレームレートが変わっても移動は等速にしたい」でいいですか?(「描画」が指す内容によってはちょっと変わりそうですが)

    キャンセル

  • tsubozaemon

    2018/11/20 22:35 編集

    「移動」という単語を使ってしまいましたが、マス目RPGですのでUpdate内での移動は「実質演出」です(目標マスへの移動自体は移動入力の瞬間で完了し、update内では実際には座標値の操作は行っていない。演出カウンタに応じた見た目の移動だけです)。それは大して重要な処理ではないので、かわりに、OnClickとUpdateの最重要処理である「攻撃コマンドハンドリング」の内容をソースに書いてみました。「やりたいこと」については、むしろ、「〇〇でなければいい」という感じです。具体的には「Updateでの演出の進行(上記でのensyutuCounterの減少)が、リアル時間で早すぎなければいい(演出が一瞬で終わらないように)」「updateで特定の1フレームのみ実行すべき処理(上記例でのターゲットのHP減少など)が、飛ばされたり2回呼ばれるのは絶対にNG」「画面タッチ(Input.GetMouseButtonDown)については、タッチしたのにいつまでも無反応という事態が起きなければいい」「ボタンについては、連打してしまった時に、画面更新が追いつく前に一気に処理が進まなければいい(連打が必要なゲームではないので、連打が無効化されるのは問題ない)」という3点が条件です。ご質問の回答としては、連打は無効→必須、計算は一定周期→そこまで厳密でなくていいが、早すぎて演出が一瞬で終わってしまうのは困る、フレームレートが変わっても移動(に限らず演出全般)は等速→別に必要ではない、ということになります。※フレームレートが期待より「遅い」場合、すべてが均等にもたつく感じになりますが、それは仕方ないと考えています。

    キャンセル

回答 1

checkベストアンサー

+2

ご存知かもしれませんがおさらいから。
Updateは1フレームに1回呼ばれ、フレームレート(1秒間が何フレームか)はゲームの負荷により変わります。
つまりUpdateは、ある時は1秒間に60回呼ばれ、ある時は1秒間に30回呼ばれます。
つまり、例えば「20フレーム」と指定してしまうと、ある時は0.3秒かかり、ある時は0.6秒かかります。

なのでまずIsPassedIntervalは使わない方針でいいと思います。

「演出がリアル時間で早すぎないように」というのはつまり「どのような環境下(=フレームレートが変わって)でも○秒間表示させる」ということなので、演出については「○フレーム」ではなく「○秒」と指定するのが適切です。
ensyutsuCounterはフレーム単位ではなく秒単位と認識し、
ensyutsuCounter -= Time.deltaTime;などとして管理した方がいいでしょう。
(間にダメージ量表示等の処理を挟む場合でも「演出開始から○秒後、まだ呼ばれていなかったら処理を呼ぶ」みたいな作りにしておけばいいかと)

「連打の無効化」についても同様で、フレーム単位ではなく秒単位での管理にすべきかと思います。

その上で「画面タッチ」は毎フレーム受け付けにしておいて「演出中」「連打無効化中」(必要に応じて「計算中」も)なら何もしないでいいのではないかと思います。
(先行入力させたいなら「演出中」等にタッチされたら「タッチした」というフラグを立てておき、演出処理終了後にフラグをチェック、立っていたら「入力された」ということにして再度処理でいいかと)

希望通りの動きになるか分かりませんが、ご参考までに。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/11/21 20:51

    すみません、やはり結局、IsPassedIntervalを使用する方法を選択します。

    >つまり、例えば「20フレーム」と指定してしまうと、ある時は0.3秒かかり、ある時は0.6秒かかります。
    >なのでまずIsPassedIntervalは使わない方針でいいと思います。
    サンプルに載せた実装ですと、ensyutsuCounter をデクリメントするのは「IsPassedInterval = trueになったUpDateInterval」の中だけです。
    IsPassedIntervalがtrueになるのはUPDATE_INTERVAL周期なので、「updateがコールされる間隔が、UPDATE_INTERVAL未満である限りは」マシンの負荷に関わらず、ensyutsuCounterが0になるまでの時間は「20 * UPDATE_INTERVAL」です。

    ご提案の方法を採用しない理由ですが、
    ・「演出開始から○秒後、まだ呼ばれていなかったら処理を呼ぶ」の中で、「まだ呼ばれてなかったら」を管理するフラグが多数の処理ごとに必要となること
    ・「演出中に実施する処理A、Bがあり、A→Bの順にやらなければならない」ような箇所が多数あり
     「演出開始から〇秒後」のみのチェックだと、順序が怪しくなると思われる場所が多数あること
     例 A:ターゲット敵のHP減少 B:敵全滅チェック
      現在「
    if (ensyutsuCounter = 5)処理B
    if (ensyutsuCounter = 10)処理A」
     のように書いているところを、一括置換的に
      if (演出開始から0.1秒以上 && 処理B未実施)処理B
    if (演出開始から0.05秒以上 && 処理A未実施)処理A
     のように変えてしまうと、「処理が遅くて演出開始から0.12秒後に、はじめて上記箇所が呼ばれた」
     場合、B→Aと実行されてしまう。

    もちろん、上記はご回答を批判するものではなく、「もともとの描画/入力受け取り/計算の実装を、あまりに単純に考えすぎていたため、今から1フレームの概念を本格的に組み直すとなると非常に厳しい」という、私の方の問題です。
    せっかくご回答いただいたのにすみません。

    キャンセル

  • 2018/11/21 21:39

    最終的にどうするかはご自分で決めるものなので、そこはお好きにどうぞ。
    私も薄々「結果的にIsPassedInterval使う場合と変わらない気もするなー」と思ってましたし。

    でもそうすると、なんで環境によって描画が早くなるのか分からんのです。
    例えばUPDATE_INTERVALに「0.03」って指定してて(=30fps)、
    実環境が60fpsの場合は2回に1回スキップされるから結局30fpsで描画される。(これは問題無し)
    逆に実環境が15fpsの場合、1回ごとに呼び出されるが、Updateの時間は30fpsより長い。
    ここで演出を「20フレーム」と指定している場合、30fpsなら0.6秒で完了、15fpsなら1.33秒で完了。
    つまり本当は「開発機より性能の高いマシンだと、開発機と演出時間は同じ」「開発機より性能が低いマシンだと、開発機より演出が遅い(長い)」はずなのです。

    結局よく分からんから全部フレームじゃなくて時間単位で管理すればよくね?って発想でした。

    ちなみに
    if (演出開始から0.1秒以上 && 処理B未実施)処理B
    if (演出開始から0.05秒以上 && 処理A未実施)処理A
    こいつは
    if (演出開始から0.05秒以上 && 処理A未実施)処理A
    if (演出開始から0.1秒以上 && 処理B未実施)処理B
    こうすれば1.2秒後にこのメソッドに入ったとしても絶対に処理A→Bの順に行われます。
    (本来0.05〜0.1秒の間に行われるはずだった演出がある場合は飛ばされますが)

    更に一応別の選択肢を挙げるならば、諸々の処理をコルーチン化すると
    「AとBを並列実行」「Aの処理完了を待ってからBを実行」「AとBの処理を待ち合わせ」等が可能になります。
    http://tsubakit1.hateblo.jp/entry/2015/04/06/060608
    今回の使用は難しいかもしれませんが「こういう方法もあるんだなー」という参考になれば幸いです。

    キャンセル

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

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

関連した質問

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